From fb598bb76a0431aa3a880ecfa45054c996a070fb Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Fri, 13 May 2016 13:28:09 +0300 Subject: [PATCH 001/409] Disabled PCH support for gcc >= 6.0.0 Command line generation routine uses "-isystem" to include headers outside of "/modules" folder, but GCC 6 does not work when passed "-isystem /usr/include" option. --- cmake/OpenCVPCHSupport.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/OpenCVPCHSupport.cmake b/cmake/OpenCVPCHSupport.cmake index 13619daf27..e6585cae02 100644 --- a/cmake/OpenCVPCHSupport.cmake +++ b/cmake/OpenCVPCHSupport.cmake @@ -19,7 +19,7 @@ IF(CMAKE_COMPILER_IS_GNUCXX) ARGS ${CMAKE_CXX_COMPILER_ARG1} -dumpversion OUTPUT_VARIABLE gcc_compiler_version) #MESSAGE("GCC Version: ${gcc_compiler_version}") - IF(gcc_compiler_version VERSION_GREATER "4.2.-1") + IF(gcc_compiler_version VERSION_GREATER "4.2.-1" AND gcc_compiler_version VERSION_LESS "6.0.0") SET(PCHSupport_FOUND TRUE) ENDIF() From a8b2b4dba12e8d23bec2a8cac98c5838620c6c6b Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Fri, 27 May 2016 13:46:12 +0300 Subject: [PATCH 002/409] Packaging fixes for Ubuntu 16.04. --- cmake/OpenCVPackaging.cmake | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cmake/OpenCVPackaging.cmake b/cmake/OpenCVPackaging.cmake index 106c03ff17..9fef5e7476 100644 --- a/cmake/OpenCVPackaging.cmake +++ b/cmake/OpenCVPackaging.cmake @@ -98,15 +98,15 @@ if(HAVE_CUDA) set(CPACK_DEB_LIBS_PACKAGE_DEPENDS "cuda-core-libs-${cuda_version_suffix}, cuda-extra-libs-${cuda_version_suffix}") set(CPACK_DEB_DEV_PACKAGE_DEPENDS "cuda-headers-${cuda_version_suffix}") else() - set(CPACK_DEB_LIBS_PACKAGE_DEPENDS "cuda-cudart-${cuda_version_suffix}, cuda-npp-${cuda_version_suffix}") - set(CPACK_DEB_DEV_PACKAGE_DEPENDS "cuda-cudart-dev-${cuda_version_suffix}, cuda-npp-dev-${cuda_version_suffix}") + set(CPACK_DEB_LIBS_PACKAGE_DEPENDS "cuda-cudart-${cuda_version_suffix} | libcudart${CUDA_VERSION}, cuda-npp-${cuda_version_suffix} | libnppc${CUDA_VERSION}, cuda-npp-${cuda_version_suffix} | libnppi${CUDA_VERSION}, cuda-npp-${cuda_version_suffix} | libnpps${CUDA_VERSION}") + set(CPACK_DEB_DEV_PACKAGE_DEPENDS "cuda-cudart-dev-${cuda_version_suffix} | nvidia-cuda-dev (>=${CUDA_VERSION}), cuda-npp-dev-${cuda_version_suffix} | nvidia-cuda-dev (>=${CUDA_VERSION})") if(HAVE_CUFFT) - set(CPACK_DEB_LIBS_PACKAGE_DEPENDS "${CPACK_DEB_LIBS_PACKAGE_DEPENDS}, cuda-cufft-${cuda_version_suffix}") - set(CPACK_DEB_DEV_PACKAGE_DEPENDS "${CPACK_DEB_DEV_PACKAGE_DEPENDS}, cuda-cufft-dev-${cuda_version_suffix}") + set(CPACK_DEB_LIBS_PACKAGE_DEPENDS "${CPACK_DEB_LIBS_PACKAGE_DEPENDS}, cuda-cufft-${cuda_version_suffix} | libcufft${CUDA_VERSION}") + set(CPACK_DEB_DEV_PACKAGE_DEPENDS "${CPACK_DEB_DEV_PACKAGE_DEPENDS}, cuda-cufft-dev-${cuda_version_suffix} | nvidia-cuda-dev (>=${CUDA_VERSION})") endif() if(HAVE_HAVE_CUBLAS) - set(CPACK_DEB_LIBS_PACKAGE_DEPENDS "${CPACK_DEB_LIBS_PACKAGE_DEPENDS}, cuda-cublas-${cuda_version_suffix}") - set(CPACK_DEB_DEV_PACKAGE_DEPENDS "${CPACK_DEB_DEV_PACKAGE_DEPENDS}, cuda-cublas-dev-${cuda_version_suffix}") + set(CPACK_DEB_LIBS_PACKAGE_DEPENDS "${CPACK_DEB_LIBS_PACKAGE_DEPENDS}, cuda-cublas-${cuda_version_suffix} | libcublas${CUDA_VERSION}") + set(CPACK_DEB_DEV_PACKAGE_DEPENDS "${CPACK_DEB_DEV_PACKAGE_DEPENDS}, cuda-cublas-dev-${cuda_version_suffix} | nvidia-cuda-dev (>=${CUDA_VERSION})") endif() endif() endif() @@ -122,10 +122,17 @@ endif() set(STD_OPENCV_LIBS opencv-data) set(STD_OPENCV_DEV libopencv-dev) +set(ABI_VERSION_SUFFIX "") +if(CMAKE_COMPILER_IS_GNUCXX) + if(${CMAKE_OPENCV_GCC_VERSION_MAJOR} EQUAL 5) + set(ABI_VERSION_SUFFIX "v5") + endif() +endif() + foreach(module calib3d contrib core features2d flann gpu highgui imgproc legacy ml objdetect ocl photo stitching superres ts video videostab) if(HAVE_opencv_${module}) - list(APPEND STD_OPENCV_LIBS "libopencv-${module}2.4") + list(APPEND STD_OPENCV_LIBS "libopencv-${module}2.4${ABI_VERSION_SUFFIX}") list(APPEND STD_OPENCV_DEV "libopencv-${module}-dev") endif() endforeach() From 78de75397e512a3fb4b9aad677ebc4c1472fa9cc Mon Sep 17 00:00:00 2001 From: Gilles Rochefort Date: Sun, 29 May 2016 17:25:02 +0200 Subject: [PATCH 003/409] Fixed BackgroundSubstractorMOG2 in opencv_video. The number of gaussians involved in a mixture is supposed to be dynamically adjusted. After being increased, the number of gaussians can't be reduced anymore. It seems to be a regression as the legacy code located in modules/legacy/src/bgfg_gaussmix.cpp allows to reduce such number of gaussians. --- modules/video/src/bgfg_gaussmix2.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/video/src/bgfg_gaussmix2.cpp b/modules/video/src/bgfg_gaussmix2.cpp index 9700dfebd8..d5eff8224c 100644 --- a/modules/video/src/bgfg_gaussmix2.cpp +++ b/modules/video/src/bgfg_gaussmix2.cpp @@ -309,7 +309,7 @@ struct MOG2Invoker : ParallelLoopBody //internal: bool fitsPDF = false;//if it remains zero a new GMM mode will be added - int nmodes = modesUsed[x], nNewModes = nmodes;//current number of modes in GMM + int nmodes = modesUsed[x];//current number of modes in GMM float totalWeight = 0.f; float* mean_m = mean; @@ -415,8 +415,6 @@ struct MOG2Invoker : ParallelLoopBody gmm[mode].weight *= totalWeight; } - nmodes = nNewModes; - //make new mode if needed and exit if( !fitsPDF ) { From 2717ba735a896d45b99a4e1143354bea19c394f7 Mon Sep 17 00:00:00 2001 From: Gilles Rochefort Date: Fri, 3 Jun 2016 11:00:31 +0200 Subject: [PATCH 004/409] Fixed BackgroundSubstractorMOG2 ocl implementation. --- modules/ocl/src/opencl/bgfg_mog.cl | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/ocl/src/opencl/bgfg_mog.cl b/modules/ocl/src/opencl/bgfg_mog.cl index a3c2619abc..f18a115325 100644 --- a/modules/ocl/src/opencl/bgfg_mog.cl +++ b/modules/ocl/src/opencl/bgfg_mog.cl @@ -369,7 +369,6 @@ __kernel void mog2_kernel(__global T_FRAME * frame, __global int* fgmask, __glob bool fitsPDF = false; //if it remains zero a new GMM mode will be added int nmodes = modesUsed[y * modesUsed_step + x]; - int nNewModes = nmodes; //current number of modes in GMM float totalWeight = 0.0f; @@ -430,8 +429,6 @@ __kernel void mog2_kernel(__global T_FRAME * frame, __global int* fgmask, __glob for (int mode = 0; mode < nmodes; ++mode) weight[(mode * frame_row + y) * weight_step + x] *= totalWeight; - nmodes = nNewModes; - if (!fitsPDF) { int mode = nmodes == (NMIXTURES) ? (NMIXTURES) - 1 : nmodes++; From 9fee6996934e7444a4cf2f9de741e689472dada1 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 8 Jun 2016 14:18:38 +0300 Subject: [PATCH 005/409] Fixed package-contains-timestamped-gzip lintian warning for Debian packages. --- cmake/OpenCVPackaging.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/OpenCVPackaging.cmake b/cmake/OpenCVPackaging.cmake index 9fef5e7476..a4913ea050 100644 --- a/cmake/OpenCVPackaging.cmake +++ b/cmake/OpenCVPackaging.cmake @@ -325,7 +325,7 @@ if(CPACK_GENERATOR STREQUAL "DEB") set(CHANGELOG_PACKAGE_NAME "${CPACK_DEBIAN_COMPONENT_${comp_upcase}_NAME}") configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/changelog.Debian.in" "${DEBIAN_CHANGELOG_OUT_FILE}" @ONLY) - execute_process(COMMAND "${GZIP_TOOL}" "-cf9" "${DEBIAN_CHANGELOG_OUT_FILE}" + execute_process(COMMAND "${GZIP_TOOL}" "-ncf9" "${DEBIAN_CHANGELOG_OUT_FILE}" OUTPUT_FILE "${DEBIAN_CHANGELOG_OUT_FILE_GZ}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") @@ -337,7 +337,7 @@ if(CPACK_GENERATOR STREQUAL "DEB") set(CHANGELOG_OUT_FILE_GZ "${CMAKE_BINARY_DIR}/deb-packages-gen/${comp}/changelog.gz") file(WRITE ${CHANGELOG_OUT_FILE} "Upstream changelog stub. See https://github.com/Itseez/opencv/wiki/ChangeLog") - execute_process(COMMAND "${GZIP_TOOL}" "-cf9" "${CHANGELOG_OUT_FILE}" + execute_process(COMMAND "${GZIP_TOOL}" "-ncf9" "${CHANGELOG_OUT_FILE}" OUTPUT_FILE "${CHANGELOG_OUT_FILE_GZ}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") From e5d12502c594c90aec0212b9b98b195ff3be21af Mon Sep 17 00:00:00 2001 From: "S. Garrido" Date: Thu, 9 Jun 2016 17:34:00 +0100 Subject: [PATCH 006/409] Fix useExtrinsicGuess in solvePnP --- modules/calib3d/src/solvepnp.cpp | 56 ++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/modules/calib3d/src/solvepnp.cpp b/modules/calib3d/src/solvepnp.cpp index 3c1e50e6b2..ae28eb5af5 100644 --- a/modules/calib3d/src/solvepnp.cpp +++ b/modules/calib3d/src/solvepnp.cpp @@ -53,17 +53,36 @@ bool cv::solvePnP( InputArray _opoints, InputArray _ipoints, Mat opoints = _opoints.getMat(), ipoints = _ipoints.getMat(); int npoints = std::max(opoints.checkVector(3, CV_32F), opoints.checkVector(3, CV_64F)); CV_Assert( npoints >= 0 && npoints == std::max(ipoints.checkVector(2, CV_32F), ipoints.checkVector(2, CV_64F)) ); - _rvec.create(3, 1, CV_64F); - _tvec.create(3, 1, CV_64F); Mat cameraMatrix = _cameraMatrix.getMat(), distCoeffs = _distCoeffs.getMat(); + Mat rvec, tvec; + if( flags != CV_ITERATIVE ) + useExtrinsicGuess = false; + + if( useExtrinsicGuess ) + { + int rtype = _rvec.type(), ttype = _tvec.type(); + Size rsize = _rvec.size(), tsize = _tvec.size(); + CV_Assert( (rtype == CV_32F || rtype == CV_64F) && + (ttype == CV_32F || ttype == CV_64F) ); + CV_Assert( (rsize == Size(1, 3) || rsize == Size(3, 1)) && + (tsize == Size(1, 3) || tsize == Size(3, 1)) ); + } + else + { + _rvec.create(3, 1, CV_64F); + _tvec.create(3, 1, CV_64F); + } + rvec = _rvec.getMat(); + tvec = _tvec.getMat(); + if (flags == CV_EPNP) { cv::Mat undistortedPoints; cv::undistortPoints(ipoints, undistortedPoints, cameraMatrix, distCoeffs); epnp PnP(cameraMatrix, opoints, undistortedPoints); - cv::Mat R, rvec = _rvec.getMat(), tvec = _tvec.getMat(); + cv::Mat R; PnP.compute_pose(R, tvec); cv::Rodrigues(R, rvec); return true; @@ -75,7 +94,7 @@ bool cv::solvePnP( InputArray _opoints, InputArray _ipoints, cv::undistortPoints(ipoints, undistortedPoints, cameraMatrix, distCoeffs); p3p P3Psolver(cameraMatrix); - cv::Mat R, rvec = _rvec.getMat(), tvec = _tvec.getMat(); + cv::Mat R; bool result = P3Psolver.solve(R, tvec, opoints, undistortedPoints); if (result) cv::Rodrigues(R, rvec); @@ -85,7 +104,7 @@ bool cv::solvePnP( InputArray _opoints, InputArray _ipoints, { CvMat c_objectPoints = opoints, c_imagePoints = ipoints; CvMat c_cameraMatrix = cameraMatrix, c_distCoeffs = distCoeffs; - CvMat c_rvec = _rvec.getMat(), c_tvec = _tvec.getMat(); + CvMat c_rvec = rvec, c_tvec = tvec; cvFindExtrinsicCameraParams2(&c_objectPoints, &c_imagePoints, &c_cameraMatrix, c_distCoeffs.rows*c_distCoeffs.cols ? &c_distCoeffs : 0, &c_rvec, &c_tvec, useExtrinsicGuess ); @@ -316,10 +335,26 @@ void cv::solvePnPRansac(InputArray _opoints, InputArray _ipoints, CV_Assert(ipoints.depth() == CV_32F || ipoints.depth() == CV_64F); CV_Assert((ipoints.rows == 1 && ipoints.channels() == 2) || ipoints.cols*ipoints.channels() == 2); - _rvec.create(3, 1, CV_64FC1); - _tvec.create(3, 1, CV_64FC1); - Mat rvec = _rvec.getMat(); - Mat tvec = _tvec.getMat(); + Mat rvec, tvec; + if( flags != CV_ITERATIVE ) + useExtrinsicGuess = false; + + if( useExtrinsicGuess ) + { + int rtype = _rvec.type(), ttype = _tvec.type(); + Size rsize = _rvec.size(), tsize = _tvec.size(); + CV_Assert( (rtype == CV_32F || rtype == CV_64F) && + (ttype == CV_32F || ttype == CV_64F) ); + CV_Assert( (rsize == Size(1, 3) || rsize == Size(3, 1)) && + (tsize == Size(1, 3) || tsize == Size(3, 1)) ); + } + else + { + _rvec.create(3, 1, CV_64F); + _tvec.create(3, 1, CV_64F); + } + rvec = _rvec.getMat(); + tvec = _tvec.getMat(); Mat objectPoints = opoints.reshape(3, 1), imagePoints = ipoints.reshape(2, 1); @@ -360,7 +395,8 @@ void cv::solvePnPRansac(InputArray _opoints, InputArray _ipoints, Mat colInlierObjectPoints = inlierObjectPoints(Rect(i, 0, 1, 1)); objectPoints.col(index).copyTo(colInlierObjectPoints); } - solvePnP(inlierObjectPoints, inlierImagePoints, params.camera.intrinsics, params.camera.distortion, localRvec, localTvec, false, flags); + solvePnP(inlierObjectPoints, inlierImagePoints, params.camera.intrinsics, params.camera.distortion, + localRvec, localTvec, params.useExtrinsicGuess, flags); } localRvec.copyTo(rvec); localTvec.copyTo(tvec); From e0d64df76351f97c1dea5babb09954afbaea63a7 Mon Sep 17 00:00:00 2001 From: Ilya Lavrenov Date: Thu, 16 Jun 2016 12:53:59 +0300 Subject: [PATCH 007/409] fixed -Wunused-result warning --- modules/highgui/src/cap_ffmpeg_impl.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/highgui/src/cap_ffmpeg_impl.hpp b/modules/highgui/src/cap_ffmpeg_impl.hpp index dc3e10d490..da05bde324 100644 --- a/modules/highgui/src/cap_ffmpeg_impl.hpp +++ b/modules/highgui/src/cap_ffmpeg_impl.hpp @@ -2287,7 +2287,8 @@ bool OutputMediaStream_FFMPEG::open(const char* fileName, int width, int height, #if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(53, 2, 0) av_write_header(oc_); #else - avformat_write_header(oc_, NULL); + if (avformat_write_header(oc_, NULL) != 0) + return false; #endif return true; From c6be51eecd923cef6eac7c8eb60ca7eb9e41c19b Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 16 Jun 2016 14:29:36 +0300 Subject: [PATCH 008/409] cmake: fix JNI configuration problem for Android samples Some CMake 3.x (2.8.12.2 is fine) provides broken build configurations with following build error message: ld: fatal error: -soname: must take a non-empty argument Linker command line sample contains this: "-shared -Wl,-soname, -o" --- cmake/OpenCVDetectAndroidSDK.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/OpenCVDetectAndroidSDK.cmake b/cmake/OpenCVDetectAndroidSDK.cmake index a518d85eba..38430e214e 100644 --- a/cmake/OpenCVDetectAndroidSDK.cmake +++ b/cmake/OpenCVDetectAndroidSDK.cmake @@ -290,7 +290,7 @@ macro(add_android_project target path) set(android_proj_NATIVE_DEPS ${android_proj_NATIVE_DEPS} android) endif() - add_library(${JNI_LIB_NAME} MODULE ${android_proj_jni_files}) + add_library(${JNI_LIB_NAME} SHARED ${android_proj_jni_files}) target_link_libraries(${JNI_LIB_NAME} ${OPENCV_LINKER_LIBS} ${android_proj_NATIVE_DEPS}) set_target_properties(${JNI_LIB_NAME} PROPERTIES From 6677f81779760b1e35346653a7449d77dfb1a273 Mon Sep 17 00:00:00 2001 From: Ilya Lavrenov Date: Fri, 17 Jun 2016 11:00:31 +0300 Subject: [PATCH 009/409] fixed lintian warnings --- cmake/OpenCVPackaging.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/OpenCVPackaging.cmake b/cmake/OpenCVPackaging.cmake index a4913ea050..b57f46595e 100644 --- a/cmake/OpenCVPackaging.cmake +++ b/cmake/OpenCVPackaging.cmake @@ -266,6 +266,11 @@ ocv_get_lintian_version(LINTIAN_VERSION) set(LIBS_LINTIAN_OVERRIDES "binary-or-shlib-defines-rpath" # usr/lib/libopencv_core.so.2.4.12 "package-name-doesnt-match-sonames") # libopencv-calib3d2.4 libopencv-contrib2.4 +if(AARCH64 AND CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) + # GCC 4.8 has a bug which sometimes causes it to produce non-PIC aarch64 code. + list(APPEND LIBS_LINTIAN_OVERRIDES "shlib-with-non-pic-code") +endif() + if(HAVE_opencv_python) set(PYTHON_LINTIAN_OVERRIDES "binary-or-shlib-defines-rpath" # usr/lib/python2.7/dist-packages/cv2.so "missing-dependency-on-numpy-abi") From 9564531ec8064fdd2dbe7f0e3adbcf6959d0ed35 Mon Sep 17 00:00:00 2001 From: Tomi Date: Thu, 30 Jun 2016 10:41:03 +0300 Subject: [PATCH 010/409] features2d: Added missing SimpleBlob parameters Added missing SimpleBlobDetector parameters to features2d_init.cpp. --- modules/features2d/src/features2d_init.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/features2d/src/features2d_init.cpp b/modules/features2d/src/features2d_init.cpp index e12310a3b4..c310f01e85 100644 --- a/modules/features2d/src/features2d_init.cpp +++ b/modules/features2d/src/features2d_init.cpp @@ -139,12 +139,16 @@ CV_INIT_ALGORITHM(SimpleBlobDetector, "Feature2D.SimpleBlob", obj.info()->addParam(obj, "filterByColor", obj.params.filterByColor); obj.info()->addParam(obj, "blobColor", obj.params.blobColor); obj.info()->addParam(obj, "filterByArea", obj.params.filterByArea); + obj.info()->addParam(obj, "minArea", obj.params.minArea); obj.info()->addParam(obj, "maxArea", obj.params.maxArea); obj.info()->addParam(obj, "filterByCircularity", obj.params.filterByCircularity); + obj.info()->addParam(obj, "minCircularity", obj.params.minCircularity); obj.info()->addParam(obj, "maxCircularity", obj.params.maxCircularity); obj.info()->addParam(obj, "filterByInertia", obj.params.filterByInertia); + obj.info()->addParam(obj, "minInertiaRatio", obj.params.minInertiaRatio); obj.info()->addParam(obj, "maxInertiaRatio", obj.params.maxInertiaRatio); obj.info()->addParam(obj, "filterByConvexity", obj.params.filterByConvexity); + obj.info()->addParam(obj, "minConvexity", obj.params.minConvexity); obj.info()->addParam(obj, "maxConvexity", obj.params.maxConvexity); ) From c3dc7266d1937d09c89d5b11fcf0934fa02e1df0 Mon Sep 17 00:00:00 2001 From: Vitaliy Lyudvichenko Date: Wed, 29 Jun 2016 19:50:51 +0300 Subject: [PATCH 011/409] Fixing of AutoBuffer::allocate(nsz) method AutoBuffer::allocate(nsz) didn't work properly when (sz < nsz < fixed_size). In this case sz remained unchanged. --- modules/core/include/opencv2/core/operations.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/operations.hpp b/modules/core/include/opencv2/core/operations.hpp index 0ae51c6939..076a5e467d 100644 --- a/modules/core/include/opencv2/core/operations.hpp +++ b/modules/core/include/opencv2/core/operations.hpp @@ -2558,10 +2558,10 @@ template inline void AutoBuffer<_Tp, fixed_size if(_size <= size) return; deallocate(); + size = _size; if(_size > fixed_size) { ptr = cv::allocate<_Tp>(_size); - size = _size; } } From 882e120a9d6302ec015a25deec776cf8f543be9f Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 29 Jun 2016 12:35:38 +0300 Subject: [PATCH 012/409] update libpng 1.5.27 --- 3rdparty/libpng/CMakeLists.txt | 2 +- 3rdparty/libpng/LICENSE | 73 ++-- 3rdparty/libpng/README | 61 ++-- 3rdparty/libpng/arm/arm_init.c | 232 ++++++++++++ 3rdparty/libpng/png.c | 416 ++++++++++++--------- 3rdparty/libpng/png.h | 650 +++++++++++++++------------------ 3rdparty/libpng/pngconf.h | 66 ++-- 3rdparty/libpng/pngdebug.h | 22 +- 3rdparty/libpng/pngerror.c | 13 +- 3rdparty/libpng/pngget.c | 66 +++- 3rdparty/libpng/pnginfo.h | 7 +- 3rdparty/libpng/pnglibconf.h | 157 ++++---- 3rdparty/libpng/pngmem.c | 12 +- 3rdparty/libpng/pngpread.c | 128 +------ 3rdparty/libpng/pngpriv.h | 248 +++++++++---- 3rdparty/libpng/pngread.c | 33 +- 3rdparty/libpng/pngrio.c | 4 +- 3rdparty/libpng/pngrtran.c | 210 ++++++----- 3rdparty/libpng/pngrutil.c | 200 +++++----- 3rdparty/libpng/pngset.c | 248 ++++++++++--- 3rdparty/libpng/pngstruct.h | 21 +- 3rdparty/libpng/pngtrans.c | 12 +- 3rdparty/libpng/pngwio.c | 6 +- 3rdparty/libpng/pngwrite.c | 414 +++++---------------- 3rdparty/libpng/pngwtran.c | 20 +- 3rdparty/libpng/pngwutil.c | 605 +++--------------------------- 3rdparty/readme.txt | 4 +- 27 files changed, 1836 insertions(+), 2094 deletions(-) create mode 100644 3rdparty/libpng/arm/arm_init.c diff --git a/3rdparty/libpng/CMakeLists.txt b/3rdparty/libpng/CMakeLists.txt index 88bf04d604..76ead5d1dd 100644 --- a/3rdparty/libpng/CMakeLists.txt +++ b/3rdparty/libpng/CMakeLists.txt @@ -15,7 +15,7 @@ file(GLOB lib_srcs *.c) file(GLOB lib_hdrs *.h) if(ARM AND NEON) - list(APPEND lib_srcs arm/filter_neon.S) + list(APPEND lib_srcs arm/filter_neon.S arm/arm_init.c) add_definitions(-DPNG_ARM_NEON) endif() diff --git a/3rdparty/libpng/LICENSE b/3rdparty/libpng/LICENSE index 3a67d54588..11a0c9c9ca 100644 --- a/3rdparty/libpng/LICENSE +++ b/3rdparty/libpng/LICENSE @@ -10,20 +10,16 @@ this sentence. This code is released under the libpng license. -libpng versions 1.2.6, August 15, 2004, through 1.5.12, July 11, 2012, are -Copyright (c) 2004, 2006-2012 Glenn Randers-Pehrson, and are -distributed according to the same disclaimer and license as libpng-1.2.5 -with the following individual added to the list of Contributing Authors - - Cosmin Truta - -libpng versions 1.0.7, July 1, 2000, through 1.2.5 - October 3, 2002, are -Copyright (c) 2000-2002 Glenn Randers-Pehrson, and are -distributed according to the same disclaimer and license as libpng-1.0.6 -with the following individuals added to the list of Contributing Authors +libpng versions 1.0.7, July 1, 2000, through 1.5.27, May 26, 2016, are +Copyright (c) 2000-2002, 2004, 2006-2016 Glenn Randers-Pehrson, are +derived from libpng-1.0.6, and are distributed according to the same +disclaimer and license as libpng-1.0.6 with the following individuals +added to the list of Contributing Authors: Simon-Pierre Cadieux Eric S. Raymond + Mans Rullgard + Cosmin Truta Gilles Vollant and with the following additions to the disclaimer: @@ -36,18 +32,20 @@ and with the following additions to the disclaimer: the user. libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are -Copyright (c) 1998, 1999 Glenn Randers-Pehrson, and are -distributed according to the same disclaimer and license as libpng-0.96, -with the following individuals added to the list of Contributing Authors: +Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from +libpng-0.96, and are distributed according to the same disclaimer and +license as libpng-0.96, with the following individuals added to the list +of Contributing Authors: Tom Lane Glenn Randers-Pehrson Willem van Schaik libpng versions 0.89, June 1996, through 0.96, May 1997, are -Copyright (c) 1996, 1997 Andreas Dilger -Distributed according to the same disclaimer and license as libpng-0.88, -with the following individuals added to the list of Contributing Authors: +Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88, +and are distributed according to the same disclaimer and license as +libpng-0.88, with the following individuals added to the list of +Contributing Authors: John Bowler Kevin Bracey @@ -57,7 +55,7 @@ with the following individuals added to the list of Contributing Authors: Tom Tanner libpng versions 0.5, May 1995, through 0.88, January 1996, are -Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc. +Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. For the purposes of this copyright and license, "Contributing Authors" is defined as the following set of individuals: @@ -80,13 +78,13 @@ Permission is hereby granted to use, copy, modify, and distribute this source code, or portions hereof, for any purpose, without fee, subject to the following restrictions: -1. The origin of this source code must not be misrepresented. + 1. The origin of this source code must not be misrepresented. -2. Altered versions must be plainly marked as such and must not - be misrepresented as being the original source. + 2. Altered versions must be plainly marked as such and must not + be misrepresented as being the original source. -3. This Copyright notice may not be removed or altered from any - source or altered source distribution. + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. The Contributing Authors and Group 42, Inc. specifically permit, without fee, and encourage the use of this source code as a component to @@ -94,18 +92,31 @@ supporting the PNG file format in commercial products. If you use this source code in a product, acknowledgment is not required but would be appreciated. +END OF COPYRIGHT NOTICE, DISCLAIMER, and LICENSE. -A "png_get_copyright" function is available, for convenient use in "about" -boxes and the like: +TRADEMARK: - printf("%s",png_get_copyright(NULL)); +The name "libpng" has not been registered by the Copyright owner +as a trademark in any jurisdiction. However, because libpng has +been distributed and maintained world-wide, continually since 1995, +the Copyright owner claims "common-law trademark protection" in any +jurisdiction where common-law trademark is recognized. -Also, the PNG logo (in PNG format, of course) is supplied in the -files "pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31). +OSI CERTIFICATION: -Libpng is OSI Certified Open Source Software. OSI Certified Open Source is a -certification mark of the Open Source Initiative. +Libpng is OSI Certified Open Source Software. OSI Certified Open Source is +a certification mark of the Open Source Initiative. OSI has not addressed +the additional disclaimers inserted at version 1.0.7. + +EXPORT CONTROL: + +The Copyright owner believes that the Export Control Classification +Number (ECCN) for libpng is EAR99, which means not subject to export +controls or International Traffic in Arms Regulations (ITAR) because +it is open source, publicly available software, that does not contain +any encryption software. See the EAR, paragraphs 734.3(b)(3) and +734.7(b). Glenn Randers-Pehrson glennrp at users.sourceforge.net -July 11, 2012 +May 26, 2016 diff --git a/3rdparty/libpng/README b/3rdparty/libpng/README index 3f5b0d6b64..38ae14629e 100644 --- a/3rdparty/libpng/README +++ b/3rdparty/libpng/README @@ -1,11 +1,11 @@ -README for libpng version 1.5.12 - July 11, 2012 (shared library 15.0) +README for libpng version 1.5.27 - May 26, 2016 (shared library 15.0) See the note about version numbers near the top of png.h See INSTALL for instructions on how to install libpng. -Libpng comes in several distribution formats. Get libpng-*.tar.gz, -libpng-*.tar.xz or libpng-*.tar.bz2 if you want UNIX-style line endings -in the text files, or lpng*.zip if you want DOS-style line endings. +Libpng comes in several distribution formats. Get libpng-*.tar.gz or +libpng-*.tar.xz or if you want UNIX-style line endings in the text files, +or lpng*.7z or lpng*.zip if you want DOS-style line endings. Version 0.89 was the first official release of libpng. Don't let the fact that it's the first release fool you. The libpng library has been in @@ -23,18 +23,25 @@ earlier versions if you are using a shared library. The type of the png_uint_32, which will affect shared-library applications that use this function. -To avoid problems with changes to the internals of png_info_struct, +To avoid problems with changes to the internals of the png info_struct, new APIs have been made available in 0.95 to avoid direct application access to info_ptr. These functions are the png_set_ and png_get_ functions. These functions should be used when accessing/storing the info_struct data, rather than manipulating it directly, to avoid such problems in the future. -It is important to note that the APIs do not make current programs +It is important to note that the APIs did not make current programs that access the info struct directly incompatible with the new -library. However, it is strongly suggested that new programs use -the new APIs (as shown in example.c and pngtest.c), and older programs -be converted to the new format, to facilitate upgrades in the future. +library, through libpng-1.2.x. In libpng-1.4.x, which was meant to +be a transitional release, members of the png_struct and the +info_struct can still be accessed, but the compiler will issue a +warning about deprecated usage. Since libpng-1.5.0, direct access +to these structs is not allowed, and the definitions of the structs +reside in private pngstruct.h and pnginfo.h header files that are not +accessible to applications. It is strongly suggested that new +programs use the new APIs (as shown in example.c and pngtest.c), and +older programs be converted to the new format, to facilitate upgrades +in the future. **** Additions since 0.90 include the ability to compile libpng as a @@ -77,17 +84,21 @@ compression library that is useful for more things than just PNG files. You can use zlib as a drop-in replacement for fread() and fwrite() if you are so inclined. -zlib should be available at the same place that libpng is, or at. -ftp://ftp.info-zip.org/pub/infozip/zlib +zlib should be available at the same place that libpng is, or at zlib.net. You may also want a copy of the PNG specification. It is available as an RFC, a W3C Recommendation, and an ISO/IEC Standard. You can find these at http://www.libpng.org/pub/png/documents/ This code is currently being archived at libpng.sf.net in the -[DOWNLOAD] area, and on CompuServe, Lib 20 (PNG SUPPORT) -at GO GRAPHSUP. If you can't find it in any of those places, -e-mail me, and I'll help you find it. +[DOWNLOAD] area, and at ftp://ftp.simplesystems.org. If you can't find it +in any of those places, e-mail me, and I'll help you find it. + +I am not a lawyer, but I believe that the Export Control Classification +Number (ECCN) for libpng is EAR99, which means not subject to export +controls or International Traffic in Arms Regulations (ITAR) because it +is open source, publicly available software, that does not contain any +encryption software. See the EAR, paragraphs 734.3(b)(3) and 734.7(b). If you have any code changes, requests, problems, etc., please e-mail them to me. Also, I'd appreciate any make files or project files, @@ -105,7 +116,7 @@ based in a large way on Guy's and Andreas' earlier work), and the PNG development group. Send comments/corrections/commendations to png-mng-implement at -lists.sourceforge.net (subscription required; visit +lists.sourceforge.net (subscription required; visit https://lists.sourceforge.net/lists/listinfo/png-mng-implement to subscribe) or to glennrp at users.sourceforge.net @@ -123,7 +134,7 @@ and ...". If in doubt, send questions to me. I'll bounce them to others, if necessary. Please do not send suggestions on how to change PNG. We have -been discussing PNG for sixteen years now, and it is official and +been discussing PNG for twenty years now, and it is official and finished. If you have suggestions for libpng, however, I'll gladly listen. Even if your suggestion is not used immediately, it may be used later. @@ -167,23 +178,23 @@ Files in this distribution: pngwrite.c => High-level write functions pngwtran.c => Write data transformations pngwutil.c => Write utility functions + arm => Contains optimized code for the ARM platform contrib => Contributions gregbook => source code for PNG reading and writing, from Greg Roelofs' "PNG: The Definitive Guide", O'Reilly, 1999 - msvctest => Builds and runs pngtest using a MSVC workspace - pngminus => Simple pnm2png and png2pnm programs - pngsuite => Test images - visupng => Contains a MSVC workspace for VisualPng + libtests => Test programs + pngminim => Minimal decoder, encoder, and progressive decoder + programs demonstrating use of pngusr.dfa + pngminus => Simple pnm2png and png2pnm programs + pngsuite => Test images + visupng => Contains a MSVC workspace for VisualPng projects => Contains project files and workspaces for building a DLL - cbuilder5 => Contains a Borland workspace for building - libpng and zlib - visualc6 => Contains a Microsoft Visual C++ (MSVC) - workspace for building libpng and zlib + owatcom => Contains a WATCOM project for building libpng visualc71 => Contains a Microsoft Visual C++ (MSVC) workspace for building libpng and zlib - xcode => Contains an Apple xcode + vstudio => Contains a Microsoft Visual C++ (MSVC) workspace for building libpng and zlib scripts => Directory containing scripts for building libpng: (see scripts/README.txt for the list of scripts) diff --git a/3rdparty/libpng/arm/arm_init.c b/3rdparty/libpng/arm/arm_init.c new file mode 100644 index 0000000000..6a6a019acb --- /dev/null +++ b/3rdparty/libpng/arm/arm_init.c @@ -0,0 +1,232 @@ + +/* arm_init.c - NEON optimised filter functions + * + * Copyright (c) 2013 Glenn Randers-Pehrson + * Written by Mans Rullgard, 2011. + * Last changed in libpng 1.6.8 [December 19, 2013] + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ +/* Below, after checking __linux__, various non-C90 POSIX 1003.1 functions are + * called. + */ +#define _POSIX_SOURCE 1 + +#include "../pngpriv.h" + +#ifdef PNG_READ_SUPPORTED +#if PNG_ARM_NEON_OPT > 0 +#ifdef PNG_ARM_NEON_CHECK_SUPPORTED /* Do run-time checks */ +#include /* for sig_atomic_t */ + +#ifdef __ANDROID__ +/* Linux provides access to information about CPU capabilites via + * /proc/self/auxv, however Android blocks this while still claiming to be + * Linux. The Andoid NDK, however, provides appropriate support. + * + * Documentation: http://www.kandroid.org/ndk/docs/CPU-ARM-NEON.html + */ +#include + +static int +png_have_neon(png_structp png_ptr) +{ + /* This is a whole lot easier than the mess below, however it is probably + * implemented as below, therefore it is better to cache the result (these + * function calls may be slow!) + */ + PNG_UNUSED(png_ptr) + return android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM && + (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0; +} +#elif defined(__linux__) +/* The generic __linux__ implementation requires reading /proc/self/auxv and + * looking at each element for one that records NEON capabilities. + */ +#include /* for POSIX 1003.1 */ +#include /* for EINTR */ + +#include +#include +#include +#include +#include + +/* A read call may be interrupted, in which case it returns -1 and sets errno to + * EINTR if nothing was done, otherwise (if something was done) a partial read + * may result. + */ +static size_t +safe_read(png_structp png_ptr, int fd, void *buffer_in, size_t nbytes) +{ + size_t ntotal = 0; + char *buffer = png_voidcast(char*, buffer_in); + + while (nbytes > 0) + { + unsigned int nread; + int iread; + + /* Passing nread > INT_MAX to read is implementation defined in POSIX + * 1003.1, therefore despite the unsigned argument portable code must + * limit the value to INT_MAX! + */ + if (nbytes > INT_MAX) + nread = INT_MAX; + + else + nread = (unsigned int)/*SAFE*/nbytes; + + iread = read(fd, buffer, nread); + + if (iread == -1) + { + /* This is the devil in the details, a read can terminate early with 0 + * bytes read because of EINTR, yet it still returns -1 otherwise end + * of file cannot be distinguished. + */ + if (errno != EINTR) + { + png_warning(png_ptr, "/proc read failed"); + return 0; /* I.e., a permanent failure */ + } + } + + else if (iread < 0) + { + /* Not a valid 'read' result: */ + png_warning(png_ptr, "OS /proc read bug"); + return 0; + } + + else if (iread > 0) + { + /* Continue reading until a permanent failure, or EOF */ + buffer += iread; + nbytes -= (unsigned int)/*SAFE*/iread; + ntotal += (unsigned int)/*SAFE*/iread; + } + + else + return ntotal; + } + + return ntotal; /* nbytes == 0 */ +} + +static int +png_have_neon(png_structp png_ptr) +{ + int fd = open("/proc/self/auxv", O_RDONLY); + Elf32_auxv_t aux; + + /* Failsafe: failure to open means no NEON */ + if (fd == -1) + { + png_warning(png_ptr, "/proc/self/auxv open failed"); + return 0; + } + + while (safe_read(png_ptr, fd, &aux, sizeof aux) == sizeof aux) + { + if (aux.a_type == AT_HWCAP && (aux.a_un.a_val & HWCAP_NEON) != 0) + { + close(fd); + return 1; + } + } + + close(fd); + return 0; +} +#else + /* We don't know how to do a run-time check on this system */ +# error "no support for run-time ARM NEON checks" +#endif /* OS checks */ +#endif /* PNG_ARM_NEON_CHECK_SUPPORTED */ + +#ifndef PNG_ALIGNED_MEMORY_SUPPORTED +# error "ALIGNED_MEMORY is required; set: -DPNG_ALIGNED_MEMORY_SUPPORTED" +#endif + +void +png_init_filter_functions_neon(png_structp pp, unsigned int bpp) +{ + /* The switch statement is compiled in for ARM_NEON_API, the call to + * png_have_neon is compiled in for ARM_NEON_CHECK. If both are defined + * the check is only performed if the API has not set the NEON option on + * or off explicitly. In this case the check controls what happens. + * + * If the CHECK is not compiled in and the option is UNSET the behavior prior + * to 1.6.7 was to use the NEON code - this was a bug caused by having the + * wrong order of the 'ON' and 'default' cases. UNSET now defaults to OFF, + * as documented in png.h + */ +#ifdef PNG_ARM_NEON_API_SUPPORTED + switch ((pp->options >> PNG_ARM_NEON) & 3) + { + case PNG_OPTION_UNSET: + /* Allow the run-time check to execute if it has been enabled - + * thus both API and CHECK can be turned on. If it isn't supported + * this case will fall through to the 'default' below, which just + * returns. + */ +#endif /* PNG_ARM_NEON_API_SUPPORTED */ +#ifdef PNG_ARM_NEON_CHECK_SUPPORTED + { + static volatile sig_atomic_t no_neon = -1; /* not checked */ + + if (no_neon < 0) + no_neon = !png_have_neon(pp); + + if (no_neon) + return; + } +#ifdef PNG_ARM_NEON_API_SUPPORTED + break; +#endif +#endif /* PNG_ARM_NEON_CHECK_SUPPORTED */ + +#ifdef PNG_ARM_NEON_API_SUPPORTED + default: /* OFF or INVALID */ + return; + + case PNG_OPTION_ON: + /* Option turned on */ + break; + } +#endif + + /* IMPORTANT: any new external functions used here must be declared using + * PNG_INTERNAL_FUNCTION in ../pngpriv.h. This is required so that the + * 'prefix' option to configure works: + * + * ./configure --with-libpng-prefix=foobar_ + * + * Verify you have got this right by running the above command, doing a build + * and examining pngprefix.h; it must contain a #define for every external + * function you add. (Notice that this happens automatically for the + * initialization function.) + */ + pp->read_filter[PNG_FILTER_VALUE_UP-1] = png_read_filter_row_up_neon; + + if (bpp == 3) + { + pp->read_filter[PNG_FILTER_VALUE_SUB-1] = png_read_filter_row_sub3_neon; + pp->read_filter[PNG_FILTER_VALUE_AVG-1] = png_read_filter_row_avg3_neon; + pp->read_filter[PNG_FILTER_VALUE_PAETH-1] = + png_read_filter_row_paeth3_neon; + } + + else if (bpp == 4) + { + pp->read_filter[PNG_FILTER_VALUE_SUB-1] = png_read_filter_row_sub4_neon; + pp->read_filter[PNG_FILTER_VALUE_AVG-1] = png_read_filter_row_avg4_neon; + pp->read_filter[PNG_FILTER_VALUE_PAETH-1] = + png_read_filter_row_paeth4_neon; + } +} +#endif /* PNG_ARM_NEON_OPT > 0 */ +#endif /* PNG_READ_SUPPORTED */ diff --git a/3rdparty/libpng/png.c b/3rdparty/libpng/png.c index 6808c5cb90..711b7c4629 100644 --- a/3rdparty/libpng/png.c +++ b/3rdparty/libpng/png.c @@ -1,8 +1,8 @@ /* png.c - location for general purpose libpng functions * - * Last changed in libpng 1.5.11 [June 14, 2012] - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.23 [July 23, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -14,7 +14,7 @@ #include "pngpriv.h" /* Generate a compiler error if there is an old png.h in the search path. */ -typedef png_libpng_version_1_5_12 Your_png_h_is_not_version_1_5_12; +typedef png_libpng_version_1_5_27 Your_png_h_is_not_version_1_5_27; /* Tells libpng that we have already handled the first "num_bytes" bytes * of the PNG file signature. If the PNG data is embedded into another @@ -26,15 +26,20 @@ typedef png_libpng_version_1_5_12 Your_png_h_is_not_version_1_5_12; void PNGAPI png_set_sig_bytes(png_structp png_ptr, int num_bytes) { + unsigned int nb = (unsigned int)num_bytes; + png_debug(1, "in png_set_sig_bytes"); if (png_ptr == NULL) return; - if (num_bytes > 8) + if (num_bytes < 0) + nb = 0; + + if (nb > 8) png_error(png_ptr, "Too many bytes for PNG signature"); - png_ptr->sig_bytes = (png_byte)(num_bytes < 0 ? 0 : num_bytes); + png_ptr->sig_bytes = (png_byte)nb; } /* Checks whether the supplied bytes match the PNG signature. We allow @@ -73,13 +78,16 @@ PNG_FUNCTION(voidpf /* PRIVATE */, png_zalloc,(voidpf png_ptr, uInt items, uInt size),PNG_ALLOCATED) { png_voidp ptr; - png_structp p=(png_structp)png_ptr; - png_uint_32 save_flags=p->flags; + png_structp p; + png_uint_32 save_flags; png_alloc_size_t num_bytes; if (png_ptr == NULL) return (NULL); + p=(png_structp)png_ptr; + save_flags=p->flags; + if (items > PNG_UINT_32_MAX/size) { png_warning (p, "Potential overflow in png_zalloc()"); @@ -146,9 +154,10 @@ png_calculate_crc(png_structp png_ptr, png_const_bytep ptr, png_size_t length) do { uInt safeLength = (uInt)length; +#ifndef __COVERITY__ if (safeLength == 0) safeLength = (uInt)-1; /* evil, but safe */ - +#endif crc = crc32(crc, ptr, safeLength); /* The following should never issue compiler warnings, if they do the @@ -171,49 +180,51 @@ png_calculate_crc(png_structp png_ptr, png_const_bytep ptr, png_size_t length) int png_user_version_check(png_structp png_ptr, png_const_charp user_png_ver) { - if (user_png_ver) + /* Libpng versions 1.0.0 and later are binary compatible if the version + * string matches through the second '.'; we must recompile any + * applications that use any older library version. + */ + + if (user_png_ver != NULL) { - int i = 0; + int i = -1; + int found_dots = 0; do { - if (user_png_ver[i] != png_libpng_ver[i]) + i++; + if (user_png_ver[i] != PNG_LIBPNG_VER_STRING[i]) png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH; - } while (png_libpng_ver[i++]); + if (user_png_ver[i] == '.') + found_dots++; + } while (found_dots < 2 && user_png_ver[i] != 0 && + PNG_LIBPNG_VER_STRING[i] != 0); } else png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH; - if (png_ptr->flags & PNG_FLAG_LIBRARY_MISMATCH) + if ((png_ptr->flags & PNG_FLAG_LIBRARY_MISMATCH) != 0) { - /* Libpng 0.90 and later are binary incompatible with libpng 0.89, so - * we must recompile any applications that use any older library version. - * For versions after libpng 1.0, we will be compatible, so we need - * only check the first digit. - */ - if (user_png_ver == NULL || user_png_ver[0] != png_libpng_ver[0] || - (user_png_ver[0] == '1' && user_png_ver[2] != png_libpng_ver[2]) || - (user_png_ver[0] == '0' && user_png_ver[2] < '9')) - { #ifdef PNG_WARNINGS_SUPPORTED - size_t pos = 0; - char m[128]; + size_t pos = 0; + char m[128]; - pos = png_safecat(m, sizeof m, pos, "Application built with libpng-"); - pos = png_safecat(m, sizeof m, pos, user_png_ver); - pos = png_safecat(m, sizeof m, pos, " but running with "); - pos = png_safecat(m, sizeof m, pos, png_libpng_ver); + pos = png_safecat(m, (sizeof m), pos, + "Application built with libpng-"); + pos = png_safecat(m, (sizeof m), pos, user_png_ver); + pos = png_safecat(m, (sizeof m), pos, " but running with "); + pos = png_safecat(m, (sizeof m), pos, PNG_LIBPNG_VER_STRING); + PNG_UNUSED(pos) - png_warning(png_ptr, m); + png_warning(png_ptr, m); #endif #ifdef PNG_ERROR_NUMBERS_SUPPORTED - png_ptr->flags = 0; + png_ptr->flags = 0; #endif - return 0; - } + return 0; } /* Success return. */ @@ -300,6 +311,8 @@ png_info_init_3(png_infopp ptr_ptr, png_size_t png_info_struct_size) png_destroy_struct(info_ptr); info_ptr = (png_infop)png_create_struct(PNG_STRUCT_INFO); *ptr_ptr = info_ptr; + if (info_ptr == NULL) + return; } /* Set everything to 0 */ @@ -337,42 +350,43 @@ png_free_data(png_structp png_ptr, png_infop info_ptr, png_uint_32 mask, #ifdef PNG_TEXT_SUPPORTED /* Free text item num or (if num == -1) all text items */ - if ((mask & PNG_FREE_TEXT) & info_ptr->free_me) + if (info_ptr->text != 0 && + ((mask & PNG_FREE_TEXT) & info_ptr->free_me) != 0) { if (num != -1) { - if (info_ptr->text && info_ptr->text[num].key) - { - png_free(png_ptr, info_ptr->text[num].key); - info_ptr->text[num].key = NULL; - } + png_free(png_ptr, info_ptr->text[num].key); + info_ptr->text[num].key = NULL; } else { int i; + for (i = 0; i < info_ptr->num_text; i++) - png_free_data(png_ptr, info_ptr, PNG_FREE_TEXT, i); + png_free(png_ptr, info_ptr->text[i].key); + png_free(png_ptr, info_ptr->text); info_ptr->text = NULL; - info_ptr->num_text=0; + info_ptr->num_text = 0; } } #endif #ifdef PNG_tRNS_SUPPORTED /* Free any tRNS entry */ - if ((mask & PNG_FREE_TRNS) & info_ptr->free_me) + if (((mask & PNG_FREE_TRNS) & info_ptr->free_me) != 0) { + info_ptr->valid &= ~PNG_INFO_tRNS; png_free(png_ptr, info_ptr->trans_alpha); info_ptr->trans_alpha = NULL; - info_ptr->valid &= ~PNG_INFO_tRNS; + info_ptr->num_trans = 0; } #endif #ifdef PNG_sCAL_SUPPORTED /* Free any sCAL entry */ - if ((mask & PNG_FREE_SCAL) & info_ptr->free_me) + if (((mask & PNG_FREE_SCAL) & info_ptr->free_me) != 0) { png_free(png_ptr, info_ptr->scal_s_width); png_free(png_ptr, info_ptr->scal_s_height); @@ -384,20 +398,20 @@ png_free_data(png_structp png_ptr, png_infop info_ptr, png_uint_32 mask, #ifdef PNG_pCAL_SUPPORTED /* Free any pCAL entry */ - if ((mask & PNG_FREE_PCAL) & info_ptr->free_me) + if (((mask & PNG_FREE_PCAL) & info_ptr->free_me) != 0) { png_free(png_ptr, info_ptr->pcal_purpose); png_free(png_ptr, info_ptr->pcal_units); info_ptr->pcal_purpose = NULL; info_ptr->pcal_units = NULL; + if (info_ptr->pcal_params != NULL) { int i; - for (i = 0; i < (int)info_ptr->pcal_nparams; i++) - { + + for (i = 0; i < info_ptr->pcal_nparams; i++) png_free(png_ptr, info_ptr->pcal_params[i]); - info_ptr->pcal_params[i] = NULL; - } + png_free(png_ptr, info_ptr->pcal_params); info_ptr->pcal_params = NULL; } @@ -406,8 +420,8 @@ png_free_data(png_structp png_ptr, png_infop info_ptr, png_uint_32 mask, #endif #ifdef PNG_iCCP_SUPPORTED - /* Free any iCCP entry */ - if ((mask & PNG_FREE_ICCP) & info_ptr->free_me) + /* Free any profile entry */ + if (((mask & PNG_FREE_ICCP) & info_ptr->free_me) != 0) { png_free(png_ptr, info_ptr->iccp_name); png_free(png_ptr, info_ptr->iccp_profile); @@ -419,74 +433,62 @@ png_free_data(png_structp png_ptr, png_infop info_ptr, png_uint_32 mask, #ifdef PNG_sPLT_SUPPORTED /* Free a given sPLT entry, or (if num == -1) all sPLT entries */ - if ((mask & PNG_FREE_SPLT) & info_ptr->free_me) + if (info_ptr->splt_palettes != 0 && + ((mask & PNG_FREE_SPLT) & info_ptr->free_me) != 0) { if (num != -1) { - if (info_ptr->splt_palettes) - { - png_free(png_ptr, info_ptr->splt_palettes[num].name); - png_free(png_ptr, info_ptr->splt_palettes[num].entries); - info_ptr->splt_palettes[num].name = NULL; - info_ptr->splt_palettes[num].entries = NULL; - } - } - - else - { - if (info_ptr->splt_palettes_num) - { - int i; - for (i = 0; i < (int)info_ptr->splt_palettes_num; i++) - png_free_data(png_ptr, info_ptr, PNG_FREE_SPLT, i); - - png_free(png_ptr, info_ptr->splt_palettes); - info_ptr->splt_palettes = NULL; - info_ptr->splt_palettes_num = 0; - } - info_ptr->valid &= ~PNG_INFO_sPLT; - } - } -#endif - -#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED - if (png_ptr->unknown_chunk.data) - { - png_free(png_ptr, png_ptr->unknown_chunk.data); - png_ptr->unknown_chunk.data = NULL; - } - - if ((mask & PNG_FREE_UNKN) & info_ptr->free_me) - { - if (num != -1) - { - if (info_ptr->unknown_chunks) - { - png_free(png_ptr, info_ptr->unknown_chunks[num].data); - info_ptr->unknown_chunks[num].data = NULL; - } + png_free(png_ptr, info_ptr->splt_palettes[num].name); + png_free(png_ptr, info_ptr->splt_palettes[num].entries); + info_ptr->splt_palettes[num].name = NULL; + info_ptr->splt_palettes[num].entries = NULL; } else { int i; - if (info_ptr->unknown_chunks_num) + for (i = 0; i < info_ptr->splt_palettes_num; i++) { - for (i = 0; i < info_ptr->unknown_chunks_num; i++) - png_free_data(png_ptr, info_ptr, PNG_FREE_UNKN, i); - - png_free(png_ptr, info_ptr->unknown_chunks); - info_ptr->unknown_chunks = NULL; - info_ptr->unknown_chunks_num = 0; + png_free(png_ptr, info_ptr->splt_palettes[i].name); + png_free(png_ptr, info_ptr->splt_palettes[i].entries); } + + png_free(png_ptr, info_ptr->splt_palettes); + info_ptr->splt_palettes = NULL; + info_ptr->splt_palettes_num = 0; + info_ptr->valid &= ~PNG_INFO_sPLT; + } + } +#endif + +#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED + if (info_ptr->unknown_chunks != 0 && + ((mask & PNG_FREE_UNKN) & info_ptr->free_me) != 0) + { + if (num != -1) + { + png_free(png_ptr, info_ptr->unknown_chunks[num].data); + info_ptr->unknown_chunks[num].data = NULL; + } + + else + { + int i; + + for (i = 0; i < info_ptr->unknown_chunks_num; i++) + png_free(png_ptr, info_ptr->unknown_chunks[i].data); + + png_free(png_ptr, info_ptr->unknown_chunks); + info_ptr->unknown_chunks = NULL; + info_ptr->unknown_chunks_num = 0; } } #endif #ifdef PNG_hIST_SUPPORTED /* Free any hIST entry */ - if ((mask & PNG_FREE_HIST) & info_ptr->free_me) + if (((mask & PNG_FREE_HIST) & info_ptr->free_me) != 0) { png_free(png_ptr, info_ptr->hist); info_ptr->hist = NULL; @@ -495,9 +497,9 @@ png_free_data(png_structp png_ptr, png_infop info_ptr, png_uint_32 mask, #endif /* Free any PLTE entry that was internally allocated */ - if ((mask & PNG_FREE_PLTE) & info_ptr->free_me) + if (((mask & PNG_FREE_PLTE) & info_ptr->free_me) != 0) { - png_zfree(png_ptr, info_ptr->palette); + png_free(png_ptr, info_ptr->palette); info_ptr->palette = NULL; info_ptr->valid &= ~PNG_INFO_PLTE; info_ptr->num_palette = 0; @@ -505,16 +507,14 @@ png_free_data(png_structp png_ptr, png_infop info_ptr, png_uint_32 mask, #ifdef PNG_INFO_IMAGE_SUPPORTED /* Free any image bits attached to the info structure */ - if ((mask & PNG_FREE_ROWS) & info_ptr->free_me) + if (((mask & PNG_FREE_ROWS) & info_ptr->free_me) != 0) { - if (info_ptr->row_pointers) + if (info_ptr->row_pointers != 0) { - int row; - for (row = 0; row < (int)info_ptr->height; row++) - { + png_uint_32 row; + for (row = 0; row < info_ptr->height; row++) png_free(png_ptr, info_ptr->row_pointers[row]); - info_ptr->row_pointers[row] = NULL; - } + png_free(png_ptr, info_ptr->row_pointers); info_ptr->row_pointers = NULL; } @@ -655,14 +655,15 @@ png_get_copyright(png_const_structp png_ptr) #else # ifdef __STDC__ return PNG_STRING_NEWLINE \ - "libpng version 1.5.12 - July 11, 2012" PNG_STRING_NEWLINE \ - "Copyright (c) 1998-2012 Glenn Randers-Pehrson" PNG_STRING_NEWLINE \ + "libpng version 1.5.27 - May 26, 2016" PNG_STRING_NEWLINE \ + "Copyright (c) 1998-2002,2004,2006-2016 Glenn Randers-Pehrson" \ + PNG_STRING_NEWLINE \ "Copyright (c) 1996-1997 Andreas Dilger" PNG_STRING_NEWLINE \ "Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc." \ PNG_STRING_NEWLINE; # else - return "libpng version 1.5.12 - July 11, 2012\ - Copyright (c) 1998-2012 Glenn Randers-Pehrson\ + return "libpng version 1.5.27 - May 26, 2016\ + Copyright (c) 1998-2002,2004,2006-2016 Glenn Randers-Pehrson\ Copyright (c) 1996-1997 Andreas Dilger\ Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc."; # endif @@ -892,7 +893,8 @@ int png_XYZ_from_xy(png_XYZ *XYZ, png_xy xy) /* Check xy and, implicitly, z. Note that wide gamut color spaces typically * have end points with 0 tristimulus values (these are impossible end - * points, but they are used to cover the possible colors.) + * points, but they are used to cover the possible colors). We check + * xy.whitey against 5, not 0, to avoid a possible integer overflow. */ if (xy.redx < 0 || xy.redx > PNG_FP_1) return 1; if (xy.redy < 0 || xy.redy > PNG_FP_1-xy.redx) return 1; @@ -901,7 +903,7 @@ int png_XYZ_from_xy(png_XYZ *XYZ, png_xy xy) if (xy.bluex < 0 || xy.bluex > PNG_FP_1) return 1; if (xy.bluey < 0 || xy.bluey > PNG_FP_1-xy.bluex) return 1; if (xy.whitex < 0 || xy.whitex > PNG_FP_1) return 1; - if (xy.whitey < 0 || xy.whitey > PNG_FP_1-xy.whitex) return 1; + if (xy.whitey < 5 || xy.whitey > PNG_FP_1-xy.whitex) return 1; /* The reverse calculation is more difficult because the original tristimulus * value had 9 independent values (red,green,blue)x(X,Y,Z) however only 8 @@ -1165,6 +1167,17 @@ int png_XYZ_from_xy_checked(png_structp png_ptr, png_XYZ *XYZ, png_xy xy) } #endif +#ifdef __GNUC__ +/* This exists solely to work round a warning from GNU C. */ +static int /* PRIVATE */ +png_gt(size_t a, size_t b) +{ + return a > b; +} +#else +# define png_gt(a,b) ((a) > (b)) +#endif + void /* PRIVATE */ png_check_IHDR(png_structp png_ptr, png_uint_32 width, png_uint_32 height, int bit_depth, @@ -1179,53 +1192,68 @@ png_check_IHDR(png_structp png_ptr, png_warning(png_ptr, "Image width is zero in IHDR"); error = 1; } + else if (width > PNG_UINT_31_MAX) + { + png_warning(png_ptr, "Invalid image width in IHDR"); + error = 1; + } + + else if (png_gt(width, + (PNG_SIZE_MAX >> 3) /* 8-byte RGBA pixels */ + - 48 /* big_row_buf hack */ + - 1 /* filter byte */ + - 7*8 /* rounding width to multiple of 8 pix */ + - 8)) /* extra max_pixel_depth pad */ + { + /* The size of the row must be within the limits of this architecture. + * Because the read code can perform arbitrary transformations the + * maximum size is checked here. Because the code in png_read_start_row + * adds extra space "for safety's sake" in several places a conservative + * limit is used here. + * + * NOTE: it would be far better to check the size that is actually used, + * but the effect in the real world is minor and the changes are more + * extensive, therefore much more dangerous and much more difficult to + * write in a way that avoids compiler warnings. + */ + png_warning(png_ptr, "Image width is too large for this architecture"); + error = 1; + } + else + { +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + if (width > png_ptr->user_width_max) +# else + if (width > PNG_USER_WIDTH_MAX) +# endif + { + png_warning(png_ptr, "Image width exceeds user limit in IHDR"); + error = 1; + } + } if (height == 0) { png_warning(png_ptr, "Image height is zero in IHDR"); error = 1; } - -# ifdef PNG_SET_USER_LIMITS_SUPPORTED - if (width > png_ptr->user_width_max) - -# else - if (width > PNG_USER_WIDTH_MAX) -# endif - { - png_warning(png_ptr, "Image width exceeds user limit in IHDR"); - error = 1; - } - -# ifdef PNG_SET_USER_LIMITS_SUPPORTED - if (height > png_ptr->user_height_max) -# else - if (height > PNG_USER_HEIGHT_MAX) -# endif - { - png_warning(png_ptr, "Image height exceeds user limit in IHDR"); - error = 1; - } - - if (width > PNG_UINT_31_MAX) - { - png_warning(png_ptr, "Invalid image width in IHDR"); - error = 1; - } - - if (height > PNG_UINT_31_MAX) + else if (height > PNG_UINT_31_MAX) { png_warning(png_ptr, "Invalid image height in IHDR"); error = 1; } - - if (width > (PNG_UINT_32_MAX - >> 3) /* 8-byte RGBA pixels */ - - 48 /* bigrowbuf hack */ - - 1 /* filter byte */ - - 7*8 /* rounding of width to multiple of 8 pixels */ - - 8) /* extra max_pixel_depth pad */ - png_warning(png_ptr, "Width is too large for libpng to process pixels"); + else + { +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + if (height > png_ptr->user_height_max) +# else + if (height > PNG_USER_HEIGHT_MAX) +# endif + { + png_warning(png_ptr, "Image height exceeds user limit in IHDR"); + error = 1; + } + } /* Check other values */ if (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 && @@ -1458,7 +1486,7 @@ png_check_fp_string(png_const_charp string, png_size_t size) } #endif /* pCAL or sCAL */ -#ifdef PNG_READ_sCAL_SUPPORTED +#ifdef PNG_sCAL_SUPPORTED # ifdef PNG_FLOATING_POINT_SUPPORTED /* Utility used below - a simple accurate power of ten from an integral * exponent. @@ -1490,7 +1518,7 @@ png_pow10(int power) } while (power > 0); - if (recip) d = 1/d; + if (recip != 0) d = 1/d; } /* else power is 0 and d is 1 */ @@ -1722,7 +1750,7 @@ png_ascii_from_fp(png_structp png_ptr, png_charp ascii, png_size_t size, /* Check for an exponent, if we don't need one we are * done and just need to terminate the string. At * this point exp_b10==(-1) is effectively if flag - it got - * to '-1' because of the decrement after outputing + * to '-1' because of the decrement after outputting * the decimal point above (the exponent required is * *not* -1!) */ @@ -1730,7 +1758,7 @@ png_ascii_from_fp(png_structp png_ptr, png_charp ascii, png_size_t size, { /* The following only happens if we didn't output the * leading zeros above for negative exponent, so this - * doest add to the digit requirement. Note that the + * doesn't add to the digit requirement. Note that the * two zeros here can only be output if the two leading * zeros were *not* output, so this doesn't increase * the output count. @@ -1904,7 +1932,7 @@ png_fixed(png_structp png_ptr, double fp, png_const_charp text) #endif #if defined(PNG_READ_GAMMA_SUPPORTED) || \ - defined(PNG_INCH_CONVERSIONS_SUPPORTED) || defined(PNG__READ_pHYs_SUPPORTED) + defined(PNG_INCH_CONVERSIONS_SUPPORTED) || defined(PNG_READ_pHYs_SUPPORTED) /* muldiv functions */ /* This API takes signed arguments and rounds the result to the nearest * integer (or, for a fixed point number - the standard argument - to @@ -2008,7 +2036,7 @@ png_muldiv(png_fixed_point_p res, png_fixed_point a, png_int_32 times, if (s00 >= (D >> 1)) ++result; - if (negative) + if (negative != 0) result = -result; /* Check for overflow. */ @@ -2044,26 +2072,31 @@ png_muldiv_warn(png_structp png_ptr, png_fixed_point a, png_int_32 times, } #endif -#ifdef PNG_READ_GAMMA_SUPPORTED /* more fixed point functions for gamma */ +#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_cHRM_SUPPORTED) +/* more fixed point functions for gamma and cHRM (xy/XYZ) suport. */ /* Calculate a reciprocal, return 0 on div-by-zero or overflow. */ png_fixed_point png_reciprocal(png_fixed_point a) { + if (a != 0) + { #ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED - double r = floor(1E10/a+.5); + double r = floor(1E10/a+.5); - if (r <= 2147483647. && r >= -2147483648.) - return (png_fixed_point)r; + if (r <= 2147483647. && r >= -2147483648.) + return (png_fixed_point)r; #else - png_fixed_point res; + png_fixed_point res; - if (png_muldiv(&res, 100000, 100000, a)) - return res; + if (png_muldiv(&res, 100000, 100000, a)) + return res; #endif + } return 0; /* error/overflow */ } +#ifdef PNG_READ_GAMMA_SUPPORTED /* A local convenience routine. */ static png_fixed_point png_product2(png_fixed_point a, png_fixed_point b) @@ -2085,6 +2118,7 @@ png_product2(png_fixed_point a, png_fixed_point b) return 0; /* overflow */ } +#endif /* READ_GAMMA */ /* The inverse of the above. */ png_fixed_point @@ -2092,17 +2126,20 @@ png_reciprocal2(png_fixed_point a, png_fixed_point b) { /* The required result is 1/a * 1/b; the following preserves accuracy. */ #ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED - double r = 1E15/a; - r /= b; - r = floor(r+.5); + if (a != 0 && b != 0) + { + double r = 1E15/a; + r /= b; + r = floor(r+.5); - if (r <= 2147483647. && r >= -2147483648.) - return (png_fixed_point)r; + if (r <= 2147483647. && r >= -2147483648.) + return (png_fixed_point)r; + } #else - /* This may overflow because the range of png_fixed_point isn't symmetric, - * but this API is only used for the product of file and screen gamma so it - * doesn't matter that the smallest number it can produce is 1/21474, not - * 1/100000 + /* This may overflow because the range of png_fixed_point isn't + * symmetric, but this API is only used for the product of file and + * screen gamma so it doesn't matter that the smallest number it can + * produce is 1/21474, not 1/100000 */ png_fixed_point res = png_product2(a, b); @@ -2112,7 +2149,7 @@ png_reciprocal2(png_fixed_point a, png_fixed_point b) return 0; /* overflow */ } -#endif /* READ_GAMMA */ +#endif /* READ_GAMMA || cHRM */ #ifdef PNG_CHECK_cHRM_SUPPORTED /* Added at libpng version 1.2.34 (Dec 8, 2008) and 1.4.0 (Jan 2, @@ -2523,6 +2560,7 @@ png_gamma_significant(png_fixed_point gamma_val) gamma_val > PNG_FP_1 + PNG_GAMMA_THRESHOLD_FIXED; } +#ifdef PNG_16BIT_SUPPORTED /* Internal function to build a single 16-bit table - the table consists of * 'num' 256-entry subtables, where 'num' is determined by 'shift' - the amount * to shift the input values right (or 16-number_of_signifiant_bits). @@ -2571,7 +2609,7 @@ png_build_16bit_table(png_structp png_ptr, png_uint_16pp *ptable, double d = floor(65535*pow(ig/(double)max, gamma_val*.00001)+.5); sub_table[j] = (png_uint_16)d; # else - if (shift) + if (shift != 0) ig = (ig * 65535U + max_by_2)/max; sub_table[j] = png_gamma_16bit_correct(ig, gamma_val); @@ -2587,7 +2625,7 @@ png_build_16bit_table(png_structp png_ptr, png_uint_16pp *ptable, { png_uint_32 ig = (j << (8-shift)) + i; - if (shift) + if (shift != 0) ig = (ig * 65535U + max_by_2)/max; sub_table[j] = (png_uint_16)ig; @@ -2595,6 +2633,7 @@ png_build_16bit_table(png_structp png_ptr, png_uint_16pp *ptable, } } } +#endif /* NOTE: this function expects the *inverse* of the overall gamma transformation * required. @@ -2872,3 +2911,24 @@ png_build_gamma_table(png_structp png_ptr, int bit_depth) } #endif /* READ_GAMMA */ #endif /* defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) */ + +/* HARDWARE OPTION SUPPORT */ +#ifdef PNG_SET_OPTION_SUPPORTED +int PNGAPI +png_set_option(png_structp png_ptr, int option, int onoff) +{ + if (png_ptr != NULL && option >= 0 && option < PNG_OPTION_NEXT && + (option & 1) == 0) + { + int mask = 3 << option; + int setting = (2 + (onoff != 0)) << option; + int current = png_ptr->options; + + png_ptr->options = (png_byte)((current & ~mask) | setting); + + return (current & mask) >> option; + } + + return PNG_OPTION_INVALID; +} +#endif diff --git a/3rdparty/libpng/png.h b/3rdparty/libpng/png.h index 4c37e58a4c..f726c7282f 100644 --- a/3rdparty/libpng/png.h +++ b/3rdparty/libpng/png.h @@ -1,8 +1,9 @@ /* png.h - header file for PNG reference library * - * libpng version 1.5.12 - July 11, 2012 - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * libpng version 1.5.27, May 26, 2016 + * + * Copyright (c) 1998-2002,2004,2006-2016 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -11,16 +12,152 @@ * Authors and maintainers: * libpng versions 0.71, May 1995, through 0.88, January 1996: Guy Schalnat * libpng versions 0.89c, June 1996, through 0.96, May 1997: Andreas Dilger - * libpng versions 0.97, January 1998, through 1.5.12 - July 11, 2012: Glenn + * libpng versions 0.97, January 1998, through 1.5.27, May 26, 2016: + * Glenn Randers-Pehrson. * See also "Contributing Authors", below. + */ + +/* + * COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: * - * Note about libpng version numbers: + * If you modify libpng you may insert additional notices immediately following + * this sentence. * - * Due to various miscommunications, unforeseen code incompatibilities - * and occasional factors outside the authors' control, version numbering - * on the library has not always been consistent and straightforward. - * The following table summarizes matters since version 0.89c, which was - * the first widely used release: + * This code is released under the libpng license. + * + * libpng versions 1.0.7, July 1, 2000, through 1.5.27, May 26, 2016, are + * Copyright (c) 2000-2002, 2004, 2006-2016 Glenn Randers-Pehrson, are + * derived from libpng-1.0.6, and are distributed according to the same + * disclaimer and license as libpng-1.0.6 with the following individuals + * added to the list of Contributing Authors: + * + * Simon-Pierre Cadieux + * Eric S. Raymond + * Mans Rullgard + * Cosmin Truta + * Gilles Vollant + * + * and with the following additions to the disclaimer: + * + * There is no warranty against interference with your enjoyment of the + * library or against infringement. There is no warranty that our + * efforts or the library will fulfill any of your particular purposes + * or needs. This library is provided with all faults, and the entire + * risk of satisfactory quality, performance, accuracy, and effort is with + * the user. + * + * libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are + * Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from + * libpng-0.96, and are distributed according to the same disclaimer and + * license as libpng-0.96, with the following individuals added to the list + * of Contributing Authors: + * + * Tom Lane + * Glenn Randers-Pehrson + * Willem van Schaik + * + * libpng versions 0.89, June 1996, through 0.96, May 1997, are + * Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88, + * and are distributed according to the same disclaimer and license as + * libpng-0.88, with the following individuals added to the list of + * Contributing Authors: + * + * John Bowler + * Kevin Bracey + * Sam Bushell + * Magnus Holmgren + * Greg Roelofs + * Tom Tanner + * + * libpng versions 0.5, May 1995, through 0.88, January 1996, are + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * For the purposes of this copyright and license, "Contributing Authors" + * is defined as the following set of individuals: + * + * Andreas Dilger + * Dave Martindale + * Guy Eric Schalnat + * Paul Schmidt + * Tim Wegner + * + * The PNG Reference Library is supplied "AS IS". The Contributing Authors + * and Group 42, Inc. disclaim all warranties, expressed or implied, + * including, without limitation, the warranties of merchantability and of + * fitness for any purpose. The Contributing Authors and Group 42, Inc. + * assume no liability for direct, indirect, incidental, special, exemplary, + * or consequential damages, which may result from the use of the PNG + * Reference Library, even if advised of the possibility of such damage. + * + * Permission is hereby granted to use, copy, modify, and distribute this + * source code, or portions hereof, for any purpose, without fee, subject + * to the following restrictions: + * + * 1. The origin of this source code must not be misrepresented. + * + * 2. Altered versions must be plainly marked as such and must not + * be misrepresented as being the original source. + * + * 3. This Copyright notice may not be removed or altered from any + * source or altered source distribution. + * + * The Contributing Authors and Group 42, Inc. specifically permit, without + * fee, and encourage the use of this source code as a component to + * supporting the PNG file format in commercial products. If you use this + * source code in a product, acknowledgment is not required but would be + * appreciated. + * + * END OF COPYRIGHT NOTICE, DISCLAIMER, and LICENSE. + * + * TRADEMARK: + * + * The name "libpng" has not been registered by the Copyright owner + * as a trademark in any jurisdiction. However, because libpng has + * been distributed and maintained world-wide, continually since 1995, + * the Copyright owner claims "common-law trademark protection" in any + * jurisdiction where common-law trademark is recognized. + * + * OSI CERTIFICATION: + * + * Libpng is OSI Certified Open Source Software. OSI Certified Open Source is + * a certification mark of the Open Source Initiative. OSI has not addressed + * the additional disclaimers inserted at version 1.0.7. + * + * EXPORT CONTROL: + * + * The Copyright owner believes that the Export Control Classification + * Number (ECCN) for libpng is EAR99, which means not subject to export + * controls or International Traffic in Arms Regulations (ITAR) because + * it is open source, publicly available software, that does not contain + * any encryption software. See the EAR, paragraphs 734.3(b)(3) and + * 734.7(b). + */ + +/* + * A "png_get_copyright" function is available, for convenient use in "about" + * boxes and the like: + * + * printf("%s", png_get_copyright(NULL)); + * + * Also, the PNG logo (in PNG format, of course) is supplied in the + * files "pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31). + */ + +/* + * The contributing authors would like to thank all those who helped + * with testing, bug fixes, and patience. This wouldn't have been + * possible without all of you. + * + * Thanks to Frank J. T. Wojcik for helping with the documentation. + */ + +/* Note about libpng version numbers: + * + * Due to various miscommunications, unforeseen code incompatibilities + * and occasional factors outside the authors' control, version numbering + * on the library has not always been consistent and straightforward. + * The following table summarizes matters since version 0.89c, which was + * the first widely used release: * * source png.h png.h shared-lib * version string int version @@ -58,276 +195,46 @@ * 1.0.7beta15-18 1 10007 2.1.0.7beta15-18 (binary compatible) * 1.0.7rc1-2 1 10007 2.1.0.7rc1-2 (binary compatible) * 1.0.7 1 10007 (still compatible) - * 1.0.8beta1-4 1 10008 2.1.0.8beta1-4 - * 1.0.8rc1 1 10008 2.1.0.8rc1 - * 1.0.8 1 10008 2.1.0.8 - * 1.0.9beta1-6 1 10009 2.1.0.9beta1-6 - * 1.0.9rc1 1 10009 2.1.0.9rc1 - * 1.0.9beta7-10 1 10009 2.1.0.9beta7-10 - * 1.0.9rc2 1 10009 2.1.0.9rc2 - * 1.0.9 1 10009 2.1.0.9 - * 1.0.10beta1 1 10010 2.1.0.10beta1 - * 1.0.10rc1 1 10010 2.1.0.10rc1 - * 1.0.10 1 10010 2.1.0.10 - * 1.0.11beta1-3 1 10011 2.1.0.11beta1-3 - * 1.0.11rc1 1 10011 2.1.0.11rc1 - * 1.0.11 1 10011 2.1.0.11 - * 1.0.12beta1-2 2 10012 2.1.0.12beta1-2 - * 1.0.12rc1 2 10012 2.1.0.12rc1 - * 1.0.12 2 10012 2.1.0.12 - * 1.1.0a-f - 10100 2.1.1.0a-f (branch abandoned) - * 1.2.0beta1-2 2 10200 2.1.2.0beta1-2 - * 1.2.0beta3-5 3 10200 3.1.2.0beta3-5 - * 1.2.0rc1 3 10200 3.1.2.0rc1 - * 1.2.0 3 10200 3.1.2.0 - * 1.2.1beta1-4 3 10201 3.1.2.1beta1-4 - * 1.2.1rc1-2 3 10201 3.1.2.1rc1-2 - * 1.2.1 3 10201 3.1.2.1 - * 1.2.2beta1-6 12 10202 12.so.0.1.2.2beta1-6 - * 1.0.13beta1 10 10013 10.so.0.1.0.13beta1 - * 1.0.13rc1 10 10013 10.so.0.1.0.13rc1 - * 1.2.2rc1 12 10202 12.so.0.1.2.2rc1 - * 1.0.13 10 10013 10.so.0.1.0.13 - * 1.2.2 12 10202 12.so.0.1.2.2 - * 1.2.3rc1-6 12 10203 12.so.0.1.2.3rc1-6 - * 1.2.3 12 10203 12.so.0.1.2.3 - * 1.2.4beta1-3 13 10204 12.so.0.1.2.4beta1-3 - * 1.0.14rc1 13 10014 10.so.0.1.0.14rc1 - * 1.2.4rc1 13 10204 12.so.0.1.2.4rc1 - * 1.0.14 10 10014 10.so.0.1.0.14 - * 1.2.4 13 10204 12.so.0.1.2.4 - * 1.2.5beta1-2 13 10205 12.so.0.1.2.5beta1-2 - * 1.0.15rc1-3 10 10015 10.so.0.1.0.15rc1-3 - * 1.2.5rc1-3 13 10205 12.so.0.1.2.5rc1-3 - * 1.0.15 10 10015 10.so.0.1.0.15 - * 1.2.5 13 10205 12.so.0.1.2.5 - * 1.2.6beta1-4 13 10206 12.so.0.1.2.6beta1-4 - * 1.0.16 10 10016 10.so.0.1.0.16 - * 1.2.6 13 10206 12.so.0.1.2.6 - * 1.2.7beta1-2 13 10207 12.so.0.1.2.7beta1-2 - * 1.0.17rc1 10 10017 12.so.0.1.0.17rc1 - * 1.2.7rc1 13 10207 12.so.0.1.2.7rc1 - * 1.0.17 10 10017 12.so.0.1.0.17 - * 1.2.7 13 10207 12.so.0.1.2.7 - * 1.2.8beta1-5 13 10208 12.so.0.1.2.8beta1-5 - * 1.0.18rc1-5 10 10018 12.so.0.1.0.18rc1-5 - * 1.2.8rc1-5 13 10208 12.so.0.1.2.8rc1-5 - * 1.0.18 10 10018 12.so.0.1.0.18 - * 1.2.8 13 10208 12.so.0.1.2.8 - * 1.2.9beta1-3 13 10209 12.so.0.1.2.9beta1-3 - * 1.2.9beta4-11 13 10209 12.so.0.9[.0] - * 1.2.9rc1 13 10209 12.so.0.9[.0] - * 1.2.9 13 10209 12.so.0.9[.0] - * 1.2.10beta1-7 13 10210 12.so.0.10[.0] - * 1.2.10rc1-2 13 10210 12.so.0.10[.0] - * 1.2.10 13 10210 12.so.0.10[.0] - * 1.4.0beta1-5 14 10400 14.so.0.0[.0] - * 1.2.11beta1-4 13 10211 12.so.0.11[.0] - * 1.4.0beta7-8 14 10400 14.so.0.0[.0] - * 1.2.11 13 10211 12.so.0.11[.0] - * 1.2.12 13 10212 12.so.0.12[.0] - * 1.4.0beta9-14 14 10400 14.so.0.0[.0] - * 1.2.13 13 10213 12.so.0.13[.0] - * 1.4.0beta15-36 14 10400 14.so.0.0[.0] - * 1.4.0beta37-87 14 10400 14.so.14.0[.0] - * 1.4.0rc01 14 10400 14.so.14.0[.0] - * 1.4.0beta88-109 14 10400 14.so.14.0[.0] - * 1.4.0rc02-08 14 10400 14.so.14.0[.0] - * 1.4.0 14 10400 14.so.14.0[.0] - * 1.4.1beta01-03 14 10401 14.so.14.1[.0] - * 1.4.1rc01 14 10401 14.so.14.1[.0] - * 1.4.1beta04-12 14 10401 14.so.14.1[.0] - * 1.4.1 14 10401 14.so.14.1[.0] - * 1.4.2 14 10402 14.so.14.2[.0] - * 1.4.3 14 10403 14.so.14.3[.0] - * 1.4.4 14 10404 14.so.14.4[.0] - * 1.5.0beta01-58 15 10500 15.so.15.0[.0] - * 1.5.0rc01-07 15 10500 15.so.15.0[.0] - * 1.5.0 15 10500 15.so.15.0[.0] - * 1.5.1beta01-11 15 10501 15.so.15.1[.0] - * 1.5.1rc01-02 15 10501 15.so.15.1[.0] - * 1.5.1 15 10501 15.so.15.1[.0] - * 1.5.2beta01-03 15 10502 15.so.15.2[.0] - * 1.5.2rc01-03 15 10502 15.so.15.2[.0] - * 1.5.2 15 10502 15.so.15.2[.0] - * 1.5.3beta01-10 15 10503 15.so.15.3[.0] - * 1.5.3rc01-02 15 10503 15.so.15.3[.0] - * 1.5.3beta11 15 10503 15.so.15.3[.0] - * 1.5.3 [omitted] - * 1.5.4beta01-08 15 10504 15.so.15.4[.0] - * 1.5.4rc01 15 10504 15.so.15.4[.0] - * 1.5.4 15 10504 15.so.15.4[.0] - * 1.5.5beta01-08 15 10505 15.so.15.5[.0] - * 1.5.5rc01 15 10505 15.so.15.5[.0] - * 1.5.5 15 10505 15.so.15.5[.0] - * 1.5.6beta01-07 15 10506 15.so.15.6[.0] - * 1.5.6rc01-03 15 10506 15.so.15.6[.0] - * 1.5.6 15 10506 15.so.15.6[.0] - * 1.5.7beta01-05 15 10507 15.so.15.7[.0] - * 1.5.7rc01-03 15 10507 15.so.15.7[.0] - * 1.5.7 15 10507 15.so.15.7[.0] - * 1.5.8beta01 15 10508 15.so.15.8[.0] - * 1.5.8rc01 15 10508 15.so.15.8[.0] - * 1.5.8 15 10508 15.so.15.8[.0] - * 1.5.9beta01-02 15 10509 15.so.15.9[.0] - * 1.5.9rc01 15 10509 15.so.15.9[.0] - * 1.5.9 15 10509 15.so.15.9[.0] - * 1.5.10beta01-05 15 10510 15.so.15.10[.0] - * 1.5.10 15 10510 15.so.15.10[.0] - * 1.5.11beta01 15 10511 15.so.15.11[.0] - * 1.5.11rc01-05 15 10511 15.so.15.11[.0] - * 1.5.11 15 10511 15.so.15.11[.0] - * 1.5.12 15 10512 15.so.15.12[.0] + * ... + * 1.0.19 10 10019 10.so.0.19[.0] + * ... + * 1.2.56 13 10256 12.so.0.56[.0] + * ... + * 1.5.27 15 10527 15.so.15.27[.0] * - * Henceforth the source version will match the shared-library major - * and minor numbers; the shared-library major version number will be - * used for changes in backward compatibility, as it is intended. The - * PNG_LIBPNG_VER macro, which is not used within libpng but is available - * for applications, is an unsigned integer of the form xyyzz corresponding - * to the source version x.y.z (leading zeros in y and z). Beta versions - * were given the previous public release number plus a letter, until - * version 1.0.6j; from then on they were given the upcoming public - * release number plus "betaNN" or "rcNN". + * Henceforth the source version will match the shared-library major + * and minor numbers; the shared-library major version number will be + * used for changes in backward compatibility, as it is intended. The + * PNG_LIBPNG_VER macro, which is not used within libpng but is available + * for applications, is an unsigned integer of the form xyyzz corresponding + * to the source version x.y.z (leading zeros in y and z). Beta versions + * were given the previous public release number plus a letter, until + * version 1.0.6j; from then on they were given the upcoming public + * release number plus "betaNN" or "rcNN". * - * Binary incompatibility exists only when applications make direct access - * to the info_ptr or png_ptr members through png.h, and the compiled - * application is loaded with a different version of the library. + * Binary incompatibility exists only when applications make direct access + * to the info_ptr or png_ptr members through png.h, and the compiled + * application is loaded with a different version of the library. * - * DLLNUM will change each time there are forward or backward changes - * in binary compatibility (e.g., when a new feature is added). + * DLLNUM will change each time there are forward or backward changes + * in binary compatibility (e.g., when a new feature is added). * - * See libpng-manual.txt or libpng.3 for more information. The PNG - * specification is available as a W3C Recommendation and as an ISO - * Specification, * * If you just need to read a PNG file and don't want to read the documentation * skip to the end of this file and read the section entitled 'simplified API'. */ /* Version information for png.h - this should match the version in png.c */ -#define PNG_LIBPNG_VER_STRING "1.5.12" +#define PNG_LIBPNG_VER_STRING "1.5.27" #define PNG_HEADER_VERSION_STRING \ - " libpng version 1.5.12 - July 11, 2012\n" + " libpng version 1.5.27 - May 26, 2016\n" #define PNG_LIBPNG_VER_SONUM 15 #define PNG_LIBPNG_VER_DLLNUM 15 @@ -396,7 +305,7 @@ /* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */ #define PNG_LIBPNG_VER_MAJOR 1 #define PNG_LIBPNG_VER_MINOR 5 -#define PNG_LIBPNG_VER_RELEASE 12 +#define PNG_LIBPNG_VER_RELEASE 27 /* This should match the numeric part of the final component of * PNG_LIBPNG_VER_STRING, omitting any leading zero: @@ -427,7 +336,7 @@ * version 1.0.0 was mis-numbered 100 instead of 10000). From * version 1.0.1 it's xxyyzz, where x=major, y=minor, z=release */ -#define PNG_LIBPNG_VER 10512 /* 1.5.12 */ +#define PNG_LIBPNG_VER 10527 /* 1.5.27 */ /* Library configuration: these options cannot be changed after * the library has been built. @@ -460,7 +369,7 @@ /* Machine specific configuration. */ # include "pngconf.h" -#endif +#endif /* PNG_VERSION_INFO_ONLY */ /* * Added at libpng-1.2.8 @@ -549,7 +458,7 @@ extern "C" { /* This triggers a compiler error in png.c, if png.c and png.h * do not agree upon the version number. */ -typedef char* png_libpng_version_1_5_12; +typedef char* png_libpng_version_1_5_27; /* Three color definitions. The order of the red, green, and blue, (and the * exact size) is not important, although the size of the fields need to @@ -813,22 +722,22 @@ typedef png_info FAR * FAR * png_infopp; * data in the info_struct to be written into the output file. The values * of the PNG_INFO_ defines should NOT be changed. */ -#define PNG_INFO_gAMA 0x0001 -#define PNG_INFO_sBIT 0x0002 -#define PNG_INFO_cHRM 0x0004 -#define PNG_INFO_PLTE 0x0008 -#define PNG_INFO_tRNS 0x0010 -#define PNG_INFO_bKGD 0x0020 -#define PNG_INFO_hIST 0x0040 -#define PNG_INFO_pHYs 0x0080 -#define PNG_INFO_oFFs 0x0100 -#define PNG_INFO_tIME 0x0200 -#define PNG_INFO_pCAL 0x0400 -#define PNG_INFO_sRGB 0x0800 /* GR-P, 0.96a */ -#define PNG_INFO_iCCP 0x1000 /* ESR, 1.0.6 */ -#define PNG_INFO_sPLT 0x2000 /* ESR, 1.0.6 */ -#define PNG_INFO_sCAL 0x4000 /* ESR, 1.0.6 */ -#define PNG_INFO_IDAT 0x8000 /* ESR, 1.0.6 */ +#define PNG_INFO_gAMA 0x0001U +#define PNG_INFO_sBIT 0x0002U +#define PNG_INFO_cHRM 0x0004U +#define PNG_INFO_PLTE 0x0008U +#define PNG_INFO_tRNS 0x0010U +#define PNG_INFO_bKGD 0x0020U +#define PNG_INFO_hIST 0x0040U +#define PNG_INFO_pHYs 0x0080U +#define PNG_INFO_oFFs 0x0100U +#define PNG_INFO_tIME 0x0200U +#define PNG_INFO_pCAL 0x0400U +#define PNG_INFO_sRGB 0x0800U /* GR-P, 0.96a */ +#define PNG_INFO_iCCP 0x1000U /* ESR, 1.0.6 */ +#define PNG_INFO_sPLT 0x2000U /* ESR, 1.0.6 */ +#define PNG_INFO_sCAL 0x4000U /* ESR, 1.0.6 */ +#define PNG_INFO_IDAT 0x8000U /* ESR, 1.0.6 */ /* This is used for the transformation routines, as some of them * change these values for the row. It also should enable using @@ -1020,11 +929,13 @@ PNG_EXPORTA(5, png_structp, png_create_write_struct, png_error_ptr warn_fn), PNG_ALLOCATED); +#ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED PNG_EXPORT(6, png_size_t, png_get_compression_buffer_size, (png_const_structp png_ptr)); PNG_EXPORT(7, void, png_set_compression_buffer_size, (png_structp png_ptr, png_size_t size)); +#endif /* WRITE_CUSTOMIZE_COMPRESSION */ /* Moved from pngconf.h in 1.4.0 and modified to ensure setjmp/longjmp * match up. @@ -1158,9 +1069,9 @@ PNG_EXPORT(31, void, png_set_gray_to_rgb, (png_structp png_ptr)); #define PNG_RGB_TO_GRAY_DEFAULT (-1)/*for red/green coefficients*/ PNG_FP_EXPORT(32, void, png_set_rgb_to_gray, (png_structp png_ptr, - int error_action, double red, double green)); + int error_action, double red, double green)) PNG_FIXED_EXPORT(33, void, png_set_rgb_to_gray_fixed, (png_structp png_ptr, - int error_action, png_fixed_point red, png_fixed_point green)); + int error_action, png_fixed_point red, png_fixed_point green)) PNG_EXPORT(34, png_byte, png_get_rgb_to_gray_status, (png_const_structp png_ptr)); @@ -1228,9 +1139,9 @@ PNG_EXPORT(35, void, png_build_grayscale_palette, (int bit_depth, #define PNG_ALPHA_BROKEN 3 /* the alpha channel is gamma encoded */ PNG_FP_EXPORT(227, void, png_set_alpha_mode, (png_structp png_ptr, int mode, - double output_gamma)); + double output_gamma)) PNG_FIXED_EXPORT(228, void, png_set_alpha_mode_fixed, (png_structp png_ptr, - int mode, png_fixed_point output_gamma)); + int mode, png_fixed_point output_gamma)) #endif #if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_READ_ALPHA_MODE_SUPPORTED) @@ -1453,10 +1364,10 @@ PNG_EXPORT(46, void, png_set_invert_mono, (png_structp png_ptr)); */ PNG_FP_EXPORT(47, void, png_set_background, (png_structp png_ptr, png_const_color_16p background_color, int background_gamma_code, - int need_expand, double background_gamma)); + int need_expand, double background_gamma)) PNG_FIXED_EXPORT(215, void, png_set_background_fixed, (png_structp png_ptr, png_const_color_16p background_color, int background_gamma_code, - int need_expand, png_fixed_point background_gamma)); + int need_expand, png_fixed_point background_gamma)) #endif #ifdef PNG_READ_BACKGROUND_SUPPORTED # define PNG_BACKGROUND_GAMMA_UNKNOWN 0 @@ -1471,7 +1382,7 @@ PNG_EXPORT(229, void, png_set_scale_16, (png_structp png_ptr)); #endif #ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED -#define PNG_READ_16_TO_8 SUPPORTED /* Name prior to 1.5.4 */ +#define PNG_READ_16_TO_8_SUPPORTED /* Name prior to 1.5.4 */ /* Strip the second byte of information from a 16-bit depth file. */ PNG_EXPORT(48, void, png_set_strip_16, (png_structp png_ptr)); #endif @@ -1505,9 +1416,9 @@ PNG_EXPORT(49, void, png_set_quantize, */ PNG_FP_EXPORT(50, void, png_set_gamma, (png_structp png_ptr, double screen_gamma, - double override_file_gamma)); + double override_file_gamma)) PNG_FIXED_EXPORT(208, void, png_set_gamma_fixed, (png_structp png_ptr, - png_fixed_point screen_gamma, png_fixed_point override_file_gamma)); + png_fixed_point screen_gamma, png_fixed_point override_file_gamma)) #endif #ifdef PNG_WRITE_FLUSH_SUPPORTED @@ -1623,8 +1534,8 @@ PNG_EXPORT(67, void, png_set_filter, #define PNG_FILTER_UP 0x20 #define PNG_FILTER_AVG 0x40 #define PNG_FILTER_PAETH 0x80 -#define PNG_ALL_FILTERS (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | \ - PNG_FILTER_AVG | PNG_FILTER_PAETH) +#define PNG_FAST_FILTERS (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP) +#define PNG_ALL_FILTERS (PNG_FAST_FILTERS | PNG_FILTER_AVG | PNG_FILTER_PAETH) /* Filter values (not flags) - used in pngwrite.c, pngwutil.c for now. * These defines should NOT be changed. @@ -1636,47 +1547,17 @@ PNG_EXPORT(67, void, png_set_filter, #define PNG_FILTER_VALUE_PAETH 4 #define PNG_FILTER_VALUE_LAST 5 -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* EXPERIMENTAL */ -/* The "heuristic_method" is given by one of the PNG_FILTER_HEURISTIC_ - * defines, either the default (minimum-sum-of-absolute-differences), or - * the experimental method (weighted-minimum-sum-of-absolute-differences). - * - * Weights are factors >= 1.0, indicating how important it is to keep the - * filter type consistent between rows. Larger numbers mean the current - * filter is that many times as likely to be the same as the "num_weights" - * previous filters. This is cumulative for each previous row with a weight. - * There needs to be "num_weights" values in "filter_weights", or it can be - * NULL if the weights aren't being specified. Weights have no influence on - * the selection of the first row filter. Well chosen weights can (in theory) - * improve the compression for a given image. - * - * Costs are factors >= 1.0 indicating the relative decoding costs of a - * filter type. Higher costs indicate more decoding expense, and are - * therefore less likely to be selected over a filter with lower computational - * costs. There needs to be a value in "filter_costs" for each valid filter - * type (given by PNG_FILTER_VALUE_LAST), or it can be NULL if you aren't - * setting the costs. Costs try to improve the speed of decompression without - * unduly increasing the compressed image size. - * - * A negative weight or cost indicates the default value is to be used, and - * values in the range [0.0, 1.0) indicate the value is to remain unchanged. - * The default values for both weights and costs are currently 1.0, but may - * change if good general weighting/cost heuristics can be found. If both - * the weights and costs are set to 1.0, this degenerates the WEIGHTED method - * to the UNWEIGHTED method, but with added encoding time/computation. - */ +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* DEPRECATED */ PNG_FP_EXPORT(68, void, png_set_filter_heuristics, (png_structp png_ptr, int heuristic_method, int num_weights, png_const_doublep filter_weights, - png_const_doublep filter_costs)); + png_const_doublep filter_costs)) PNG_FIXED_EXPORT(209, void, png_set_filter_heuristics_fixed, (png_structp png_ptr, int heuristic_method, int num_weights, png_const_fixed_point_p - filter_weights, png_const_fixed_point_p filter_costs)); + filter_weights, png_const_fixed_point_p filter_costs)) #endif /* PNG_WRITE_WEIGHTED_FILTER_SUPPORTED */ -/* Heuristic used for row filter selection. These defines should NOT be - * changed. - */ +/* The following are no longer used and will be removed from libpng-1.7: */ #define PNG_FILTER_HEURISTIC_DEFAULT 0 /* Currently "UNWEIGHTED" */ #define PNG_FILTER_HEURISTIC_UNWEIGHTED 1 /* Used by libpng < 0.95 */ #define PNG_FILTER_HEURISTIC_WEIGHTED 2 /* Experimental feature */ @@ -1690,6 +1571,7 @@ PNG_FIXED_EXPORT(209, void, png_set_filter_heuristics_fixed, * for PNG images, and do considerably fewer caclulations. In the future, * these values may not correspond directly to the zlib compression levels. */ +#ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED PNG_EXPORT(69, void, png_set_compression_level, (png_structp png_ptr, int level)); @@ -1707,7 +1589,7 @@ PNG_EXPORT(72, void, png_set_compression_window_bits, (png_structp png_ptr, PNG_EXPORT(73, void, png_set_compression_method, (png_structp png_ptr, int method)); -#endif +#endif /* WRITE_CUSTOMIZE_COMPRESSION */ #ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED /* Also set zlib parameters for compressing non-IDAT chunks */ @@ -1729,6 +1611,7 @@ PNG_EXPORT(225, void, png_set_text_compression_window_bits, (png_structp PNG_EXPORT(226, void, png_set_text_compression_method, (png_structp png_ptr, int method)); #endif /* PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED */ +#endif /* WRITE */ /* These next functions are called for input/output, memory, and error * handling. They are in the file pngrio.c, pngwio.c, and pngerror.c, @@ -1940,6 +1823,8 @@ PNG_EXPORTA(103, void, png_chunk_error, (png_structp png_ptr, #else /* Fatal error in PNG image of libpng - can't continue */ PNG_EXPORTA(104, void, png_err, (png_structp png_ptr), PNG_NORETURN); +# define png_error(s1,s2) png_err(s1) +# define png_chunk_error(s1,s2) png_err(s1) #endif #ifdef PNG_WARNINGS_SUPPORTED @@ -1950,6 +1835,9 @@ PNG_EXPORT(105, void, png_warning, (png_structp png_ptr, /* Non-fatal error in libpng, chunk name is prepended to message. */ PNG_EXPORT(106, void, png_chunk_warning, (png_structp png_ptr, png_const_charp warning_message)); +#else +# define png_warning(s1,s2) ((void)(s1)) +# define png_chunk_warning(s1,s2) ((void)(s1)) #endif #ifdef PNG_BENIGN_ERRORS_SUPPORTED @@ -2053,9 +1941,9 @@ PNG_EXPORT(124, png_uint_32, png_get_y_pixels_per_meter, /* Returns pixel aspect ratio, computed from pHYs chunk data. */ PNG_FP_EXPORT(125, float, png_get_pixel_aspect_ratio, - (png_const_structp png_ptr, png_const_infop info_ptr)); + (png_const_structp png_ptr, png_const_infop info_ptr)) PNG_FIXED_EXPORT(210, png_fixed_point, png_get_pixel_aspect_ratio_fixed, - (png_const_structp png_ptr, png_const_infop info_ptr)); + (png_const_structp png_ptr, png_const_infop info_ptr)) /* Returns image x, y offset in pixels or microns, from oFFs chunk data. */ PNG_EXPORT(126, png_int_32, png_get_x_offset_pixels, @@ -2088,11 +1976,11 @@ PNG_EXPORT(132, void, png_set_bKGD, (png_structp png_ptr, png_infop info_ptr, PNG_FP_EXPORT(133, png_uint_32, png_get_cHRM, (png_const_structp png_ptr, png_const_infop info_ptr, double *white_x, double *white_y, double *red_x, double *red_y, double *green_x, double *green_y, double *blue_x, - double *blue_y)); + double *blue_y)) PNG_FP_EXPORT(230, png_uint_32, png_get_cHRM_XYZ, (png_structp png_ptr, png_const_infop info_ptr, double *red_X, double *red_Y, double *red_Z, double *green_X, double *green_Y, double *green_Z, double *blue_X, - double *blue_Y, double *blue_Z)); + double *blue_Y, double *blue_Z)) #ifdef PNG_FIXED_POINT_SUPPORTED /* Otherwise not implemented */ PNG_FIXED_EXPORT(134, png_uint_32, png_get_cHRM_fixed, (png_const_structp png_ptr, @@ -2100,7 +1988,7 @@ PNG_FIXED_EXPORT(134, png_uint_32, png_get_cHRM_fixed, png_fixed_point *int_white_y, png_fixed_point *int_red_x, png_fixed_point *int_red_y, png_fixed_point *int_green_x, png_fixed_point *int_green_y, png_fixed_point *int_blue_x, - png_fixed_point *int_blue_y)); + png_fixed_point *int_blue_y)) #endif PNG_FIXED_EXPORT(231, png_uint_32, png_get_cHRM_XYZ_fixed, (png_structp png_ptr, png_const_infop info_ptr, @@ -2108,46 +1996,46 @@ PNG_FIXED_EXPORT(231, png_uint_32, png_get_cHRM_XYZ_fixed, png_fixed_point *int_red_Z, png_fixed_point *int_green_X, png_fixed_point *int_green_Y, png_fixed_point *int_green_Z, png_fixed_point *int_blue_X, png_fixed_point *int_blue_Y, - png_fixed_point *int_blue_Z)); + png_fixed_point *int_blue_Z)) #endif #ifdef PNG_cHRM_SUPPORTED PNG_FP_EXPORT(135, void, png_set_cHRM, (png_structp png_ptr, png_infop info_ptr, double white_x, double white_y, double red_x, double red_y, double green_x, - double green_y, double blue_x, double blue_y)); + double green_y, double blue_x, double blue_y)) PNG_FP_EXPORT(232, void, png_set_cHRM_XYZ, (png_structp png_ptr, png_infop info_ptr, double red_X, double red_Y, double red_Z, double green_X, double green_Y, double green_Z, double blue_X, - double blue_Y, double blue_Z)); + double blue_Y, double blue_Z)) PNG_FIXED_EXPORT(136, void, png_set_cHRM_fixed, (png_structp png_ptr, png_infop info_ptr, png_fixed_point int_white_x, png_fixed_point int_white_y, png_fixed_point int_red_x, png_fixed_point int_red_y, png_fixed_point int_green_x, png_fixed_point int_green_y, png_fixed_point int_blue_x, - png_fixed_point int_blue_y)); + png_fixed_point int_blue_y)) PNG_FIXED_EXPORT(233, void, png_set_cHRM_XYZ_fixed, (png_structp png_ptr, png_infop info_ptr, png_fixed_point int_red_X, png_fixed_point int_red_Y, png_fixed_point int_red_Z, png_fixed_point int_green_X, png_fixed_point int_green_Y, png_fixed_point int_green_Z, png_fixed_point int_blue_X, png_fixed_point int_blue_Y, - png_fixed_point int_blue_Z)); + png_fixed_point int_blue_Z)) #endif #ifdef PNG_gAMA_SUPPORTED PNG_FP_EXPORT(137, png_uint_32, png_get_gAMA, (png_const_structp png_ptr, png_const_infop info_ptr, - double *file_gamma)); + double *file_gamma)) PNG_FIXED_EXPORT(138, png_uint_32, png_get_gAMA_fixed, (png_const_structp png_ptr, png_const_infop info_ptr, - png_fixed_point *int_file_gamma)); + png_fixed_point *int_file_gamma)) #endif #ifdef PNG_gAMA_SUPPORTED PNG_FP_EXPORT(139, void, png_set_gAMA, (png_structp png_ptr, - png_infop info_ptr, double file_gamma)); + png_infop info_ptr, double file_gamma)) PNG_FIXED_EXPORT(140, void, png_set_gAMA_fixed, (png_structp png_ptr, - png_infop info_ptr, png_fixed_point int_file_gamma)); + png_infop info_ptr, png_fixed_point int_file_gamma)) #endif #ifdef PNG_hIST_SUPPORTED @@ -2313,7 +2201,7 @@ PNG_EXPORT(167, void, png_set_tRNS, #ifdef PNG_sCAL_SUPPORTED PNG_FP_EXPORT(168, png_uint_32, png_get_sCAL, (png_const_structp png_ptr, png_const_infop info_ptr, - int *unit, double *width, double *height)); + int *unit, double *width, double *height)) #ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED /* NOTE: this API is currently implemented using floating point arithmetic, * consequently it can only be used on systems with floating point support. @@ -2323,7 +2211,7 @@ PNG_FP_EXPORT(168, png_uint_32, png_get_sCAL, PNG_FIXED_EXPORT(214, png_uint_32, png_get_sCAL_fixed, (png_structp png_ptr, png_const_infop info_ptr, int *unit, png_fixed_point *width, - png_fixed_point *height)); + png_fixed_point *height)) #endif PNG_EXPORT(169, png_uint_32, png_get_sCAL_s, (png_const_structp png_ptr, png_const_infop info_ptr, @@ -2331,10 +2219,10 @@ PNG_EXPORT(169, png_uint_32, png_get_sCAL_s, PNG_FP_EXPORT(170, void, png_set_sCAL, (png_structp png_ptr, png_infop info_ptr, - int unit, double width, double height)); + int unit, double width, double height)) PNG_FIXED_EXPORT(213, void, png_set_sCAL_fixed, (png_structp png_ptr, png_infop info_ptr, int unit, png_fixed_point width, - png_fixed_point height)); + png_fixed_point height)) PNG_EXPORT(171, void, png_set_sCAL_s, (png_structp png_ptr, png_infop info_ptr, int unit, png_const_charp swidth, png_const_charp sheight)); @@ -2447,17 +2335,17 @@ PNG_EXPORT(195, png_uint_32, png_get_y_pixels_per_inch, (png_const_structp png_ptr, png_const_infop info_ptr)); PNG_FP_EXPORT(196, float, png_get_x_offset_inches, - (png_const_structp png_ptr, png_const_infop info_ptr)); + (png_const_structp png_ptr, png_const_infop info_ptr)) #ifdef PNG_FIXED_POINT_SUPPORTED /* otherwise not implemented. */ PNG_FIXED_EXPORT(211, png_fixed_point, png_get_x_offset_inches_fixed, - (png_structp png_ptr, png_const_infop info_ptr)); + (png_structp png_ptr, png_const_infop info_ptr)) #endif PNG_FP_EXPORT(197, float, png_get_y_offset_inches, (png_const_structp png_ptr, - png_const_infop info_ptr)); + png_const_infop info_ptr)) #ifdef PNG_FIXED_POINT_SUPPORTED /* otherwise not implemented. */ PNG_FIXED_EXPORT(212, png_fixed_point, png_get_y_offset_inches_fixed, - (png_structp png_ptr, png_const_infop info_ptr)); + (png_structp png_ptr, png_const_infop info_ptr)) #endif # ifdef PNG_pHYs_SUPPORTED @@ -2640,15 +2528,59 @@ PNG_EXPORT(207, void, png_save_uint_16, (png_bytep buf, unsigned int i)); # define png_get_int_32(buf) \ ((png_int_32)((*(buf) & 0x80) \ - ? -((png_int_32)((png_get_uint_32(buf) ^ 0xffffffffL) + 1)) \ + ? -((png_int_32)(((png_get_uint_32(buf)^0xffffffffU)+1U)&0x7fffffffU)) \ : (png_int_32)png_get_uint_32(buf))) #endif -#if defined(PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED) || \ - defined(PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED) +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED PNG_EXPORT(234, void, png_set_check_for_invalid_index, (png_structp png_ptr, int allowed)); +# ifdef PNG_GET_PALETTE_MAX_SUPPORTED +PNG_EXPORT(235, int, png_get_palette_max, (png_const_structp png_ptr, + png_const_infop info_ptr)); +# endif +#endif /* CHECK_FOR_INVALID_INDEX */ + +/******************************************************************************* + * IMPLEMENTATION OPTIONS + ******************************************************************************* + * + * Support for arbitrary implementation-specific optimizations. The API allows + * particular options to be turned on or off. 'Option' is the number of the + * option and 'onoff' is 0 (off) or non-0 (on). The value returned is given + * by the PNG_OPTION_ defines below. + * + * HARDWARE: normally hardware capabilites, such as the Intel SSE instructions, + * are detected at run time, however sometimes it may be impossible + * to do this in user mode, in which case it is necessary to discover + * the capabilities in an OS specific way. Such capabilities are + * listed here when libpng has support for them and must be turned + * ON by the application if present. + * + * SOFTWARE: sometimes software optimizations actually result in performance + * decrease on some architectures or systems, or with some sets of + * PNG images. 'Software' options allow such optimizations to be + * selected at run time. + */ +#ifdef PNG_SET_OPTION_SUPPORTED +#ifdef PNG_ARM_NEON_API_SUPPORTED +# define PNG_ARM_NEON 0 /* HARDWARE: ARM Neon SIMD instructions supported */ #endif +#define PNG_OPTION_NEXT 2 /* Next option - numbers must be even */ + +/* Return values: NOTE: there are four values and 'off' is *not* zero */ +#define PNG_OPTION_UNSET 0 /* Unset - defaults to off */ +#define PNG_OPTION_INVALID 1 /* Option number out of range */ +#define PNG_OPTION_OFF 2 +#define PNG_OPTION_ON 3 + +PNG_EXPORT(236, int, png_set_option, (png_structp png_ptr, int option, + int onoff)); +#endif + +/******************************************************************************* + * END OF HARDWARE OPTIONS + ******************************************************************************/ /* Maintainer: Put new public prototypes here ^, in libpng.3, and project * defs @@ -2659,7 +2591,7 @@ PNG_EXPORT(234, void, png_set_check_for_invalid_index, (png_structp png_ptr, * scripts/symbols.def as well. */ #ifdef PNG_EXPORT_LAST_ORDINAL - PNG_EXPORT_LAST_ORDINAL(234); + PNG_EXPORT_LAST_ORDINAL(236); #endif #ifdef __cplusplus diff --git a/3rdparty/libpng/pngconf.h b/3rdparty/libpng/pngconf.h index 5c3eb1454d..ce1ff5853f 100644 --- a/3rdparty/libpng/pngconf.h +++ b/3rdparty/libpng/pngconf.h @@ -1,9 +1,9 @@ /* pngconf.h - machine configurable file for libpng * - * libpng version 1.5.12 - July 11, 2012 + * libpng version 1.5.27, May 26, 2016 * - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Copyright (c) 1998-2002,2004,2006-2013 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -11,9 +11,7 @@ * For conditions of distribution and use, see the disclaimer * and license in png.h * - */ - -/* Any machine specific code is near the front of this file, so if you + * Any machine specific code is near the front of this file, so if you * are configuring libpng for a machine, you may want to read the section * starting here down to where it starts to typedef png_color, png_text, * and png_info. @@ -177,18 +175,16 @@ * ========================== * This code is used at build time to find PNG_IMPEXP, the API settings * and PNG_EXPORT_TYPE(), it may also set a macro to indicate the DLL - * import processing is possible. On Windows/x86 systems it also sets + * import processing is possible. On Windows systems it also sets * compiler-specific macros to the values required to change the calling * conventions of the various functions. */ -#if ( defined(_Windows) || defined(_WINDOWS) || defined(WIN32) ||\ - defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) ) &&\ - ( defined(_X86_) || defined(_X64_) || defined(_M_IX86) ||\ - defined(_M_X64) || defined(_M_IA64) ) - /* Windows system (DOS doesn't support DLLs) running on x86/x64. Includes - * builds under Cygwin or MinGW. Also includes Watcom builds but these need - * special treatment because they are not compatible with GCC or Visual C - * because of different calling conventions. +#if defined(_Windows) || defined(_WINDOWS) || defined(WIN32) ||\ + defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) + /* Windows system (DOS doesn't support DLLs). Includes builds under Cygwin or + * MinGW on any architecture currently supported by Windows. Also includes + * Watcom builds but these need special treatment because they are not + * compatible with GCC or Visual C because of different calling conventions. */ # if PNG_API_RULE == 2 /* If this line results in an error, either because __watcall is not @@ -199,9 +195,12 @@ # define PNGCAPI __watcall # endif -# if defined(__GNUC__) || (defined (_MSC_VER) && (_MSC_VER >= 800)) +# if defined(__GNUC__) || (defined(_MSC_VER) && (_MSC_VER >= 800)) # define PNGCAPI __cdecl # if PNG_API_RULE == 1 + /* If this line results in an error __stdcall is not understood and + * PNG_API_RULE should not have been set to '1'. + */ # define PNGAPI __stdcall # endif # else @@ -239,7 +238,7 @@ # endif # endif /* compiler */ -#else /* !Windows/x86 */ +#else /* !Windows */ # if (defined(__IBMC__) || defined(__IBMCPP__)) && defined(__OS2__) # define PNGAPI _System # else /* !Windows/x86 && !OS/2 */ @@ -337,6 +336,7 @@ * functions in png.h will generate compiler warnings. Added at libpng * version 1.2.41. */ + # if defined(__GNUC__) # ifndef PNG_USE_RESULT # define PNG_USE_RESULT __attribute__((__warn_unused_result__)) @@ -344,21 +344,23 @@ # ifndef PNG_NORETURN # define PNG_NORETURN __attribute__((__noreturn__)) # endif -# ifndef PNG_ALLOCATED -# define PNG_ALLOCATED __attribute__((__malloc__)) -# endif -# ifndef PNG_DEPRECATED -# define PNG_DEPRECATED __attribute__((__deprecated__)) -# endif -# ifndef PNG_PRIVATE -# if 0 /* Doesn't work so we use deprecated instead*/ -# define PNG_PRIVATE \ - __attribute__((warning("This function is not exported by libpng."))) -# else -# define PNG_PRIVATE \ - __attribute__((__deprecated__)) +# if __GNUC__ >= 3 +# ifndef PNG_ALLOCATED +# define PNG_ALLOCATED __attribute__((__malloc__)) # endif -# endif +# ifndef PNG_DEPRECATED +# define PNG_DEPRECATED __attribute__((__deprecated__)) +# endif +# ifndef PNG_PRIVATE +# if 0 /* Doesn't work so we use deprecated instead*/ +# define PNG_PRIVATE \ + __attribute__((warning("This function is not exported by libpng."))) +# else +# define PNG_PRIVATE \ + __attribute__((__deprecated__)) +# endif +# endif +# endif /* __GNUC__ >= 3 */ # endif /* __GNUC__ */ # if defined(_MSC_VER) && (_MSC_VER >= 1300) @@ -400,7 +402,7 @@ #ifndef PNG_FP_EXPORT /* A floating point API. */ # ifdef PNG_FLOATING_POINT_SUPPORTED # define PNG_FP_EXPORT(ordinal, type, name, args)\ - PNG_EXPORT(ordinal, type, name, args) + PNG_EXPORT(ordinal, type, name, args); # else /* No floating point APIs */ # define PNG_FP_EXPORT(ordinal, type, name, args) # endif @@ -408,7 +410,7 @@ #ifndef PNG_FIXED_EXPORT /* A fixed point API. */ # ifdef PNG_FIXED_POINT_SUPPORTED # define PNG_FIXED_EXPORT(ordinal, type, name, args)\ - PNG_EXPORT(ordinal, type, name, args) + PNG_EXPORT(ordinal, type, name, args); # else /* No fixed point APIs */ # define PNG_FIXED_EXPORT(ordinal, type, name, args) # endif diff --git a/3rdparty/libpng/pngdebug.h b/3rdparty/libpng/pngdebug.h index 16f81fdd14..020369f061 100644 --- a/3rdparty/libpng/pngdebug.h +++ b/3rdparty/libpng/pngdebug.h @@ -1,12 +1,11 @@ /* pngdebug.h - Debugging macros for libpng, also used in pngtest.c * - * Copyright (c) 1998-2011 Glenn Randers-Pehrson + * Last changed in libpng 1.5.18 [February 6, 2014] + * Copyright (c) 1998-2002,2004,2006-2014 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * - * Last changed in libpng 1.5.0 [January 6, 2011] - * * This code is released under the libpng license. * For conditions of distribution and use, see the disclaimer * and license in png.h @@ -25,7 +24,7 @@ * (actually ((void)0)). * * level: level of detail of message, starting at 0. A level 'n' - * message is preceded by 'n' tab characters (not implemented + * message is preceded by 'n' 3-space indentations (not implemented * on Microsoft compilers unless PNG_DEBUG_FILE is also * defined, to allow debug DLL compilation with no standard IO). * message: a printf(3) style text string. A trailing '\n' is added @@ -77,32 +76,29 @@ # endif /* PNG_DEBUG_FILE */ # if (PNG_DEBUG > 1) -/* Note: ["%s"m PNG_STRING_NEWLINE] probably does not work on - * non-ISO compilers - */ # ifdef __STDC__ # ifndef png_debug # define png_debug(l,m) \ do { \ int num_tabs=l; \ - fprintf(PNG_DEBUG_FILE,"%s"m PNG_STRING_NEWLINE,(num_tabs==1 ? "\t" : \ - (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":"")))); \ + fprintf(PNG_DEBUG_FILE,"%s" m PNG_STRING_NEWLINE,(num_tabs==1 ? " " : \ + (num_tabs==2 ? " " : (num_tabs>2 ? " " : "")))); \ } while (0) # endif # ifndef png_debug1 # define png_debug1(l,m,p1) \ do { \ int num_tabs=l; \ - fprintf(PNG_DEBUG_FILE,"%s"m PNG_STRING_NEWLINE,(num_tabs==1 ? "\t" : \ - (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))),p1); \ + fprintf(PNG_DEBUG_FILE,"%s" m PNG_STRING_NEWLINE,(num_tabs==1 ? " " : \ + (num_tabs==2 ? " " : (num_tabs>2 ? " " : ""))),p1); \ } while (0) # endif # ifndef png_debug2 # define png_debug2(l,m,p1,p2) \ do { \ int num_tabs=l; \ - fprintf(PNG_DEBUG_FILE,"%s"m PNG_STRING_NEWLINE,(num_tabs==1 ? "\t" : \ - (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))),p1,p2); \ + fprintf(PNG_DEBUG_FILE,"%s" m PNG_STRING_NEWLINE,(num_tabs==1 ? " " : \ + (num_tabs==2 ? " " : (num_tabs>2 ? " " : ""))),p1,p2);\ } while (0) # endif # else /* __STDC __ */ diff --git a/3rdparty/libpng/pngerror.c b/3rdparty/libpng/pngerror.c index e0585a856e..372d1b6fbd 100644 --- a/3rdparty/libpng/pngerror.c +++ b/3rdparty/libpng/pngerror.c @@ -1,8 +1,8 @@ /* pngerror.c - stub functions for i/o and memory allocation * - * Last changed in libpng 1.5.8 [February 1, 2011] - * Copyright (c) 1998-2011 Glenn Randers-Pehrson + * Last changed in libpng 1.5.19 [August 21, 2014] + * Copyright (c) 1998-2002,2004,2006-2014 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -161,7 +161,7 @@ png_format_number(png_const_charp start, png_charp end, int format, case PNG_NUMBER_FORMAT_02u: /* Expects at least 2 digits. */ mincount = 2; - /* fall through */ + /* FALL THROUGH */ case PNG_NUMBER_FORMAT_u: *--end = digits[number % 10]; @@ -171,7 +171,7 @@ png_format_number(png_const_charp start, png_charp end, int format, case PNG_NUMBER_FORMAT_02x: /* This format expects at least two digits */ mincount = 2; - /* fall through */ + /* FALL THROUGH */ case PNG_NUMBER_FORMAT_x: *--end = digits[number & 0xf]; @@ -193,7 +193,7 @@ png_format_number(png_const_charp start, png_charp end, int format, * drop the decimal point. If the number is a true zero handle that * here. */ - if (output) + if (output != 0) *--end = '.'; else if (number == 0) /* and !output */ *--end = '0'; @@ -578,6 +578,9 @@ png_longjmp,(png_structp png_ptr, int val),PNG_NORETURN) png_ptr->longjmp_fn(png_ptr->longjmp_buffer, val); # endif } +#else + PNG_UNUSED(png_ptr); + PNG_UNUSED(val); #endif /* Here if not setjmp support or if png_ptr is null. */ PNG_ABORT(); diff --git a/3rdparty/libpng/pngget.c b/3rdparty/libpng/pngget.c index 0e56124bae..5b1d757c66 100644 --- a/3rdparty/libpng/pngget.c +++ b/3rdparty/libpng/pngget.c @@ -1,8 +1,8 @@ /* pngget.c - retrieval of values from info struct * - * Last changed in libpng 1.5.7 [December 15, 2011] - * Copyright (c) 1998-2011 Glenn Randers-Pehrson + * Last changed in libpng 1.5.19 [August 21, 2014] + * Copyright (c) 1998-2002,2004,2006-2014 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -123,6 +123,9 @@ png_get_x_pixels_per_meter(png_const_structp png_ptr, png_const_infop info_ptr) if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER) return (info_ptr->x_pixels_per_unit); } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) #endif return (0); @@ -140,6 +143,9 @@ png_get_y_pixels_per_meter(png_const_structp png_ptr, png_const_infop info_ptr) if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER) return (info_ptr->y_pixels_per_unit); } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) #endif return (0); @@ -157,6 +163,9 @@ png_get_pixels_per_meter(png_const_structp png_ptr, png_const_infop info_ptr) info_ptr->x_pixels_per_unit == info_ptr->y_pixels_per_unit) return (info_ptr->x_pixels_per_unit); } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) #endif return (0); @@ -175,6 +184,9 @@ png_get_pixel_aspect_ratio(png_const_structp png_ptr, png_const_infop info_ptr) return ((float)((float)info_ptr->y_pixels_per_unit /(float)info_ptr->x_pixels_per_unit)); } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) #endif return ((float)0.0); @@ -203,6 +215,9 @@ png_get_pixel_aspect_ratio_fixed(png_const_structp png_ptr, (png_int_32)info_ptr->x_pixels_per_unit)) return res; } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) #endif return 0; @@ -220,6 +235,9 @@ png_get_x_offset_microns(png_const_structp png_ptr, png_const_infop info_ptr) if (info_ptr->offset_unit_type == PNG_OFFSET_MICROMETER) return (info_ptr->x_offset); } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) #endif return (0); @@ -236,6 +254,9 @@ png_get_y_offset_microns(png_const_structp png_ptr, png_const_infop info_ptr) if (info_ptr->offset_unit_type == PNG_OFFSET_MICROMETER) return (info_ptr->y_offset); } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) #endif return (0); @@ -252,6 +273,9 @@ png_get_x_offset_pixels(png_const_structp png_ptr, png_const_infop info_ptr) if (info_ptr->offset_unit_type == PNG_OFFSET_PIXEL) return (info_ptr->x_offset); } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) #endif return (0); @@ -268,6 +292,9 @@ png_get_y_offset_pixels(png_const_structp png_ptr, png_const_infop info_ptr) if (info_ptr->offset_unit_type == PNG_OFFSET_PIXEL) return (info_ptr->y_offset); } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) #endif return (0); @@ -646,7 +673,7 @@ png_get_gAMA(png_const_structp png_ptr, png_const_infop info_ptr, png_fixed_point igamma; png_uint_32 ok = png_get_gAMA_fixed(png_ptr, info_ptr, &igamma); - if (ok) + if (ok != 0) *file_gamma = png_float(png_ptr, igamma, "png_get_gAMA"); return ok; @@ -683,7 +710,7 @@ png_get_iCCP(png_const_structp png_ptr, png_const_infop info_ptr, if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_iCCP) && name != NULL && compression_type != NULL && profile != NULL && - proflen != NULL) + proflen != NULL) { *name = info_ptr->iccp_name; *profile = info_ptr->iccp_profile; @@ -741,14 +768,20 @@ png_get_IHDR(png_structp png_ptr, png_infop info_ptr, { png_debug1(1, "in %s retrieval function", "IHDR"); - if (png_ptr == NULL || info_ptr == NULL || width == NULL || - height == NULL || bit_depth == NULL || color_type == NULL) + if (png_ptr == NULL || info_ptr == NULL) return (0); - *width = info_ptr->width; - *height = info_ptr->height; - *bit_depth = info_ptr->bit_depth; - *color_type = info_ptr->color_type; + if (width != NULL) + *width = info_ptr->width; + + if (height != NULL) + *height = info_ptr->height; + + if (bit_depth != NULL) + *bit_depth = info_ptr->bit_depth; + + if (color_type != NULL) + *color_type = info_ptr->color_type; if (compression_type != NULL) *compression_type = info_ptr->compression_type; @@ -1121,4 +1154,17 @@ png_get_io_chunk_name (png_structp png_ptr) } #endif /* ?PNG_IO_STATE_SUPPORTED */ +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +# ifdef PNG_GET_PALETTE_MAX_SUPPORTED +int PNGAPI +png_get_palette_max(png_const_structp png_ptr, png_const_infop info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return png_ptr->num_palette_max; + + return (-1); +} +# endif +#endif + #endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */ diff --git a/3rdparty/libpng/pnginfo.h b/3rdparty/libpng/pnginfo.h index a33bfab06d..c5b68c1e84 100644 --- a/3rdparty/libpng/pnginfo.h +++ b/3rdparty/libpng/pnginfo.h @@ -1,12 +1,11 @@ /* pnginfo.h - header file for PNG reference library * - * Copyright (c) 1998-2011 Glenn Randers-Pehrson + * Last changed in libpng 1.5.0 [January 6, 2011] + * Copyright (c) 1998-2002,2004,2006-2011 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * - * Last changed in libpng 1.5.0 [January 6, 2011] - * * This code is released under the libpng license. * For conditions of distribution and use, see the disclaimer * and license in png.h @@ -242,7 +241,7 @@ defined(PNG_READ_BACKGROUND_SUPPORTED) #ifdef PNG_sPLT_SUPPORTED /* Data on sPLT chunks (there may be more than one). */ png_sPLT_tp splt_palettes; - png_uint_32 splt_palettes_num; + int splt_palettes_num; #endif #ifdef PNG_sCAL_SUPPORTED diff --git a/3rdparty/libpng/pnglibconf.h b/3rdparty/libpng/pnglibconf.h index d93adabaa6..1df6146f29 100644 --- a/3rdparty/libpng/pnglibconf.h +++ b/3rdparty/libpng/pnglibconf.h @@ -1,48 +1,28 @@ - -/* libpng STANDARD API DEFINITION */ - +/* 1.5.27 STANDARD API DEFINITION */ /* pnglibconf.h - library build configuration */ -/* Libpng 1.5.12 - July 11, 2012 */ +/* libpng version 1.5.27 - May 26, 2016 */ -/* Copyright (c) 1998-2012 Glenn Randers-Pehrson */ +/* Copyright (c) 2011-2015 Glenn Randers-Pehrson */ /* This code is released under the libpng license. */ /* For conditions of distribution and use, see the disclaimer */ /* and license in png.h */ /* pnglibconf.h */ +/* Machine generated file: DO NOT EDIT */ /* Derived from: scripts/pnglibconf.dfa */ -/* If you edit this file by hand you must obey the rules expressed in */ -/* pnglibconf.dfa with respect to the dependencies between the following */ -/* symbols. It is much better to generate a new file using */ -/* scripts/libpngconf.mak */ - #ifndef PNGLCONF_H #define PNGLCONF_H -/* settings */ -#define PNG_API_RULE 0 -#define PNG_CALLOC_SUPPORTED -#define PNG_COST_SHIFT 3 -#define PNG_DEFAULT_READ_MACROS 1 -#define PNG_GAMMA_THRESHOLD_FIXED 5000 -#define PNG_MAX_GAMMA_8 11 -#define PNG_QUANTIZE_BLUE_BITS 5 -#define PNG_QUANTIZE_GREEN_BITS 5 -#define PNG_QUANTIZE_RED_BITS 5 -#define PNG_sCAL_PRECISION 5 -#define PNG_WEIGHT_SHIFT 8 -#define PNG_ZBUF_SIZE 8192 -/* end of settings */ /* options */ #define PNG_16BIT_SUPPORTED -#define PNG_ALIGN_MEMORY_SUPPORTED +#define PNG_ALIGNED_MEMORY_SUPPORTED +/*#undef PNG_ARM_NEON_API_SUPPORTED*/ +/*#undef PNG_ARM_NEON_CHECK_SUPPORTED*/ #define PNG_BENIGN_ERRORS_SUPPORTED -#define PNG_bKGD_SUPPORTED #define PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED -#define PNG_CHECK_cHRM_SUPPORTED #define PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED -#define PNG_cHRM_SUPPORTED +#define PNG_CHECK_cHRM_SUPPORTED #define PNG_CONSOLE_IO_SUPPORTED #define PNG_CONVERT_tIME_SUPPORTED #define PNG_EASY_ACCESS_SUPPORTED @@ -51,18 +31,12 @@ #define PNG_FIXED_POINT_SUPPORTED #define PNG_FLOATING_ARITHMETIC_SUPPORTED #define PNG_FLOATING_POINT_SUPPORTED -#define PNG_gAMA_SUPPORTED +#define PNG_GET_PALETTE_MAX_SUPPORTED #define PNG_HANDLE_AS_UNKNOWN_SUPPORTED -#define PNG_hIST_SUPPORTED -#define PNG_iCCP_SUPPORTED #define PNG_INCH_CONVERSIONS_SUPPORTED #define PNG_INFO_IMAGE_SUPPORTED #define PNG_IO_STATE_SUPPORTED -#define PNG_iTXt_SUPPORTED #define PNG_MNG_FEATURES_SUPPORTED -#define PNG_oFFs_SUPPORTED -#define PNG_pCAL_SUPPORTED -#define PNG_pHYs_SUPPORTED #define PNG_POINTER_INDEXING_SUPPORTED #define PNG_PROGRESSIVE_READ_SUPPORTED #define PNG_READ_16BIT_SUPPORTED @@ -70,68 +44,62 @@ #define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED #define PNG_READ_BACKGROUND_SUPPORTED #define PNG_READ_BGR_SUPPORTED -#define PNG_READ_bKGD_SUPPORTED #define PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED -#define PNG_READ_cHRM_SUPPORTED #define PNG_READ_COMPOSITE_NODIV_SUPPORTED #define PNG_READ_COMPRESSED_TEXT_SUPPORTED #define PNG_READ_EXPAND_16_SUPPORTED #define PNG_READ_EXPAND_SUPPORTED #define PNG_READ_FILLER_SUPPORTED -#define PNG_READ_gAMA_SUPPORTED #define PNG_READ_GAMMA_SUPPORTED +#define PNG_READ_GET_PALETTE_MAX_SUPPORTED #define PNG_READ_GRAY_TO_RGB_SUPPORTED -#define PNG_READ_hIST_SUPPORTED -#define PNG_READ_iCCP_SUPPORTED #define PNG_READ_INTERLACING_SUPPORTED #define PNG_READ_INT_FUNCTIONS_SUPPORTED #define PNG_READ_INVERT_ALPHA_SUPPORTED #define PNG_READ_INVERT_SUPPORTED -#define PNG_READ_iTXt_SUPPORTED -#define PNG_READ_oFFs_SUPPORTED #define PNG_READ_OPT_PLTE_SUPPORTED -#define PNG_READ_PACK_SUPPORTED #define PNG_READ_PACKSWAP_SUPPORTED -#define PNG_READ_pCAL_SUPPORTED -#define PNG_READ_pHYs_SUPPORTED +#define PNG_READ_PACK_SUPPORTED #define PNG_READ_QUANTIZE_SUPPORTED #define PNG_READ_RGB_TO_GRAY_SUPPORTED -#define PNG_READ_sBIT_SUPPORTED #define PNG_READ_SCALE_16_TO_8_SUPPORTED -#define PNG_READ_sCAL_SUPPORTED #define PNG_READ_SHIFT_SUPPORTED -#define PNG_READ_sPLT_SUPPORTED -#define PNG_READ_sRGB_SUPPORTED #define PNG_READ_STRIP_16_TO_8_SUPPORTED #define PNG_READ_STRIP_ALPHA_SUPPORTED #define PNG_READ_SUPPORTED #define PNG_READ_SWAP_ALPHA_SUPPORTED #define PNG_READ_SWAP_SUPPORTED -#define PNG_READ_tEXt_SUPPORTED #define PNG_READ_TEXT_SUPPORTED -#define PNG_READ_tIME_SUPPORTED #define PNG_READ_TRANSFORMS_SUPPORTED -#define PNG_READ_tRNS_SUPPORTED #define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED #define PNG_READ_USER_CHUNKS_SUPPORTED #define PNG_READ_USER_TRANSFORM_SUPPORTED +#define PNG_READ_bKGD_SUPPORTED +#define PNG_READ_cHRM_SUPPORTED +#define PNG_READ_gAMA_SUPPORTED +#define PNG_READ_hIST_SUPPORTED +#define PNG_READ_iCCP_SUPPORTED +#define PNG_READ_iTXt_SUPPORTED +#define PNG_READ_oFFs_SUPPORTED +#define PNG_READ_pCAL_SUPPORTED +#define PNG_READ_pHYs_SUPPORTED +#define PNG_READ_sBIT_SUPPORTED +#define PNG_READ_sCAL_SUPPORTED +#define PNG_READ_sPLT_SUPPORTED +#define PNG_READ_sRGB_SUPPORTED +#define PNG_READ_tEXt_SUPPORTED +#define PNG_READ_tIME_SUPPORTED +#define PNG_READ_tRNS_SUPPORTED #define PNG_READ_zTXt_SUPPORTED +/*#undef PNG_SAFE_LIMITS_SUPPORTED*/ #define PNG_SAVE_INT_32_SUPPORTED -#define PNG_sBIT_SUPPORTED -#define PNG_sCAL_SUPPORTED #define PNG_SEQUENTIAL_READ_SUPPORTED -#define PNG_SET_CHUNK_CACHE_LIMIT_SUPPORTED -#define PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED #define PNG_SETJMP_SUPPORTED +/*#undef PNG_SET_OPTION_SUPPORTED*/ #define PNG_SET_USER_LIMITS_SUPPORTED -#define PNG_sPLT_SUPPORTED -#define PNG_sRGB_SUPPORTED #define PNG_STDIO_SUPPORTED -#define PNG_tEXt_SUPPORTED #define PNG_TEXT_SUPPORTED #define PNG_TIME_RFC1123_SUPPORTED -#define PNG_tIME_SUPPORTED -#define PNG_tRNS_SUPPORTED #define PNG_UNKNOWN_CHUNKS_SUPPORTED #define PNG_USER_CHUNKS_SUPPORTED #define PNG_USER_LIMITS_SUPPORTED @@ -142,45 +110,78 @@ #define PNG_WRITE_16BIT_SUPPORTED #define PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED #define PNG_WRITE_BGR_SUPPORTED -#define PNG_WRITE_bKGD_SUPPORTED #define PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED -#define PNG_WRITE_cHRM_SUPPORTED #define PNG_WRITE_COMPRESSED_TEXT_SUPPORTED +#define PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED #define PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED #define PNG_WRITE_FILLER_SUPPORTED #define PNG_WRITE_FILTER_SUPPORTED #define PNG_WRITE_FLUSH_SUPPORTED -#define PNG_WRITE_gAMA_SUPPORTED -#define PNG_WRITE_hIST_SUPPORTED -#define PNG_WRITE_iCCP_SUPPORTED +#define PNG_WRITE_GET_PALETTE_MAX_SUPPORTED #define PNG_WRITE_INTERLACING_SUPPORTED #define PNG_WRITE_INT_FUNCTIONS_SUPPORTED #define PNG_WRITE_INVERT_ALPHA_SUPPORTED #define PNG_WRITE_INVERT_SUPPORTED +#define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED +#define PNG_WRITE_PACKSWAP_SUPPORTED +#define PNG_WRITE_PACK_SUPPORTED +#define PNG_WRITE_SHIFT_SUPPORTED +#define PNG_WRITE_SUPPORTED +#define PNG_WRITE_SWAP_ALPHA_SUPPORTED +#define PNG_WRITE_SWAP_SUPPORTED +#define PNG_WRITE_TEXT_SUPPORTED +#define PNG_WRITE_TRANSFORMS_SUPPORTED +#define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_WRITE_USER_TRANSFORM_SUPPORTED +#define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED +#define PNG_WRITE_bKGD_SUPPORTED +#define PNG_WRITE_cHRM_SUPPORTED +#define PNG_WRITE_gAMA_SUPPORTED +#define PNG_WRITE_hIST_SUPPORTED +#define PNG_WRITE_iCCP_SUPPORTED #define PNG_WRITE_iTXt_SUPPORTED #define PNG_WRITE_oFFs_SUPPORTED -#define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED -#define PNG_WRITE_PACK_SUPPORTED -#define PNG_WRITE_PACKSWAP_SUPPORTED #define PNG_WRITE_pCAL_SUPPORTED #define PNG_WRITE_pHYs_SUPPORTED #define PNG_WRITE_sBIT_SUPPORTED #define PNG_WRITE_sCAL_SUPPORTED -#define PNG_WRITE_SHIFT_SUPPORTED #define PNG_WRITE_sPLT_SUPPORTED #define PNG_WRITE_sRGB_SUPPORTED -#define PNG_WRITE_SUPPORTED -#define PNG_WRITE_SWAP_ALPHA_SUPPORTED -#define PNG_WRITE_SWAP_SUPPORTED #define PNG_WRITE_tEXt_SUPPORTED -#define PNG_WRITE_TEXT_SUPPORTED #define PNG_WRITE_tIME_SUPPORTED -#define PNG_WRITE_TRANSFORMS_SUPPORTED #define PNG_WRITE_tRNS_SUPPORTED -#define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED -#define PNG_WRITE_USER_TRANSFORM_SUPPORTED -#define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED #define PNG_WRITE_zTXt_SUPPORTED +#define PNG_bKGD_SUPPORTED +#define PNG_cHRM_SUPPORTED +#define PNG_gAMA_SUPPORTED +#define PNG_hIST_SUPPORTED +#define PNG_iCCP_SUPPORTED +#define PNG_iTXt_SUPPORTED +#define PNG_oFFs_SUPPORTED +#define PNG_pCAL_SUPPORTED +#define PNG_pHYs_SUPPORTED +#define PNG_sBIT_SUPPORTED +#define PNG_sCAL_SUPPORTED +#define PNG_sPLT_SUPPORTED +#define PNG_sRGB_SUPPORTED +#define PNG_tEXt_SUPPORTED +#define PNG_tIME_SUPPORTED +#define PNG_tRNS_SUPPORTED #define PNG_zTXt_SUPPORTED /* end of options */ +/* settings */ +#define PNG_API_RULE 0 +#define PNG_DEFAULT_READ_MACROS 1 +#define PNG_GAMMA_THRESHOLD_FIXED 5000 +#define PNG_MAX_GAMMA_8 11 +#define PNG_QUANTIZE_BLUE_BITS 5 +#define PNG_QUANTIZE_GREEN_BITS 5 +#define PNG_QUANTIZE_RED_BITS 5 +#define PNG_USER_CHUNK_CACHE_MAX 1000 +#define PNG_USER_CHUNK_MALLOC_MAX 8000000 +#define PNG_USER_HEIGHT_MAX 1000000 +#define PNG_USER_WIDTH_MAX 1000000 +#define PNG_ZBUF_SIZE 8192 +#define PNG_sCAL_PRECISION 5 +/* end of settings */ #endif /* PNGLCONF_H */ diff --git a/3rdparty/libpng/pngmem.c b/3rdparty/libpng/pngmem.c index bf5ff037da..ae74dcace0 100644 --- a/3rdparty/libpng/pngmem.c +++ b/3rdparty/libpng/pngmem.c @@ -1,8 +1,8 @@ /* pngmem.c - stub functions for memory allocation * - * Last changed in libpng 1.5.7 [December 15, 2011] - * Copyright (c) 1998-2011 Glenn Randers-Pehrson + * Last changed in libpng 1.5.13 [September 27, 2012] + * Copyright (c) 1998-2002,2004,2006-2012 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -56,7 +56,7 @@ png_create_struct_2,(int type, png_malloc_ptr malloc_fn, png_voidp mem_ptr), if (malloc_fn != NULL) { png_struct dummy_struct; - memset(&dummy_struct, 0, sizeof dummy_struct); + png_memset(&dummy_struct, 0, sizeof dummy_struct); dummy_struct.mem_ptr=mem_ptr; struct_ptr = (*(malloc_fn))(&dummy_struct, (png_alloc_size_t)size); } @@ -90,7 +90,7 @@ png_destroy_struct_2(png_voidp struct_ptr, png_free_ptr free_fn, if (free_fn != NULL) { png_struct dummy_struct; - memset(&dummy_struct, 0, sizeof dummy_struct); + png_memset(&dummy_struct, 0, sizeof dummy_struct); dummy_struct.mem_ptr=mem_ptr; (*(free_fn))(&dummy_struct, struct_ptr); return; @@ -102,7 +102,7 @@ png_destroy_struct_2(png_voidp struct_ptr, png_free_ptr free_fn, } /* Allocate memory. For reasonable files, size should never exceed - * 64K. However, zlib may allocate more then 64K if you don't tell + * 64K. However, zlib may allocate more than 64K if you don't tell * it not to. See zconf.h and png.h for more information. zlib does * need to allocate exactly 64K, so whatever you call here must * have the ability to do that. @@ -475,7 +475,7 @@ png_destroy_struct_2(png_voidp struct_ptr, png_free_ptr free_fn, } /* Allocate memory. For reasonable files, size should never exceed - * 64K. However, zlib may allocate more then 64K if you don't tell + * 64K. However, zlib may allocate more than 64K if you don't tell * it not to. See zconf.h and png.h for more information. zlib does * need to allocate exactly 64K, so whatever you call here must * have the ability to do that. diff --git a/3rdparty/libpng/pngpread.c b/3rdparty/libpng/pngpread.c index 6b65ba8f4e..9cf987d7e3 100644 --- a/3rdparty/libpng/pngpread.c +++ b/3rdparty/libpng/pngpread.c @@ -1,8 +1,8 @@ /* pngpread.c - read a png file in push mode * - * Last changed in libpng 1.5.11 [June 14, 2012] - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.23 [July 23, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -19,7 +19,6 @@ #define PNG_READ_SIG_MODE 0 #define PNG_READ_CHUNK_MODE 1 #define PNG_READ_IDAT_MODE 2 -#define PNG_SKIP_MODE 3 #define PNG_READ_tEXt_MODE 4 #define PNG_READ_zTXt_MODE 5 #define PNG_READ_DONE_MODE 6 @@ -49,7 +48,7 @@ png_process_data_pause(png_structp png_ptr, int save) /* It's easiest for the caller if we do the save, then the caller doesn't * have to supply the same data again: */ - if (save) + if (save != 0) png_push_save_buffer(png_ptr); else { @@ -71,32 +70,15 @@ png_process_data_pause(png_structp png_ptr, int save) png_uint_32 PNGAPI png_process_data_skip(png_structp png_ptr) { - png_uint_32 remaining = 0; - - if (png_ptr != NULL && png_ptr->process_mode == PNG_SKIP_MODE && - png_ptr->skip_length > 0) - { - /* At the end of png_process_data the buffer size must be 0 (see the loop - * above) so we can detect a broken call here: - */ - if (png_ptr->buffer_size != 0) - png_error(png_ptr, - "png_process_data_skip called inside png_process_data"); - - /* If is impossible for there to be a saved buffer at this point - - * otherwise we could not be in SKIP mode. This will also happen if - * png_process_skip is called inside png_process_data (but only very - * rarely.) - */ - if (png_ptr->save_buffer_size != 0) - png_error(png_ptr, "png_process_data_skip called with saved data"); - - remaining = png_ptr->skip_length; - png_ptr->skip_length = 0; - png_ptr->process_mode = PNG_READ_CHUNK_MODE; - } - - return remaining; + /* TODO: Deprecate and remove this API. + * Somewhere the implementation of this seems to have been lost, + * or abandoned. It was only to support some internal back-door access + * to png_struct) in libpng-1.4.x. + */ + png_warning(png_ptr, + "png_process_data_skip is not implemented in any current version" + " of libpng"); + return 0; } /* What we do with the incoming data depends on what we were previously @@ -128,12 +110,6 @@ png_process_some_data(png_structp png_ptr, png_infop info_ptr) break; } - case PNG_SKIP_MODE: - { - png_push_crc_finish(png_ptr); - break; - } - default: { png_ptr->buffer_size = 0; @@ -151,7 +127,7 @@ png_process_some_data(png_structp png_ptr, png_infop info_ptr) void /* PRIVATE */ png_push_read_sig(png_structp png_ptr, png_infop info_ptr) { - png_size_t num_checked = png_ptr->sig_bytes, + png_size_t num_checked = png_ptr->sig_bytes, /* SAFE, does not exceed 8 */ num_to_check = 8 - num_checked; if (png_ptr->buffer_size < num_to_check) @@ -564,76 +540,6 @@ png_push_read_chunk(png_structp png_ptr, png_infop info_ptr) png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; } -void /* PRIVATE */ -png_push_crc_skip(png_structp png_ptr, png_uint_32 skip) -{ - png_ptr->process_mode = PNG_SKIP_MODE; - png_ptr->skip_length = skip; -} - -void /* PRIVATE */ -png_push_crc_finish(png_structp png_ptr) -{ - if (png_ptr->skip_length && png_ptr->save_buffer_size) - { - png_size_t save_size = png_ptr->save_buffer_size; - png_uint_32 skip_length = png_ptr->skip_length; - - /* We want the smaller of 'skip_length' and 'save_buffer_size', but - * they are of different types and we don't know which variable has the - * fewest bits. Carefully select the smaller and cast it to the type of - * the larger - this cannot overflow. Do not cast in the following test - * - it will break on either 16 or 64 bit platforms. - */ - if (skip_length < save_size) - save_size = (png_size_t)skip_length; - - else - skip_length = (png_uint_32)save_size; - - png_calculate_crc(png_ptr, png_ptr->save_buffer_ptr, save_size); - - png_ptr->skip_length -= skip_length; - png_ptr->buffer_size -= save_size; - png_ptr->save_buffer_size -= save_size; - png_ptr->save_buffer_ptr += save_size; - } - - if (png_ptr->skip_length && png_ptr->current_buffer_size) - { - png_size_t save_size = png_ptr->current_buffer_size; - png_uint_32 skip_length = png_ptr->skip_length; - - /* We want the smaller of 'skip_length' and 'current_buffer_size', here, - * the same problem exists as above and the same solution. - */ - if (skip_length < save_size) - save_size = (png_size_t)skip_length; - - else - skip_length = (png_uint_32)save_size; - - png_calculate_crc(png_ptr, png_ptr->current_buffer_ptr, save_size); - - png_ptr->skip_length -= skip_length; - png_ptr->buffer_size -= save_size; - png_ptr->current_buffer_size -= save_size; - png_ptr->current_buffer_ptr += save_size; - } - - if (!png_ptr->skip_length) - { - if (png_ptr->buffer_size < 4) - { - png_push_save_buffer(png_ptr); - return; - } - - png_crc_finish(png_ptr, 0); - png_ptr->process_mode = PNG_READ_CHUNK_MODE; - } -} - void PNGCBAPI png_push_fill_buffer(png_structp png_ptr, png_bytep buffer, png_size_t length) { @@ -897,6 +803,12 @@ png_process_IDAT_data(png_structp png_ptr, png_bytep buffer, */ ret = inflate(&png_ptr->zstream, Z_SYNC_FLUSH); + /* Hack, added in 1.5.18: the progressive reader does not reset + * png_ptr->zstream, so any attempt to use it after the last IDAT fails + * (silently). This allows the read code to do the reset when required. + */ + png_ptr->flags |= PNG_FLAG_ZSTREAM_PROGRESSIVE; + /* Check for any failure before proceeding. */ if (ret != Z_OK && ret != Z_STREAM_END) { @@ -1285,7 +1197,7 @@ png_progressive_combine_row (png_structp png_ptr, png_bytep old_row, * it must be png_ptr->row_buf+1 */ if (new_row != NULL) - png_combine_row(png_ptr, old_row, 1/*display*/); + png_combine_row(png_ptr, old_row, 1/*blocky display*/); } #endif /* PNG_READ_INTERLACING_SUPPORTED */ diff --git a/3rdparty/libpng/pngpriv.h b/3rdparty/libpng/pngpriv.h index e7824b839e..8bdccccafb 100644 --- a/3rdparty/libpng/pngpriv.h +++ b/3rdparty/libpng/pngpriv.h @@ -1,13 +1,11 @@ /* pngpriv.h - private declarations for use inside libpng * - * For conditions of distribution and use, see copyright notice in png.h - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.26 [December 17, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * - * Last changed in libpng 1.5.10 [March 29, 2012] - * * This code is released under the libpng license. * For conditions of distribution and use, see the disclaimer * and license in png.h @@ -39,6 +37,7 @@ */ #define _POSIX_SOURCE 1 /* Just the POSIX 1003.1 and C89 APIs */ +#ifndef PNG_VERSION_INFO_ONLY /* This is required for the definition of abort(), used as a last ditch * error handler when all else fails. */ @@ -46,6 +45,7 @@ /* This is used to find 'offsetof', used below for alignment tests. */ #include +#endif /* !PNG_VERSION_INFO_ONLY */ #define PNGLIB_BUILD /*libpng is being built, not used*/ @@ -125,14 +125,59 @@ #endif #include "png.h" -#include "pnginfo.h" -#include "pngstruct.h" /* pngconf.h does not set PNG_DLL_EXPORT unless it is required, so: */ #ifndef PNG_DLL_EXPORT # define PNG_DLL_EXPORT #endif +/* Compile time options. + * ===================== + * In a multi-arch build the compiler may compile the code several times for the + * same object module, producing different binaries for different architectures. + * When this happens configure-time setting of the target host options cannot be + * done and this interferes with the handling of the ARM NEON optimizations, and + * possibly other similar optimizations. Put additional tests here; in general + * this is needed when the same option can be changed at both compile time and + * run time depending on the target OS (i.e. iOS vs Android.) + * + * NOTE: symbol prefixing does not pass $(CFLAGS) to the preprocessor, because + * this is not possible with certain compilers (Oracle SUN OS CC), as a result + * it is necessary to ensure that all extern functions that *might* be used + * regardless of $(CFLAGS) get declared in this file. The test on __ARM_NEON__ + * below is one example of this behavior because it is controlled by the + * presence or not of -mfpu=neon on the GCC command line, it is possible to do + * this in $(CC), e.g. "CC=gcc -mfpu=neon", but people who build libpng rarely + * do this. + */ +#ifndef PNG_ARM_NEON_OPT + /* ARM NEON optimizations are being controlled by the compiler settings, + * typically the target FPU. If the FPU has been set to NEON (-mfpu=neon + * with GCC) then the compiler will define __ARM_NEON__ and we can rely + * unconditionally on NEON instructions not crashing, otherwise we must + * disable use of NEON instructions. + * + * NOTE: at present these optimizations depend on 'ALIGNED_MEMORY', so they + * can only be turned on automatically if that is supported too. If + * PNG_ARM_NEON_OPT is set in CPPFLAGS (to >0) then arm/arm_init.c will fail + * to compile with an appropriate #error if ALIGNED_MEMORY has been turned + * off. + */ +# if defined(__ARM_NEON__) && defined(PNG_ALIGNED_MEMORY_SUPPORTED) +# define PNG_ARM_NEON_OPT 2 +# else +# define PNG_ARM_NEON_OPT 0 +# endif +#endif + + +#if PNG_ARM_NEON_OPT > 0 + /* NEON optimizations are to be at least considered by libpng, so enable the + * callbacks to do this. + */ +# define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_neon +#endif + /* SECURITY and SAFETY: * * By default libpng is built without any internal limits on image size, @@ -143,44 +188,6 @@ * real system capabilities. */ -#ifdef PNG_SAFE_LIMITS_SUPPORTED - /* 'safe' limits */ -# ifndef PNG_USER_WIDTH_MAX -# define PNG_USER_WIDTH_MAX 1000000 -# endif -# ifndef PNG_USER_HEIGHT_MAX -# define PNG_USER_HEIGHT_MAX 1000000 -# endif -# ifndef PNG_USER_CHUNK_CACHE_MAX -# define PNG_USER_CHUNK_CACHE_MAX 128 -# endif -# ifndef PNG_USER_CHUNK_MALLOC_MAX -# define PNG_USER_CHUNK_MALLOC_MAX 8000000 -# endif -#else - /* values for no limits */ -# ifndef PNG_USER_WIDTH_MAX -# define PNG_USER_WIDTH_MAX 0x7fffffff -# endif -# ifndef PNG_USER_HEIGHT_MAX -# define PNG_USER_HEIGHT_MAX 0x7fffffff -# endif -# ifndef PNG_USER_CHUNK_CACHE_MAX -# define PNG_USER_CHUNK_CACHE_MAX 0 -# endif -# ifndef PNG_USER_CHUNK_MALLOC_MAX -# define PNG_USER_CHUNK_MALLOC_MAX 0 -# endif -#endif - -/* This is used for 16 bit gamma tables - only the top level pointers are const, - * this could be changed: - */ -typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; - -/* Added at libpng-1.2.9 */ -/* Moved to pngpriv.h at libpng-1.5.0 */ - /* config.h is created by and PNG_CONFIGURE_LIBPNG is set by the "configure" * script. We may need it here to get the correct configuration on things * like limits. @@ -191,9 +198,11 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; # endif #endif -/* Moved to pngpriv.h at libpng-1.5.0 */ -/* NOTE: some of these may have been used in external applications as - * these definitions were exposed in pngconf.h prior to 1.5. +/* SECURITY and SAFETY: + * + * libpng is built with support for internal limits on image dimensions and + * memory usage. These are documented in scripts/pnglibconf.dfa of the + * source and recorded in the machine generated header file pnglibconf.h. */ /* If you are running on a machine where you cannot allocate more @@ -211,6 +220,11 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; # define PNG_MAX_MALLOC_64K #endif +/* Moved to pngpriv.h at libpng-1.5.0 */ +/* NOTE: some of these may have been used in external applications as + * these definitions were exposed in pngconf.h prior to 1.5. + */ + #ifndef PNG_UNUSED /* Unused formal parameter warnings are silenced using the following macro * which is expected to have no bad effects on performance (optimizing @@ -246,7 +260,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; /* Modern compilers support restrict, but assume not for anything not * recognized here: */ -# if defined __GNUC__ || defined _MSC_VER || defined __WATCOMC__ +# if defined(__GNUC__) || defined(_MSC_VER) || defined(__WATCOMC__) # define PNG_RESTRICT restrict # else # define PNG_RESTRICT @@ -260,8 +274,6 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; #ifdef PNG_WARNINGS_SUPPORTED # define PNG_WARNING_PARAMETERS(p) png_warning_parameters p; #else -# define png_warning(s1,s2) ((void)(s1)) -# define png_chunk_warning(s1,s2) ((void)(s1)) # define png_warning_parameter(p,number,string) ((void)0) # define png_warning_parameter_unsigned(p,number,format,value) ((void)0) # define png_warning_parameter_signed(p,number,format,value) ((void)0) @@ -269,8 +281,6 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; # define PNG_WARNING_PARAMETERS(p) #endif #ifndef PNG_ERROR_TEXT_SUPPORTED -# define png_error(s1,s2) png_err(s1) -# define png_chunk_error(s1,s2) png_err(s1) # define png_fixed_error(s1,s2) png_err(s1) #endif @@ -308,6 +318,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; # define PNGFAPI /* PRIVATE */ #endif +#ifndef PNG_VERSION_INFO_ONLY /* Other defines specific to compilers can go here. Try to keep * them inside an appropriate ifdef/endif pair for portability. */ @@ -352,6 +363,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; defined(_WIN32) || defined(__WIN32__) # include /* defines _WINDOWS_ macro */ #endif +#endif /* !PNG_VERSION_INFO_ONLY */ /* Moved here around 1.5.0beta36 from pngconf.h */ /* Users may want to use these so they are not private. Any library @@ -360,7 +372,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; /* Memory model/platform independent fns */ #ifndef PNG_ABORT -# if defined(_WINDOWS_) && !defined(HAVE_WINRT) +# ifdef _WINDOWS_ # define PNG_ABORT() ExitProcess(0) # else # define PNG_ABORT() abort() @@ -378,12 +390,12 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; # define png_memcpy _fmemcpy # define png_memset _fmemset #else -# if defined(_WINDOWS_) && !defined(HAVE_WINRT) /* Favor Windows over C runtime fns */ +# ifdef _WINDOWS_ /* Favor Windows over C runtime fns */ # define CVT_PTR(ptr) (ptr) # define CVT_PTR_NOCHECK(ptr) (ptr) # define png_strlen lstrlenA # define png_memcmp memcmp -# define png_memcpy CopyMemory +# define png_memcpy memcpy # define png_memset memset # else # define CVT_PTR(ptr) (ptr) @@ -416,7 +428,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; #if PNG_ALIGN_TYPE == PNG_ALIGN_SIZE /* This is used because in some compiler implementations non-aligned * structure members are supported, so the offsetof approach below fails. - * Set PNG_ALIGN_TO_SIZE=0 for compiler combinations where unaligned access + * Set PNG_ALIGN_SIZE=0 for compiler combinations where unaligned access * is good for performance. Do not do this unless you have tested the result * and understand it. */ @@ -505,10 +517,6 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; #define PNG_STRUCT_PNG 0x0001 #define PNG_STRUCT_INFO 0x0002 -/* Scaling factor for filter heuristic weighting calculations */ -#define PNG_WEIGHT_FACTOR (1<<(PNG_WEIGHT_SHIFT)) -#define PNG_COST_FACTOR (1<<(PNG_COST_SHIFT)) - /* Flags for the png_ptr->flags rather than declaring a byte for each one */ #define PNG_FLAG_ZLIB_CUSTOM_STRATEGY 0x0001 #define PNG_FLAG_ZLIB_CUSTOM_LEVEL 0x0002 @@ -531,7 +539,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; #define PNG_FLAG_STRIP_ERROR_NUMBERS 0x40000 #define PNG_FLAG_STRIP_ERROR_TEXT 0x80000 #define PNG_FLAG_MALLOC_NULL_MEM_OK 0x100000 - /* 0x200000 unused */ +#define PNG_FLAG_ZSTREAM_PROGRESSIVE 0x200000 /* 0x400000 unused */ #define PNG_FLAG_BENIGN_ERRORS_WARN 0x800000 /* Added to libpng-1.4.0 */ #define PNG_FLAG_ZTXT_CUSTOM_STRATEGY 0x1000000 /* 5 lines added */ @@ -617,8 +625,10 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; #define png_fixed(png_ptr, fp, s) ((fp) <= 21474 && (fp) >= -21474 ?\ ((png_fixed_point)(100000 * (fp))) : (png_fixed_error(png_ptr, s),0)) #else +#ifndef PNG_VERSION_INFO_ONLY PNG_EXTERN png_fixed_point png_fixed PNGARG((png_structp png_ptr, double fp, png_const_charp text)); +#endif /* !PNG_VERSION_INFO_ONLY */ #endif #endif @@ -692,6 +702,18 @@ PNG_EXTERN png_fixed_point png_fixed PNGARG((png_structp png_ptr, double fp, #define PNG_GAMMA_MAC_INVERSE 65909 #define PNG_GAMMA_sRGB_INVERSE 45455 +/* Almost everything below is C specific; the #defines above can be used in + * non-C code (so long as it is C-preprocessed) the rest of this stuff cannot. + */ +#ifndef PNG_VERSION_INFO_ONLY + +#include "pngstruct.h" +#include "pnginfo.h" + +/* This is used for 16 bit gamma tables -- only the top level pointers are + * const; this could be changed: + */ +typedef const png_uint_16p * png_const_uint_16pp; /* Inhibit C++ name-mangling for libpng functions but not for system calls. */ #ifdef __cplusplus @@ -823,10 +845,8 @@ PNG_EXTERN void png_write_IEND PNGARG((png_structp png_ptr)); # ifdef PNG_FLOATING_POINT_SUPPORTED PNG_EXTERN void png_write_gAMA PNGARG((png_structp png_ptr, double file_gamma)); # endif -# ifdef PNG_FIXED_POINT_SUPPORTED PNG_EXTERN void png_write_gAMA_fixed PNGARG((png_structp png_ptr, png_fixed_point file_gamma)); -# endif #endif #ifdef PNG_WRITE_sBIT_SUPPORTED @@ -881,13 +901,6 @@ PNG_EXTERN void png_write_hIST PNGARG((png_structp png_ptr, png_const_uint_16p hist, int num_hist)); #endif -/* Chunks that have keywords */ -#if defined(PNG_WRITE_TEXT_SUPPORTED) || defined(PNG_WRITE_pCAL_SUPPORTED) || \ - defined(PNG_WRITE_iCCP_SUPPORTED) || defined(PNG_WRITE_sPLT_SUPPORTED) -PNG_EXTERN png_size_t png_check_keyword PNGARG((png_structp png_ptr, - png_const_charp key, png_charpp new_key)); -#endif - #ifdef PNG_WRITE_tEXt_SUPPORTED PNG_EXTERN void png_write_tEXt PNGARG((png_structp png_ptr, png_const_charp key, png_const_charp text, png_size_t text_len)); @@ -993,8 +1006,8 @@ PNG_EXTERN void png_do_write_interlace PNGARG((png_row_infop row_info, /* Unfilter a row: check the filter value before calling this, there is no point * calling it for PNG_FILTER_VALUE_NONE. */ -PNG_EXTERN void png_read_filter_row PNGARG((png_structp pp, png_row_infop row_info, - png_bytep row, png_const_bytep prev_row, int filter)); +PNG_EXTERN void png_read_filter_row PNGARG((png_structp pp, png_row_infop + row_info, png_bytep row, png_const_bytep prev_row, int filter)); PNG_EXTERN void png_read_filter_row_up_neon PNGARG((png_row_infop row_info, png_bytep row, png_const_bytep prev_row)); @@ -1293,9 +1306,6 @@ PNG_EXTERN void png_push_read_chunk PNGARG((png_structp png_ptr, PNG_EXTERN void png_push_read_sig PNGARG((png_structp png_ptr, png_infop info_ptr)); PNG_EXTERN void png_push_check_crc PNGARG((png_structp png_ptr)); -PNG_EXTERN void png_push_crc_skip PNGARG((png_structp png_ptr, - png_uint_32 length)); -PNG_EXTERN void png_push_crc_finish PNGARG((png_structp png_ptr)); PNG_EXTERN void png_push_save_buffer PNGARG((png_structp png_ptr)); PNG_EXTERN void png_push_restore_buffer PNGARG((png_structp png_ptr, png_bytep buffer, png_size_t buffer_length)); @@ -1491,14 +1501,16 @@ PNG_EXTERN void png_formatted_warning(png_structp png_ptr, /* ASCII to FP interfaces, currently only implemented if sCAL * support is required. */ -#if defined(PNG_READ_sCAL_SUPPORTED) +#ifdef PNG_sCAL_SUPPORTED /* MAX_DIGITS is actually the maximum number of characters in an sCAL * width or height, derived from the precision (number of significant * digits - a build time settable option) and assumpitions about the * maximum ridiculous exponent. */ #define PNG_sCAL_MAX_DIGITS (PNG_sCAL_PRECISION+1/*.*/+1/*E*/+10/*exponent*/) +#endif +#ifdef PNG_sCAL_SUPPORTED #ifdef PNG_FLOATING_POINT_SUPPORTED PNG_EXTERN void png_ascii_from_fp PNGARG((png_structp png_ptr, png_charp ascii, png_size_t size, double fp, unsigned int precision)); @@ -1583,14 +1595,14 @@ PNG_EXTERN void png_ascii_from_fixed PNGARG((png_structp png_ptr, #define PNG_FP_IS_POSITIVE(state) (((state) & PNG_FP_NZ_MASK) == PNG_FP_Z_MASK) #define PNG_FP_IS_NEGATIVE(state) (((state) & PNG_FP_NZ_MASK) == PNG_FP_NZ_MASK) -/* The actual parser. This can be called repeatedly, it updates +/* The actual parser. This can be called repeatedly. It updates * the index into the string and the state variable (which must - * be initialzed to 0). It returns a result code, as above. There + * be initialized to 0). It returns a result code, as above. There * is no point calling the parser any more if it fails to advance to * the end of the string - it is stuck on an invalid character (or * terminated by '\0'). * - * Note that the pointer will consume an E or even an E+ then leave + * Note that the pointer will consume an E or even an E+ and then leave * a 'maybe' state even though a preceding integer.fraction is valid. * The PNG_FP_WAS_VALID flag indicates that a preceding substring was * a valid number. It's possible to recover from this by calling @@ -1629,7 +1641,7 @@ PNG_EXTERN png_fixed_point png_muldiv_warn PNGARG((png_structp png_ptr, png_fixed_point a, png_int_32 multiplied_by, png_int_32 divided_by)); #endif -#ifdef PNG_READ_GAMMA_SUPPORTED +#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_cHRM_SUPPORTED) /* Calculate a reciprocal - used for gamma values. This returns * 0 if the argument is 0 in order to maintain an undefined value, * there are no warnings. @@ -1664,7 +1676,80 @@ PNG_EXTERN void png_build_gamma_table PNGARG((png_structp png_ptr, int bit_depth)); #endif -/* Maintainer: Put new private prototypes here ^ and in libpngpf.3 */ +/* Missing declarations if FIXED_POINT is *not* supported - fixed properly + * in libpng 1.6 + */ +#ifndef PNG_FIXED_POINT_SUPPORTED +#ifdef PNG_cHRM_SUPPORTED +PNG_EXTERN png_uint_32 png_get_cHRM_XYZ_fixed PNGARG( + (png_structp png_ptr, png_const_infop info_ptr, + png_fixed_point *int_red_X, png_fixed_point *int_red_Y, + png_fixed_point *int_red_Z, png_fixed_point *int_green_X, + png_fixed_point *int_green_Y, png_fixed_point *int_green_Z, + png_fixed_point *int_blue_X, png_fixed_point *int_blue_Y, + png_fixed_point *int_blue_Z)); +PNG_EXTERN void png_set_cHRM_XYZ_fixed PNGARG((png_structp png_ptr, + png_infop info_ptr, png_fixed_point int_red_X, png_fixed_point int_red_Y, + png_fixed_point int_red_Z, png_fixed_point int_green_X, + png_fixed_point int_green_Y, png_fixed_point int_green_Z, + png_fixed_point int_blue_X, png_fixed_point int_blue_Y, + png_fixed_point int_blue_Z)); +PNG_EXTERN void png_set_cHRM_fixed PNGARG((png_structp png_ptr, + png_infop info_ptr, png_fixed_point int_white_x, + png_fixed_point int_white_y, png_fixed_point int_red_x, + png_fixed_point int_red_y, png_fixed_point int_green_x, + png_fixed_point int_green_y, png_fixed_point int_blue_x, + png_fixed_point int_blue_y)); +#endif + +#ifdef PNG_gAMA_SUPPORTED +PNG_EXTERN png_uint_32 png_get_gAMA_fixed PNGARG( + (png_const_structp png_ptr, png_const_infop info_ptr, + png_fixed_point *int_file_gamma)); +PNG_EXTERN void png_set_gAMA_fixed PNGARG((png_structp png_ptr, + png_infop info_ptr, png_fixed_point int_file_gamma)); +#endif + +#ifdef PNG_READ_BACKGROUND_SUPPORTED +PNG_EXTERN void png_set_background_fixed PNGARG((png_structp png_ptr, + png_const_color_16p background_color, int background_gamma_code, + int need_expand, png_fixed_point background_gamma)); +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +PNG_EXTERN void png_set_alpha_mode_fixed PNGARG((png_structp png_ptr, + int mode, png_fixed_point output_gamma)); +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED +PNG_EXTERN void png_set_gamma_fixed PNGARG((png_structp png_ptr, + png_fixed_point screen_gamma, png_fixed_point override_file_gamma)); +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +PNG_EXTERN void png_set_rgb_to_gray_fixed PNGARG((png_structp png_ptr, + int error_action, png_fixed_point red, png_fixed_point green)); +#endif +#endif /* FIX MISSING !FIXED_POINT DECLARATIONS */ + +/* These are initialization functions for hardware specific PNG filter + * optimizations; list these here then select the appropriate one at compile + * time using the macro PNG_FILTER_OPTIMIZATIONS. If the macro is not defined + * the generic code is used. + */ +#ifdef PNG_FILTER_OPTIMIZATIONS +PNG_EXTERN void PNG_FILTER_OPTIMIZATIONS(png_structp png_ptr, unsigned int bpp); + /* Just declare the optimization that will be used */ +#else + /* List *all* the possible optimizations here - this branch is required if + * the builder of libpng passes the definition of PNG_FILTER_OPTIMIZATIONS in + * CFLAGS in place of CPPFLAGS *and* uses symbol prefixing. + */ +PNG_EXTERN void png_init_filter_functions_neon(png_structp png_ptr, + unsigned int bpp); +#endif + +/* Maintainer: Put new private prototypes here ^ */ #include "pngdebug.h" @@ -1672,4 +1757,5 @@ PNG_EXTERN void png_build_gamma_table PNGARG((png_structp png_ptr, } #endif +#endif /* PNG_VERSION_INFO_ONLY */ #endif /* PNGPRIV_H */ diff --git a/3rdparty/libpng/pngread.c b/3rdparty/libpng/pngread.c index 1d8c6b3346..b90e017e62 100644 --- a/3rdparty/libpng/pngread.c +++ b/3rdparty/libpng/pngread.c @@ -1,8 +1,8 @@ /* pngread.c - read a PNG file * - * Last changed in libpng 1.5.10 [March 8, 2012] - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.23 [July 23, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -100,7 +100,7 @@ png_create_read_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, if (!png_user_version_check(png_ptr, user_png_ver)) png_cleanup_needed = 1; - if (!png_cleanup_needed) + if (png_cleanup_needed == 0) { /* Initialize zbuf - compression buffer */ png_ptr->zbuf_size = PNG_ZBUF_SIZE; @@ -114,7 +114,7 @@ png_create_read_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, png_ptr->zstream.zfree = png_zfree; png_ptr->zstream.opaque = (voidpf)png_ptr; - if (!png_cleanup_needed) + if (png_cleanup_needed == 0) { switch (inflateInit(&png_ptr->zstream)) { @@ -141,7 +141,7 @@ png_create_read_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, } } - if (png_cleanup_needed) + if (png_cleanup_needed != 0) { /* Clean up PNG structure and deallocate any memory. */ png_free(png_ptr, png_ptr->zbuf); @@ -559,7 +559,7 @@ png_read_row(png_structp png_ptr, png_bytep row, png_bytep dsp_row) if (ret == Z_STREAM_END) { if (png_ptr->zstream.avail_out || png_ptr->zstream.avail_in || - png_ptr->idat_size) + png_ptr->idat_size) png_benign_error(png_ptr, "Extra compressed data"); png_ptr->mode |= PNG_AFTER_IDAT; png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED; @@ -617,7 +617,7 @@ png_read_row(png_structp png_ptr, png_bytep row, png_bytep dsp_row) #ifdef PNG_READ_INTERLACING_SUPPORTED /* Blow up interlaced rows to full size */ if (png_ptr->interlaced && - (png_ptr->transformations & PNG_INTERLACE)) + (png_ptr->transformations & PNG_INTERLACE)) { if (png_ptr->pass < 6) png_do_read_interlace(&row_info, png_ptr->row_buf + 1, png_ptr->pass, @@ -1119,9 +1119,8 @@ png_set_read_status_fn(png_structp png_ptr, png_read_status_ptr read_row_fn) #ifdef PNG_SEQUENTIAL_READ_SUPPORTED #ifdef PNG_INFO_IMAGE_SUPPORTED void PNGAPI -png_read_png(png_structp png_ptr, png_infop info_ptr, - int transforms, - voidp params) +png_read_png(png_structp png_ptr, png_infop info_ptr, int transforms, + voidp params) { int row; @@ -1191,7 +1190,7 @@ png_read_png(png_structp png_ptr, png_infop info_ptr, if (transforms & PNG_TRANSFORM_EXPAND) if ((png_ptr->bit_depth < 8) || (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) || - (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) + (info_ptr->valid & PNG_INFO_tRNS)) png_set_expand(png_ptr); #endif @@ -1210,14 +1209,8 @@ png_read_png(png_structp png_ptr, png_infop info_ptr, * [0,65535] to the original [0,7] or [0,31], or whatever range the * colors were originally in: */ - if ((transforms & PNG_TRANSFORM_SHIFT) - && png_get_valid(png_ptr, info_ptr, PNG_INFO_sBIT)) - { - png_color_8p sig_bit; - - png_get_sBIT(png_ptr, info_ptr, &sig_bit); - png_set_shift(png_ptr, sig_bit); - } + if ((transforms & PNG_TRANSFORM_SHIFT) && (info_ptr->valid & PNG_INFO_sBIT)) + png_set_shift(png_ptr, &info_ptr->sig_bit); #endif #ifdef PNG_READ_BGR_SUPPORTED @@ -1287,7 +1280,7 @@ png_read_png(png_structp png_ptr, png_infop info_ptr, for (row = 0; row < (int)info_ptr->height; row++) info_ptr->row_pointers[row] = (png_bytep)png_malloc(png_ptr, - png_get_rowbytes(png_ptr, info_ptr)); + png_get_rowbytes(png_ptr, info_ptr)); } png_read_image(png_ptr, info_ptr->row_pointers); diff --git a/3rdparty/libpng/pngrio.c b/3rdparty/libpng/pngrio.c index e9c381c5ba..b4042e9ed3 100644 --- a/3rdparty/libpng/pngrio.c +++ b/3rdparty/libpng/pngrio.c @@ -2,7 +2,7 @@ /* pngrio.c - functions for data input * * Last changed in libpng 1.5.0 [January 6, 2011] - * Copyright (c) 1998-2011 Glenn Randers-Pehrson + * Copyright (c) 1998-2002,2004,2006-2011 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -26,7 +26,7 @@ * reads from a file pointer. Note that this routine sometimes gets called * with very small lengths, so you should implement some kind of simple * buffering if you are using unbuffered reads. This should never be asked - * to read more then 64K on a 16 bit machine. + * to read more than 64K on a 16 bit machine. */ void /* PRIVATE */ png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) diff --git a/3rdparty/libpng/pngrtran.c b/3rdparty/libpng/pngrtran.c index 96732b55c5..f273362616 100644 --- a/3rdparty/libpng/pngrtran.c +++ b/3rdparty/libpng/pngrtran.c @@ -1,8 +1,8 @@ /* pngrtran.c - transforms the data in a row for PNG readers * - * Last changed in libpng 1.5.11 [June 14, 2012] - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.24 [November 12, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -114,7 +114,7 @@ png_set_background_fixed(png_structp png_ptr, png_sizeof(png_color_16)); png_ptr->background_gamma = background_gamma; png_ptr->background_gamma_type = (png_byte)(background_gamma_code); - if (need_expand) + if (need_expand != 0) png_ptr->transformations |= PNG_BACKGROUND_EXPAND; else png_ptr->transformations &= ~PNG_BACKGROUND_EXPAND; @@ -194,8 +194,10 @@ translate_gamma_flags(png_structp png_ptr, png_fixed_point output_gamma, */ # ifdef PNG_READ_sRGB_SUPPORTED png_ptr->flags |= PNG_FLAG_ASSUME_sRGB; +# else + PNG_UNUSED(png_ptr) # endif - if (is_screen) + if (is_screen != 0) output_gamma = PNG_GAMMA_sRGB; else output_gamma = PNG_GAMMA_sRGB_INVERSE; @@ -204,7 +206,7 @@ translate_gamma_flags(png_structp png_ptr, png_fixed_point output_gamma, else if (output_gamma == PNG_GAMMA_MAC_18 || output_gamma == PNG_FP_1 / PNG_GAMMA_MAC_18) { - if (is_screen) + if (is_screen != 0) output_gamma = PNG_GAMMA_MAC_OLD; else output_gamma = PNG_GAMMA_MAC_INVERSE; @@ -329,7 +331,7 @@ png_set_alpha_mode_fixed(png_structp png_ptr, int mode, /* Finally, if pre-multiplying, set the background fields to achieve the * desired result. */ - if (compose) + if (compose != 0) { /* And obtain alpha pre-multiplication by composing on black: */ png_memset(&png_ptr->background, 0, sizeof png_ptr->background); @@ -389,7 +391,7 @@ png_set_quantize(png_structp png_ptr, png_colorp palette, png_ptr->transformations |= PNG_QUANTIZE; - if (!full_quantize) + if (full_quantize == 0) { int i; @@ -444,12 +446,12 @@ png_set_quantize(png_structp png_ptr, png_colorp palette, } } - if (done) + if (done != 0) break; } /* Swap the palette around, and set up a table, if necessary */ - if (full_quantize) + if (full_quantize != 0) { int j = num_palette; @@ -632,7 +634,7 @@ png_set_quantize(png_structp png_ptr, png_colorp palette, num_new_palette--; palette[png_ptr->index_to_palette[j]] = palette[num_new_palette]; - if (!full_quantize) + if (full_quantize == 0) { int k; @@ -700,7 +702,7 @@ png_set_quantize(png_structp png_ptr, png_colorp palette, } png_ptr->num_palette = (png_uint_16)num_palette; - if (full_quantize) + if (full_quantize != 0) { int i; png_bytep distance; @@ -969,7 +971,7 @@ png_set_rgb_to_gray_fixed(png_structp png_ptr, int error_action, png_uint_16 red_int, green_int; /* NOTE: this calculation does not round, but this behavior is retained - * for consistency, the inaccuracy is very small. The code here always + * for consistency; the inaccuracy is very small. The code here always * overwrites the coefficients, regardless of whether they have been * defaulted or set already. */ @@ -1068,7 +1070,7 @@ png_gamma_threshold(png_fixed_point screen_gamma, png_fixed_point file_gamma) * the palette. */ -/*For the moment 'png_init_palette_transformations' and +/* For the moment 'png_init_palette_transformations' and * 'png_init_rgb_transformations' only do some flag canceling optimizations. * The intent is that these two routines should have palette or rgb operations * extracted from 'png_init_read_transformations'. @@ -1093,25 +1095,31 @@ png_init_palette_transformations(png_structp png_ptr) /* Ignore if all the entries are opaque (unlikely!) */ for (i=0; inum_trans; ++i) + { if (png_ptr->trans_alpha[i] == 255) continue; else if (png_ptr->trans_alpha[i] == 0) input_has_transparency = 1; else + { + input_has_transparency = 1; input_has_alpha = 1; + break; + } + } } /* If no alpha we can optimize. */ - if (!input_has_alpha) + if (input_has_alpha == 0) { /* Any alpha means background and associative alpha processing is - * required, however if the alpha is 0 or 1 throughout OPTIIMIZE_ALPHA + * required, however if the alpha is 0 or 1 throughout OPTIMIZE_ALPHA * and ENCODE_ALPHA are irrelevant. */ png_ptr->transformations &= ~PNG_ENCODE_ALPHA; png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; - if (!input_has_transparency) + if (input_has_transparency == 0) png_ptr->transformations &= ~(PNG_COMPOSE | PNG_BACKGROUND_EXPAND); } @@ -1167,10 +1175,10 @@ png_init_rgb_transformations(png_structp png_ptr) int input_has_transparency = png_ptr->num_trans > 0; /* If no alpha we can optimize. */ - if (!input_has_alpha) + if (input_has_alpha == 0) { /* Any alpha means background and associative alpha processing is - * required, however if the alpha is 0 or 1 throughout OPTIIMIZE_ALPHA + * required, however if the alpha is 0 or 1 throughout OPTIMIZE_ALPHA * and ENCODE_ALPHA are irrelevant. */ # ifdef PNG_READ_ALPHA_MODE_SUPPORTED @@ -1178,7 +1186,7 @@ png_init_rgb_transformations(png_structp png_ptr) png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; # endif - if (!input_has_transparency) + if (input_has_transparency == 0) png_ptr->transformations &= ~(PNG_COMPOSE | PNG_BACKGROUND_EXPAND); } @@ -1221,7 +1229,7 @@ png_init_rgb_transformations(png_structp png_ptr) default: case 8: - /* Already 8 bits, fall through */ + /* FALL THROUGH (already 8 bits) */ case 16: /* Already a full 16 bits */ @@ -1304,7 +1312,7 @@ png_init_read_transformations(png_structp png_ptr) * the code immediately below if the transform can be handled outside the * row loop. */ - if (gamma_correction) + if (gamma_correction != 0) png_ptr->transformations |= PNG_GAMMA; else @@ -1313,7 +1321,7 @@ png_init_read_transformations(png_structp png_ptr) #endif /* Certain transformations have the effect of preventing other - * transformations that happen afterward in png_do_read_transformations, + * transformations that happen afterward in png_do_read_transformations; * resolve the interdependencies here. From the code of * png_do_read_transformations the order is: * @@ -1702,11 +1710,11 @@ png_init_read_transformations(png_structp png_ptr) g_sig = png_gamma_significant(g); gs_sig = png_gamma_significant(gs); - if (g_sig) + if (g_sig != 0) png_ptr->background_1.gray = png_gamma_correct(png_ptr, png_ptr->background.gray, g); - if (gs_sig) + if (gs_sig != 0) png_ptr->background.gray = png_gamma_correct(png_ptr, png_ptr->background.gray, gs); @@ -1715,7 +1723,7 @@ png_init_read_transformations(png_structp png_ptr) (png_ptr->background.red != png_ptr->background.gray)) { /* RGB or RGBA with color background */ - if (g_sig) + if (g_sig != 0) { png_ptr->background_1.red = png_gamma_correct(png_ptr, png_ptr->background.red, g); @@ -1727,7 +1735,7 @@ png_init_read_transformations(png_structp png_ptr) png_ptr->background.blue, g); } - if (gs_sig) + if (gs_sig != 0) { png_ptr->background.red = png_gamma_correct(png_ptr, png_ptr->background.red, gs); @@ -1898,6 +1906,9 @@ png_read_transform_info(png_structp png_ptr, png_infop info_ptr) info_ptr->bit_depth = 8; info_ptr->num_trans = 0; + + if (png_ptr->palette == NULL) + png_error (png_ptr, "Palette is NULL in indexed image"); } else { @@ -2045,10 +2056,10 @@ png_read_transform_info(png_structp png_ptr, png_infop info_ptr) defined(PNG_READ_USER_TRANSFORM_SUPPORTED) if (png_ptr->transformations & PNG_USER_TRANSFORM) { - if (info_ptr->bit_depth < png_ptr->user_transform_depth) + if (png_ptr->user_transform_depth) info_ptr->bit_depth = png_ptr->user_transform_depth; - if (info_ptr->channels < png_ptr->user_transform_channels) + if (png_ptr->user_transform_channels) info_ptr->channels = png_ptr->user_transform_channels; } #endif @@ -2067,7 +2078,7 @@ defined(PNG_READ_USER_TRANSFORM_SUPPORTED) png_ptr->info_rowbytes = info_ptr->rowbytes; #ifndef PNG_READ_EXPAND_SUPPORTED - if (png_ptr) + if (png_ptr != NULL) return; #endif } @@ -2144,7 +2155,7 @@ png_do_read_transformations(png_structp png_ptr, png_row_infop row_info) png_do_rgb_to_gray(png_ptr, row_info, png_ptr->row_buf + 1); - if (rgb_error) + if (rgb_error != 0) { png_ptr->rgb_to_gray_status=1; if ((png_ptr->transformations & PNG_RGB_TO_GRAY) == @@ -2198,8 +2209,8 @@ png_do_read_transformations(png_structp png_ptr, png_row_infop row_info) png_do_gray_to_rgb(row_info, png_ptr->row_buf + 1); #endif -#if (defined PNG_READ_BACKGROUND_SUPPORTED) ||\ - (defined PNG_READ_ALPHA_MODE_SUPPORTED) +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) if (png_ptr->transformations & PNG_COMPOSE) png_do_compose(row_info, png_ptr->row_buf + 1, png_ptr); #endif @@ -2210,8 +2221,8 @@ png_do_read_transformations(png_structp png_ptr, png_row_infop row_info) /* Because RGB_TO_GRAY does the gamma transform. */ !(png_ptr->transformations & PNG_RGB_TO_GRAY) && #endif -#if (defined PNG_READ_BACKGROUND_SUPPORTED) ||\ - (defined PNG_READ_ALPHA_MODE_SUPPORTED) +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) /* Because PNG_COMPOSE does the gamma transform if there is something to * do (if there is an alpha channel or transparency.) */ @@ -2522,7 +2533,7 @@ png_do_unshift(png_row_infop row_info, png_bytep row, have_shift = 1; } - if (!have_shift) + if (have_shift == 0) return; } @@ -2958,13 +2969,13 @@ png_do_read_filler(png_row_infop row_info, png_bytep row, png_bytep dp = sp + (png_size_t)row_width * 2; for (i = 1; i < row_width; i++) { - *(--dp) = hi_filler; *(--dp) = lo_filler; + *(--dp) = hi_filler; *(--dp) = *(--sp); *(--dp) = *(--sp); } - *(--dp) = hi_filler; *(--dp) = lo_filler; + *(--dp) = hi_filler; row_info->channels = 2; row_info->pixel_depth = 32; row_info->rowbytes = row_width * 4; @@ -2979,8 +2990,8 @@ png_do_read_filler(png_row_infop row_info, png_bytep row, { *(--dp) = *(--sp); *(--dp) = *(--sp); - *(--dp) = hi_filler; *(--dp) = lo_filler; + *(--dp) = hi_filler; } row_info->channels = 2; row_info->pixel_depth = 32; @@ -3039,8 +3050,8 @@ png_do_read_filler(png_row_infop row_info, png_bytep row, png_bytep dp = sp + (png_size_t)row_width * 2; for (i = 1; i < row_width; i++) { - *(--dp) = hi_filler; *(--dp) = lo_filler; + *(--dp) = hi_filler; *(--dp) = *(--sp); *(--dp) = *(--sp); *(--dp) = *(--sp); @@ -3048,8 +3059,8 @@ png_do_read_filler(png_row_infop row_info, png_bytep row, *(--dp) = *(--sp); *(--dp) = *(--sp); } - *(--dp) = hi_filler; *(--dp) = lo_filler; + *(--dp) = hi_filler; row_info->channels = 4; row_info->pixel_depth = 64; row_info->rowbytes = row_width * 8; @@ -3068,8 +3079,8 @@ png_do_read_filler(png_row_infop row_info, png_bytep row, *(--dp) = *(--sp); *(--dp) = *(--sp); *(--dp) = *(--sp); - *(--dp) = hi_filler; *(--dp) = lo_filler; + *(--dp) = hi_filler; } row_info->channels = 4; @@ -3283,7 +3294,7 @@ png_do_rgb_to_gray(png_structp png_ptr, png_row_infop row_info, png_bytep row) *(dp++) = red; } - if (have_alpha) + if (have_alpha != 0) *(dp++) = *(sp++); } } @@ -3312,7 +3323,7 @@ png_do_rgb_to_gray(png_structp png_ptr, png_row_infop row_info, png_bytep row) else *(dp++) = red; - if (have_alpha) + if (have_alpha != 0) *(dp++) = *(sp++); } } @@ -3330,11 +3341,17 @@ png_do_rgb_to_gray(png_structp png_ptr, png_row_infop row_info, png_bytep row) for (i = 0; i < row_width; i++) { png_uint_16 red, green, blue, w; - +#if 0 /* Coverity doesn't like this */ red = (png_uint_16)(((*(sp))<<8) | *(sp + 1)); sp += 2; green = (png_uint_16)(((*(sp))<<8) | *(sp + 1)); sp += 2; blue = (png_uint_16)(((*(sp))<<8) | *(sp + 1)); sp += 2; +#else + png_byte hi,lo; + hi=*(sp)++; lo=*(sp)++; red = (png_uint_16)((hi << 8) | (lo)); + hi=*(sp)++; lo=*(sp)++; green = (png_uint_16)((hi << 8) | (lo)); + hi=*(sp)++; lo=*(sp)++; blue = (png_uint_16)((hi << 8) | (lo)); +#endif if (red == green && red == blue) { if (png_ptr->gamma_16_table != NULL) @@ -3364,7 +3381,7 @@ png_do_rgb_to_gray(png_structp png_ptr, png_row_infop row_info, png_bytep row) *(dp++) = (png_byte)((w>>8) & 0xff); *(dp++) = (png_byte)(w & 0xff); - if (have_alpha) + if (have_alpha != 0) { *(dp++) = *(sp++); *(dp++) = *(sp++); @@ -3398,7 +3415,7 @@ png_do_rgb_to_gray(png_structp png_ptr, png_row_infop row_info, png_bytep row) *(dp++) = (png_byte)((gray16>>8) & 0xff); *(dp++) = (png_byte)(gray16 & 0xff); - if (have_alpha) + if (have_alpha != 0) { *(dp++) = *(sp++); *(dp++) = *(sp++); @@ -3477,8 +3494,8 @@ png_build_grayscale_palette(int bit_depth, png_colorp palette) #ifdef PNG_READ_TRANSFORMS_SUPPORTED -#if (defined PNG_READ_BACKGROUND_SUPPORTED) ||\ - (defined PNG_READ_ALPHA_MODE_SUPPORTED) +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) /* Replace any alpha or transparency with the supplied background color. * "background" is already in the screen gamma, while "background_1" is * at a gamma of 1.0. Paletted files have already been taken care of. @@ -3524,7 +3541,7 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) *sp |= (png_byte)(png_ptr->background.gray << shift); } - if (!shift) + if (shift == 0) { shift = 7; sp++; @@ -3561,7 +3578,7 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) *sp |= (png_byte)(g << shift); } - if (!shift) + if (shift == 0) { shift = 6; sp++; @@ -3586,7 +3603,7 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) *sp |= (png_byte)(png_ptr->background.gray << shift); } - if (!shift) + if (shift == 0) { shift = 6; sp++; @@ -3624,7 +3641,7 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) *sp |= (png_byte)(g << shift); } - if (!shift) + if (shift == 0) { shift = 4; sp++; @@ -3649,7 +3666,7 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) *sp |= (png_byte)(png_ptr->background.gray << shift); } - if (!shift) + if (shift == 0) { shift = 4; sp++; @@ -3705,8 +3722,10 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) if (v == png_ptr->trans_color.gray) { /* Background is already in screen gamma */ - *sp = (png_byte)((png_ptr->background.gray >> 8) & 0xff); - *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray + & 0xff); } else @@ -3729,8 +3748,10 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) if (v == png_ptr->trans_color.gray) { - *sp = (png_byte)((png_ptr->background.gray >> 8) & 0xff); - *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray + & 0xff); } } } @@ -3810,9 +3831,12 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) /* Background is already in screen gamma */ *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); - *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) & 0xff); - *(sp + 3) = (png_byte)(png_ptr->background.green & 0xff); - *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); } @@ -3853,9 +3877,12 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) { *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); - *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) & 0xff); - *(sp + 3) = (png_byte)(png_ptr->background.green & 0xff); - *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); } } @@ -3892,7 +3919,7 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) v = gamma_to_1[*sp]; png_composite(w, v, a, png_ptr->background_1.gray); - if (!optimize) + if (optimize == 0) w = gamma_from_1[w]; *sp = w; } @@ -3910,7 +3937,7 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) *sp = (png_byte)png_ptr->background.gray; else if (a < 0xff) - png_composite(*sp, *sp, a, png_ptr->background_1.gray); + png_composite(*sp, *sp, a, png_ptr->background.gray); } } } @@ -3938,7 +3965,8 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) else if (a == 0) { /* Background is already in screen gamma */ - *sp = (png_byte)((png_ptr->background.gray >> 8) & 0xff); + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); } @@ -3948,7 +3976,7 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) g = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp]; png_composite_16(v, g, a, png_ptr->background_1.gray); - if (optimize) + if (optimize != 0) w = v; else w = gamma_16_from_1[(v&0xff) >> gamma_shift][v >> 8]; @@ -3968,7 +3996,8 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) if (a == 0) { - *sp = (png_byte)((png_ptr->background.gray >> 8) & 0xff); + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); } @@ -3977,7 +4006,7 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) png_uint_16 g, v; g = (png_uint_16)(((*sp) << 8) + *(sp + 1)); - png_composite_16(v, g, a, png_ptr->background_1.gray); + png_composite_16(v, g, a, png_ptr->background.gray); *sp = (png_byte)((v >> 8) & 0xff); *(sp + 1) = (png_byte)(v & 0xff); } @@ -4021,17 +4050,17 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) v = gamma_to_1[*sp]; png_composite(w, v, a, png_ptr->background_1.red); - if (!optimize) w = gamma_from_1[w]; + if (optimize == 0) w = gamma_from_1[w]; *sp = w; v = gamma_to_1[*(sp + 1)]; png_composite(w, v, a, png_ptr->background_1.green); - if (!optimize) w = gamma_from_1[w]; + if (optimize == 0) w = gamma_from_1[w]; *(sp + 1) = w; v = gamma_to_1[*(sp + 2)]; png_composite(w, v, a, png_ptr->background_1.blue); - if (!optimize) w = gamma_from_1[w]; + if (optimize == 0) w = gamma_from_1[w]; *(sp + 2) = w; } } @@ -4098,9 +4127,12 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) /* Background is already in screen gamma */ *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); - *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) & 0xff); - *(sp + 3) = (png_byte)(png_ptr->background.green & 0xff); - *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); } @@ -4110,23 +4142,26 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) v = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp]; png_composite_16(w, v, a, png_ptr->background_1.red); - if (!optimize) - w = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8]; + if (optimize == 0) + w = gamma_16_from_1[((w&0xff) >> gamma_shift)] + [w >> 8]; *sp = (png_byte)((w >> 8) & 0xff); *(sp + 1) = (png_byte)(w & 0xff); v = gamma_16_to_1[*(sp + 3) >> gamma_shift][*(sp + 2)]; png_composite_16(w, v, a, png_ptr->background_1.green); - if (!optimize) - w = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8]; + if (optimize == 0) + w = gamma_16_from_1[((w&0xff) >> gamma_shift)] + [w >> 8]; *(sp + 2) = (png_byte)((w >> 8) & 0xff); *(sp + 3) = (png_byte)(w & 0xff); v = gamma_16_to_1[*(sp + 5) >> gamma_shift][*(sp + 4)]; png_composite_16(w, v, a, png_ptr->background_1.blue); - if (!optimize) - w = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8]; + if (optimize == 0) + w = gamma_16_from_1[((w&0xff) >> gamma_shift)] + [w >> 8]; *(sp + 4) = (png_byte)((w >> 8) & 0xff); *(sp + 5) = (png_byte)(w & 0xff); @@ -4147,9 +4182,12 @@ png_do_compose(png_row_infop row_info, png_bytep row, png_structp png_ptr) { *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); - *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) & 0xff); - *(sp + 3) = (png_byte)(png_ptr->background.green & 0xff); - *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); } @@ -4725,7 +4763,9 @@ png_do_expand(png_row_infop row_info, png_bytep row, { if (row_info->bit_depth == 8) { - gray = gray & 0xff; + /* NOTE: prior to libpng 1.5.14 this cleared out the top bits of + * 'gray', however if those are set it is an error. + */ sp = row + (png_size_t)row_width - 1; dp = row + (png_size_t)(row_width << 1) - 1; diff --git a/3rdparty/libpng/pngrutil.c b/3rdparty/libpng/pngrutil.c index d8fe54cc62..6061651fef 100644 --- a/3rdparty/libpng/pngrutil.c +++ b/3rdparty/libpng/pngrutil.c @@ -1,8 +1,8 @@ /* pngrutil.c - utilities to read a PNG file * - * Last changed in libpng 1.5.10 [March 8, 2012] - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.25 [December 3, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -18,8 +18,6 @@ #ifdef PNG_READ_SUPPORTED -#define png_strtod(p,a,b) strtod(a,b) - png_uint_32 PNGAPI png_get_uint_31(png_structp png_ptr, png_const_bytep buf) { @@ -91,7 +89,13 @@ png_get_int_32)(png_const_bytep buf) return uval; uval = (uval ^ 0xffffffff) + 1; /* 2's complement: -x = ~x+1 */ - return -(png_int_32)uval; + if ((uval & 0x80000000) == 0) /* no overflow */ + return -(png_int_32)uval; + /* The following has to be safe; this function only gets called on PNG data + * and if we get here that data is invalid. 0 is the most safe value and + * if not then an attacker would surely just generate a PNG with 0 instead. + */ + return 0; } /* Grab an unsigned 16-bit integer from a buffer in big-endian format. */ @@ -211,7 +215,7 @@ png_crc_finish(png_structp png_ptr, png_uint_32 skip) png_crc_read(png_ptr, png_ptr->zbuf, png_ptr->zbuf_size); } - if (i) + if (i != 0) { png_crc_read(png_ptr, png_ptr->zbuf, i); } @@ -267,7 +271,7 @@ png_crc_error(png_structp png_ptr) /* The chunk CRC must be serialized in a single I/O call. */ png_read_data(png_ptr, crc_bytes, 4); - if (need_crc) + if (need_crc != 0) { crc = png_get_uint_32(crc_bytes); return ((int)(crc != png_ptr->crc)); @@ -284,6 +288,17 @@ png_inflate(png_structp png_ptr, png_bytep data, png_size_t size, { png_size_t count = 0; + /* HACK: added in libpng 1.5.18: the progressive reader always leaves + * png_ptr->zstream in a non-reset state. This causes a reset if it needs to + * be used again. This only copes with that one specific error; see libpng + * 1.6 for a better solution. + */ + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_PROGRESSIVE) != 0) + { + (void)inflateReset(&png_ptr->zstream); + png_ptr->flags &= ~PNG_FLAG_ZSTREAM_PROGRESSIVE; + } + /* zlib can't necessarily handle more than 65535 bytes at once (i.e. it can't * even necessarily handle 65536 bytes) because the type uInt is "16 bits or * more". Consequently it is necessary to chunk the input to zlib. This @@ -596,7 +611,7 @@ void /* PRIVATE */ png_handle_PLTE(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) { png_color palette[PNG_MAX_PALETTE_LENGTH]; - int num, i; + int max_palette_length, num, i; #ifdef PNG_POINTER_INDEXING_SUPPORTED png_colorp pal_ptr; #endif @@ -649,8 +664,22 @@ png_handle_PLTE(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) } } + /* The cast is safe because 'length' is less than 3*PNG_MAX_PALETTE_LENGTH */ num = (int)length / 3; + /* If the palette has 256 or fewer entries but is too large for the bit + * depth, we don't issue an error, to preserve the behavior of previous + * libpng versions. We silently truncate the unused extra palette entries + * here. + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + max_palette_length = (1 << png_ptr->bit_depth); + else + max_palette_length = PNG_MAX_PALETTE_LENGTH; + + if (num > max_palette_length) + num = max_palette_length; + #ifdef PNG_POINTER_INDEXING_SUPPORTED for (i = 0, pal_ptr = palette; i < num; i++, pal_ptr++) { @@ -683,7 +712,7 @@ png_handle_PLTE(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) #endif { - png_crc_finish(png_ptr, 0); + png_crc_finish(png_ptr, (int) length - num * 3); } #ifndef PNG_READ_OPT_PLTE_SUPPORTED @@ -1298,7 +1327,7 @@ png_handle_iCCP(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) /* There should be at least one zero (the compression type byte) * following the separator, and we should be on it */ - if (profile >= png_ptr->chunkdata + slength - 1) + if (slength < 1U || profile >= png_ptr->chunkdata + slength - 1U) { png_free(png_ptr, png_ptr->chunkdata); png_ptr->chunkdata = NULL; @@ -1309,7 +1338,7 @@ png_handle_iCCP(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) /* Compression_type should always be zero */ compression_type = *profile++; - if (compression_type) + if (compression_type != 0) { png_warning(png_ptr, "Ignoring nonzero compression type in iCCP chunk"); compression_type = 0x00; /* Reset it to zero (libpng-1.0.6 through 1.0.8 @@ -1447,7 +1476,8 @@ png_handle_sPLT(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) ++entry_start; /* A sample depth should follow the separator, and we should be on it */ - if (entry_start > (png_bytep)png_ptr->chunkdata + slength - 2) + if (slength < 2U || + entry_start > (png_bytep)png_ptr->chunkdata + slength - 2U) { png_free(png_ptr, png_ptr->chunkdata); png_ptr->chunkdata = NULL; @@ -1976,7 +2006,7 @@ png_handle_pCAL(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) /* We need to have at least 12 bytes after the purpose string * in order to get the parameter information. */ - if (endptr <= buf + 12) + if (endptr - buf <= 12) { png_warning(png_ptr, "Invalid pCAL data"); png_free(png_ptr, png_ptr->chunkdata); @@ -2309,7 +2339,7 @@ png_handle_tEXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) png_ptr->chunkdata = NULL; png_free(png_ptr, text_ptr); - if (ret) + if (ret != 0) png_warning(png_ptr, "Insufficient memory to process text chunk"); } #endif @@ -2388,7 +2418,7 @@ png_handle_zTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) /* Empty loop */ ; /* zTXt must have some text after the chunkdataword */ - if (text >= png_ptr->chunkdata + slength - 2) + if (slength < 2U || text >= png_ptr->chunkdata + slength - 2U) { png_warning(png_ptr, "Truncated zTXt chunk"); png_free(png_ptr, png_ptr->chunkdata); @@ -2439,7 +2469,7 @@ png_handle_zTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) png_free(png_ptr, png_ptr->chunkdata); png_ptr->chunkdata = NULL; - if (ret) + if (ret != 0) png_error(png_ptr, "Insufficient memory to store zTXt chunk"); } #endif @@ -2452,7 +2482,7 @@ png_handle_iTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) png_textp text_ptr; png_charp key, lang, text, lang_key; int comp_flag; - int comp_type = 0; + int comp_type; int ret; png_size_t slength, prefix_len, data_len; @@ -2525,7 +2555,7 @@ png_handle_iTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) * keyword */ - if (lang >= png_ptr->chunkdata + slength - 3) + if (slength < 3U || lang >= png_ptr->chunkdata + slength - 3U) { png_warning(png_ptr, "Truncated iTXt chunk"); png_free(png_ptr, png_ptr->chunkdata); @@ -2533,18 +2563,30 @@ png_handle_iTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) return; } - else - { - comp_flag = *lang++; - comp_type = *lang++; - } + comp_flag = *lang++; + comp_type = *lang++; - if (comp_type || (comp_flag && comp_flag != PNG_TEXT_COMPRESSION_zTXt)) + /* 1.5.14: The spec says "for uncompressed text decoders shall ignore [the + * compression type]". The compression flag shall be 0 (no compression) or + * 1 (compressed with method 0 - deflate.) + */ + if (comp_flag/*compressed*/ != 0) { - png_warning(png_ptr, "Unknown iTXt compression type or method"); - png_free(png_ptr, png_ptr->chunkdata); - png_ptr->chunkdata = NULL; - return; + if (comp_flag != 1) + { + png_warning(png_ptr, "invalid iTXt compression flag"); + png_free(png_ptr, png_ptr->chunkdata); + png_ptr->chunkdata = NULL; + return; + } + + if (comp_type != 0) + { + png_warning(png_ptr, "unknown iTXt compression type"); + png_free(png_ptr, png_ptr->chunkdata); + png_ptr->chunkdata = NULL; + return; + } } for (lang_key = lang; *lang_key; lang_key++) @@ -2577,7 +2619,7 @@ png_handle_iTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) key=png_ptr->chunkdata; - if (comp_flag) + if (comp_flag/*compressed*/) png_decompress_chunk(png_ptr, comp_type, (size_t)length, prefix_len, &data_len); @@ -2595,7 +2637,8 @@ png_handle_iTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) return; } - text_ptr->compression = (int)comp_flag + 1; + text_ptr->compression = + (comp_flag ? PNG_ITXT_COMPRESSION_zTXt : PNG_ITXT_COMPRESSION_NONE); text_ptr->lang_key = png_ptr->chunkdata + (lang_key - key); text_ptr->lang = png_ptr->chunkdata + (lang - key); text_ptr->itxt_length = data_len; @@ -2609,7 +2652,7 @@ png_handle_iTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) png_free(png_ptr, png_ptr->chunkdata); png_ptr->chunkdata = NULL; - if (ret) + if (ret != 0) png_error(png_ptr, "Insufficient memory to store iTXt chunk"); } #endif @@ -2786,7 +2829,7 @@ png_combine_row(png_structp png_ptr, png_bytep dp, int display) { unsigned int pixel_depth = png_ptr->transformed_pixel_depth; png_const_bytep sp = png_ptr->row_buf + 1; - png_uint_32 row_width = png_ptr->width; + png_alloc_size_t row_width = png_ptr->width; unsigned int pass = png_ptr->pass; png_bytep end_ptr = 0; png_byte end_byte = 0; @@ -2938,7 +2981,7 @@ png_combine_row(png_structp png_ptr, png_bytep dp, int display) # define S_MASKS(d,s) { S_MASK(0,d,s), S_MASK(1,d,s), S_MASK(2,d,s),\ S_MASK(3,d,s), S_MASK(4,d,s), S_MASK(5,d,s) } -# define B_MASKS(d,s) { B_MASK(1,d,s), S_MASK(3,d,s), S_MASK(5,d,s) } +# define B_MASKS(d,s) { B_MASK(1,d,s), B_MASK(3,d,s), B_MASK(5,d,s) } # define DEPTH_INDEX(d) ((d)==1?0:((d)==2?1:2)) @@ -3049,7 +3092,7 @@ png_combine_row(png_structp png_ptr, png_bytep dp, int display) } /* Work out the bytes to copy. */ - if (display) + if (display != 0) { /* When doing the 'block' algorithm the pixel in the pass gets * replicated to adjacent pixels. This is why the even (0,2,4,6) @@ -3059,7 +3102,7 @@ png_combine_row(png_structp png_ptr, png_bytep dp, int display) /* But don't allow this number to exceed the actual row width. */ if (bytes_to_copy > row_width) - bytes_to_copy = row_width; + bytes_to_copy = (unsigned int)/*SAFE*/row_width; } else /* normal row; Adam7 only ever gives us one pixel to copy. */ @@ -3151,7 +3194,7 @@ png_combine_row(png_structp png_ptr, png_bytep dp, int display) { png_uint_32p dp32 = (png_uint_32p)dp; png_const_uint_32p sp32 = (png_const_uint_32p)sp; - unsigned int skip = (bytes_to_jump-bytes_to_copy) / + size_t skip = (bytes_to_jump-bytes_to_copy) / sizeof (png_uint_32); do @@ -3192,7 +3235,7 @@ png_combine_row(png_structp png_ptr, png_bytep dp, int display) { png_uint_16p dp16 = (png_uint_16p)dp; png_const_uint_16p sp16 = (png_const_uint_16p)sp; - unsigned int skip = (bytes_to_jump-bytes_to_copy) / + size_t skip = (bytes_to_jump-bytes_to_copy) / sizeof (png_uint_16); do @@ -3237,7 +3280,7 @@ png_combine_row(png_structp png_ptr, png_bytep dp, int display) dp += bytes_to_jump; row_width -= bytes_to_jump; if (bytes_to_copy > row_width) - bytes_to_copy = row_width; + bytes_to_copy = (unsigned int)/*SAFE*/row_width; } } @@ -3476,7 +3519,7 @@ png_do_read_interlace(png_row_infop row_info, png_bytep row, int pass, for (i = 0; i < row_info->width; i++) { - png_byte v[8]; + png_byte v[8]; /* SAFE; pixel_depth does not exceed 64 */ int j; png_memcpy(v, sp, pixel_bytes); @@ -3660,66 +3703,6 @@ png_read_filter_row_paeth_multibyte_pixel(png_row_infop row_info, png_bytep row, } } -#ifdef PNG_ARM_NEON - -#if defined __linux__ && !defined __ANDROID__ -#include -#include -#include - -static int png_have_hwcap(unsigned cap) -{ - FILE *f = fopen("/proc/self/auxv", "r"); - Elf32_auxv_t aux; - int have_cap = 0; - - if (!f) - return 0; - - while (fread(&aux, sizeof(aux), 1, f) > 0) - { - if (aux.a_type == AT_HWCAP && - aux.a_un.a_val & cap) - { - have_cap = 1; - break; - } - } - - fclose(f); - - return have_cap; -} -#endif /* __linux__ */ - -static void -png_init_filter_functions_neon(png_structp pp, unsigned int bpp) -{ -#if defined __linux__ && !defined __ANDROID__ - if (!png_have_hwcap(HWCAP_NEON)) - return; -#endif - - pp->read_filter[PNG_FILTER_VALUE_UP-1] = png_read_filter_row_up_neon; - - if (bpp == 3) - { - pp->read_filter[PNG_FILTER_VALUE_SUB-1] = png_read_filter_row_sub3_neon; - pp->read_filter[PNG_FILTER_VALUE_AVG-1] = png_read_filter_row_avg3_neon; - pp->read_filter[PNG_FILTER_VALUE_PAETH-1] = - png_read_filter_row_paeth3_neon; - } - - else if (bpp == 4) - { - pp->read_filter[PNG_FILTER_VALUE_SUB-1] = png_read_filter_row_sub4_neon; - pp->read_filter[PNG_FILTER_VALUE_AVG-1] = png_read_filter_row_avg4_neon; - pp->read_filter[PNG_FILTER_VALUE_PAETH-1] = - png_read_filter_row_paeth4_neon; - } -} -#endif /* PNG_ARM_NEON */ - static void png_init_filter_functions(png_structp pp) { @@ -3735,8 +3718,16 @@ png_init_filter_functions(png_structp pp) pp->read_filter[PNG_FILTER_VALUE_PAETH-1] = png_read_filter_row_paeth_multibyte_pixel; -#ifdef PNG_ARM_NEON - png_init_filter_functions_neon(pp, bpp); +#ifdef PNG_FILTER_OPTIMIZATIONS + /* To use this define PNG_FILTER_OPTIMIZATIONS as the name of a function to + * call to install hardware optimizations for the above functions; simply + * replace whatever elements of the pp->read_filter[] array with a hardware + * specific (or, for that matter, generic) optimization. + * + * To see an example of this examine what configure.ac does when + * --enable-arm-neon is specified on the command line. + */ + PNG_FILTER_OPTIMIZATIONS(pp, bpp); #endif } @@ -3744,10 +3735,13 @@ void /* PRIVATE */ png_read_filter_row(png_structp pp, png_row_infop row_info, png_bytep row, png_const_bytep prev_row, int filter) { - if (pp->read_filter[0] == NULL) - png_init_filter_functions(pp); if (filter > PNG_FILTER_VALUE_NONE && filter < PNG_FILTER_VALUE_LAST) + { + if (pp->read_filter[0] == NULL) + png_init_filter_functions(pp); + pp->read_filter[filter-1](row_info, row, prev_row); + } } #ifdef PNG_SEQUENTIAL_READ_SUPPORTED diff --git a/3rdparty/libpng/pngset.c b/3rdparty/libpng/pngset.c index 8c07eec3e8..61be6bc62e 100644 --- a/3rdparty/libpng/pngset.c +++ b/3rdparty/libpng/pngset.c @@ -1,8 +1,8 @@ /* pngset.c - storage of image information into info struct * - * Last changed in libpng 1.5.11 [June 14, 2012] - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.26 [December 17, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -20,6 +20,60 @@ #if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +#if defined(PNG_TEXT_SUPPORTED) || defined(PNG_pCAL_SUPPORTED) ||\ + defined(PNG_iCCP_SUPPORTED) || defined(PNG_sPLT_SUPPORTED) +/* Check that the tEXt or zTXt keyword is valid per PNG 1.0 specification, + * and if invalid, correct the keyword rather than discarding the entire + * chunk. The PNG 1.0 specification requires keywords 1-79 characters in + * length, forbids leading or trailing whitespace, multiple internal spaces, + * and the non-break space (0x80) from ISO 8859-1. Returns keyword length. + * + * The 'new_key' buffer must be 80 characters in size (for the keyword plus a + * trailing '\0'). If this routine returns 0 then there was no keyword, or a + * valid one could not be generated, and the caller must handle the error by not + * setting the keyword. + */ +static png_uint_32 +png_check_keyword(png_const_charp key, png_bytep new_key) +{ + png_uint_32 key_len = 0; + int space = 1; + + if (key == NULL) + { + *new_key = 0; + return 0; + } + + while (*key && key_len < 79) + { + png_byte ch = (png_byte)*key++; + + if ((ch > 32 && ch <= 126) || (ch >= 161 /*&& ch <= 255*/)) + *new_key++ = ch, ++key_len, space = 0; + + else if (space == 0) + { + /* A space or an invalid character when one wasn't seen immediately + * before; output just a space. + */ + *new_key++ = 32, ++key_len, space = 1; + } + } + + if (key_len > 0 && space != 0) /* trailing space */ + --key_len, --new_key; + + /* Terminate the keyword */ + *new_key = 0; + + if (key_len == 0) + return 0; + + return key_len; +} +#endif /* TEXT || pCAL || iCCP || sPLT */ + #ifdef PNG_bKGD_SUPPORTED void PNGAPI png_set_bKGD(png_structp png_ptr, png_infop info_ptr, @@ -123,12 +177,12 @@ png_set_cHRM_XYZ(png_structp png_ptr, png_infop info_ptr, double red_X, png_fixed(png_ptr, red_X, "cHRM Red X"), png_fixed(png_ptr, red_Y, "cHRM Red Y"), png_fixed(png_ptr, red_Z, "cHRM Red Z"), - png_fixed(png_ptr, green_X, "cHRM Red X"), - png_fixed(png_ptr, green_Y, "cHRM Red Y"), - png_fixed(png_ptr, green_Z, "cHRM Red Z"), - png_fixed(png_ptr, blue_X, "cHRM Red X"), - png_fixed(png_ptr, blue_Y, "cHRM Red Y"), - png_fixed(png_ptr, blue_Z, "cHRM Red Z")); + png_fixed(png_ptr, green_X, "cHRM Green X"), + png_fixed(png_ptr, green_Y, "cHRM Green Y"), + png_fixed(png_ptr, green_Z, "cHRM Green Z"), + png_fixed(png_ptr, blue_X, "cHRM Blue X"), + png_fixed(png_ptr, blue_Y, "cHRM Blue Y"), + png_fixed(png_ptr, blue_Z, "cHRM Blue Z")); } # endif /* PNG_FLOATING_POINT_SUPPORTED */ @@ -252,16 +306,7 @@ png_set_IHDR(png_structp png_ptr, png_infop info_ptr, info_ptr->pixel_depth = (png_byte)(info_ptr->channels * info_ptr->bit_depth); - /* Check for potential overflow */ - if (width > - (PNG_UINT_32_MAX >> 3) /* 8-byte RRGGBBAA pixels */ - - 48 /* bigrowbuf hack */ - - 1 /* filter byte */ - - 7*8 /* rounding of width to multiple of 8 pixels */ - - 8) /* extra max_pixel_depth pad */ - info_ptr->rowbytes = 0; - else - info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, width); + info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, width); } #ifdef PNG_oFFs_SUPPORTED @@ -287,6 +332,7 @@ png_set_pCAL(png_structp png_ptr, png_infop info_ptr, png_const_charp purpose, png_int_32 X0, png_int_32 X1, int type, int nparams, png_const_charp units, png_charpp params) { + png_byte new_purpose[80]; png_size_t length; int i; @@ -295,7 +341,15 @@ png_set_pCAL(png_structp png_ptr, png_infop info_ptr, if (png_ptr == NULL || info_ptr == NULL) return; - length = png_strlen(purpose) + 1; + length = png_check_keyword(purpose, new_purpose); + + if (length == 0) + { + png_warning(png_ptr, "pCAL: invalid purpose keyword"); + return; + } + + ++length; png_debug1(3, "allocating purpose for info (%lu bytes)", (unsigned long)length); @@ -318,7 +372,7 @@ png_set_pCAL(png_structp png_ptr, png_infop info_ptr, return; } - png_memcpy(info_ptr->pcal_purpose, purpose, length); + png_memcpy(info_ptr->pcal_purpose, new_purpose, length); png_debug(3, "storing X0, X1, type, and nparams in info"); info_ptr->pcal_X0 = X0; @@ -517,12 +571,17 @@ png_set_PLTE(png_structp png_ptr, png_infop info_ptr, png_const_colorp palette, int num_palette) { + png_uint_32 max_palette_length; + png_debug1(1, "in %s storage function", "PLTE"); if (png_ptr == NULL || info_ptr == NULL) return; - if (num_palette < 0 || num_palette > PNG_MAX_PALETTE_LENGTH) + max_palette_length = (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) ? + (1 << info_ptr->bit_depth) : PNG_MAX_PALETTE_LENGTH; + + if (num_palette < 0 || num_palette > (int) max_palette_length) { if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) png_error(png_ptr, "Invalid palette length"); @@ -541,8 +600,8 @@ png_set_PLTE(png_structp png_ptr, png_infop info_ptr, png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, 0); /* Changed in libpng-1.2.1 to allocate PNG_MAX_PALETTE_LENGTH instead - * of num_palette entries, in case of an invalid PNG file that has - * too-large sample values. + * of num_palette entries, in case of an invalid PNG file or incorrect + * call to png_set_PLTE() with too-large sample values. */ png_ptr->palette = (png_colorp)png_calloc(png_ptr, PNG_MAX_PALETTE_LENGTH * png_sizeof(png_color)); @@ -618,6 +677,7 @@ png_set_iCCP(png_structp png_ptr, png_infop info_ptr, png_const_charp name, int compression_type, png_const_bytep profile, png_uint_32 proflen) { + png_byte new_name[80]; png_charp new_iccp_name; png_bytep new_iccp_profile; png_size_t length; @@ -627,7 +687,15 @@ png_set_iCCP(png_structp png_ptr, png_infop info_ptr, if (png_ptr == NULL || info_ptr == NULL || name == NULL || profile == NULL) return; - length = png_strlen(name)+1; + length = png_check_keyword(name, new_name); + + if (length == 0) + { + png_warning(png_ptr, "iCCP: invalid keyword"); + return; + } + + ++length; new_iccp_name = (png_charp)png_malloc_warn(png_ptr, length); if (new_iccp_name == NULL) @@ -636,7 +704,7 @@ png_set_iCCP(png_structp png_ptr, png_infop info_ptr, return; } - png_memcpy(new_iccp_name, name, length); + png_memcpy(new_iccp_name, new_name, length); new_iccp_profile = (png_bytep)png_malloc_warn(png_ptr, proflen); if (new_iccp_profile == NULL) @@ -671,7 +739,7 @@ png_set_text(png_structp png_ptr, png_infop info_ptr, png_const_textp text_ptr, int ret; ret = png_set_text_2(png_ptr, info_ptr, text_ptr, num_text); - if (ret) + if (ret != 0) png_error(png_ptr, "Insufficient memory to store text"); } @@ -680,8 +748,9 @@ png_set_text_2(png_structp png_ptr, png_infop info_ptr, png_const_textp text_ptr, int num_text) { int i; + size_t element_size; - png_debug1(1, "in %lx storage function", png_ptr == NULL ? "unexpected" : + png_debug1(1, "in %lx storage function", png_ptr == NULL ? 0xabadca11 : (unsigned long)png_ptr->chunk_name); if (png_ptr == NULL || info_ptr == NULL || num_text == 0) @@ -690,6 +759,18 @@ png_set_text_2(png_structp png_ptr, png_infop info_ptr, /* Make sure we have enough space in the "text" array in info_struct * to hold all of the incoming text_ptr objects. */ + + element_size=png_sizeof(png_text); + if (num_text < 0 || + num_text > INT_MAX - info_ptr->num_text - 8 || + (unsigned int)/*SAFE*/(num_text +/*SAFE*/ + info_ptr->num_text + 8) >= + PNG_SIZE_MAX/element_size) + { + png_warning(png_ptr, "too many text chunks"); + return(0); + } + if (info_ptr->num_text + num_text > info_ptr->max_text) { int old_max_text = info_ptr->max_text; @@ -739,6 +820,7 @@ png_set_text_2(png_structp png_ptr, png_infop info_ptr, } for (i = 0; i < num_text; i++) { + png_byte new_key[80], new_lang[80]; png_size_t text_length, key_len; png_size_t lang_len, lang_key_len; png_textp textp = &(info_ptr->text[info_ptr->num_text]); @@ -753,7 +835,13 @@ png_set_text_2(png_structp png_ptr, png_infop info_ptr, continue; } - key_len = png_strlen(text_ptr[i].key); + key_len = png_check_keyword(text_ptr[i].key, new_key); + + if (key_len == 0) + { + png_warning(png_ptr, "invalid text keyword"); + continue; + } if (text_ptr[i].compression <= 0) { @@ -766,8 +854,9 @@ png_set_text_2(png_structp png_ptr, png_infop info_ptr, { /* Set iTXt data */ + /* Zero length language is OK */ if (text_ptr[i].lang != NULL) - lang_len = png_strlen(text_ptr[i].lang); + lang_len = png_check_keyword(text_ptr[i].lang, new_lang); else lang_len = 0; @@ -815,7 +904,7 @@ png_set_text_2(png_structp png_ptr, png_infop info_ptr, (key_len + lang_len + lang_key_len + text_length + 4), textp->key); - png_memcpy(textp->key, text_ptr[i].key,(png_size_t)(key_len)); + png_memcpy(textp->key, new_key, (png_size_t)(key_len)); *(textp->key + key_len) = '\0'; if (text_ptr[i].compression > 0) @@ -836,7 +925,7 @@ png_set_text_2(png_structp png_ptr, png_infop info_ptr, textp->text = textp->key + key_len + 1; } - if (text_length) + if (text_length != 0) png_memcpy(textp->text, text_ptr[i].text, (png_size_t)(text_length)); @@ -897,6 +986,12 @@ png_set_tRNS(png_structp png_ptr, png_infop info_ptr, if (png_ptr == NULL || info_ptr == NULL) return; + if (num_trans < 0 || num_trans > PNG_MAX_PALETTE_LENGTH) + { + png_warning(png_ptr, "Ignoring invalid num_trans value"); + return; + } + if (trans_alpha != NULL) { /* It may not actually be necessary to set png_ptr->trans_alpha here; @@ -916,16 +1011,19 @@ png_set_tRNS(png_structp png_ptr, png_infop info_ptr, if (trans_color != NULL) { - int sample_max = (1 << info_ptr->bit_depth); + if (info_ptr->bit_depth < 16) + { + unsigned int sample_max = (1U << info_ptr->bit_depth) - 1U; - if ((info_ptr->color_type == PNG_COLOR_TYPE_GRAY && - (int)trans_color->gray > sample_max) || - (info_ptr->color_type == PNG_COLOR_TYPE_RGB && - ((int)trans_color->red > sample_max || - (int)trans_color->green > sample_max || - (int)trans_color->blue > sample_max))) - png_warning(png_ptr, - "tRNS chunk has out-of-range samples for bit_depth"); + if ((info_ptr->color_type == PNG_COLOR_TYPE_GRAY && + trans_color->gray > sample_max) || + (info_ptr->color_type == PNG_COLOR_TYPE_RGB && + (trans_color->red > sample_max || + trans_color->green > sample_max || + trans_color->blue > sample_max))) + png_warning(png_ptr, + "tRNS chunk has out-of-range samples for bit_depth"); + } png_memcpy(&(info_ptr->trans_color), trans_color, png_sizeof(png_color_16)); @@ -958,14 +1056,25 @@ png_set_sPLT(png_structp png_ptr, */ { png_sPLT_tp np; - int i; + int i, j; + size_t element_size; if (png_ptr == NULL || info_ptr == NULL) return; - np = (png_sPLT_tp)png_malloc_warn(png_ptr, - (info_ptr->splt_palettes_num + nentries) * - (png_size_t)png_sizeof(png_sPLT_t)); + element_size = png_sizeof(png_sPLT_t); + if (nentries < 0 || + nentries > INT_MAX-info_ptr->splt_palettes_num || + (unsigned int)/*SAFE*/(nentries +/*SAFE*/ + info_ptr->splt_palettes_num) >= + PNG_SIZE_MAX/element_size) + np=NULL; + + else + + np = (png_sPLT_tp)png_malloc_warn(png_ptr, + (info_ptr->splt_palettes_num + nentries) * + (png_size_t)png_sizeof(png_sPLT_t)); if (np == NULL) { @@ -979,13 +1088,22 @@ png_set_sPLT(png_structp png_ptr, png_free(png_ptr, info_ptr->splt_palettes); info_ptr->splt_palettes=NULL; - for (i = 0; i < nentries; i++) + for (i = j = 0; i < nentries; i++) { - png_sPLT_tp to = np + info_ptr->splt_palettes_num + i; + png_sPLT_tp to = np + info_ptr->splt_palettes_num + j; png_const_sPLT_tp from = entries + i; + png_byte new_name[80]; png_size_t length; - length = png_strlen(from->name) + 1; + length = png_check_keyword(from->name, new_name); + + if (length == 0) + { + png_warning(png_ptr, "sPLT: invalid keyword"); + continue; + } + + ++length; /* for trailing '\0' */ to->name = (png_charp)png_malloc_warn(png_ptr, length); if (to->name == NULL) @@ -995,7 +1113,7 @@ png_set_sPLT(png_structp png_ptr, continue; } - png_memcpy(to->name, from->name, length); + png_memcpy(to->name, new_name, length); to->entries = (png_sPLT_entryp)png_malloc_warn(png_ptr, from->nentries * png_sizeof(png_sPLT_entry)); @@ -1013,10 +1131,11 @@ png_set_sPLT(png_structp png_ptr, to->nentries = from->nentries; to->depth = from->depth; + ++j; } info_ptr->splt_palettes = np; - info_ptr->splt_palettes_num += nentries; + info_ptr->splt_palettes_num = j; info_ptr->valid |= PNG_INFO_sPLT; info_ptr->free_me |= PNG_FREE_SPLT; } @@ -1029,13 +1148,23 @@ png_set_unknown_chunks(png_structp png_ptr, { png_unknown_chunkp np; int i; + size_t element_size; if (png_ptr == NULL || info_ptr == NULL || num_unknowns == 0) return; - np = (png_unknown_chunkp)png_malloc_warn(png_ptr, - (png_size_t)(info_ptr->unknown_chunks_num + num_unknowns) * - png_sizeof(png_unknown_chunk)); + element_size = png_sizeof(png_unknown_chunk); + if (num_unknowns < 0 || + num_unknowns > INT_MAX-info_ptr->unknown_chunks_num || + (unsigned int)/*SAFE*/(num_unknowns +/*SAFE*/ + info_ptr->unknown_chunks_num) >= + PNG_SIZE_MAX/element_size) + np=NULL; + + else + np = (png_unknown_chunkp)png_malloc_warn(png_ptr, + (png_size_t)(info_ptr->unknown_chunks_num + num_unknowns) * + png_sizeof(png_unknown_chunk)); if (np == NULL) { @@ -1198,11 +1327,12 @@ png_set_rows(png_structp png_ptr, png_infop info_ptr, png_bytepp row_pointers) info_ptr->row_pointers = row_pointers; - if (row_pointers) + if (row_pointers != NULL) info_ptr->valid |= PNG_INFO_IDAT; } #endif +#ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED void PNGAPI png_set_compression_buffer_size(png_structp png_ptr, png_size_t size) { @@ -1230,6 +1360,7 @@ png_set_compression_buffer_size(png_structp png_ptr, png_size_t size) png_ptr->zstream.avail_out = 0; png_ptr->zstream.avail_in = 0; } +#endif /* WRITE_CUSTOMIZE_COMPRESSION */ void PNGAPI png_set_invalid(png_structp png_ptr, png_infop info_ptr, int mask) @@ -1248,7 +1379,7 @@ png_set_user_limits (png_structp png_ptr, png_uint_32 user_width_max, { /* Images with dimensions larger than these limits will be * rejected by png_set_IHDR(). To accept any PNG datastream - * regardless of dimensions, set both limits to 0x7ffffffL. + * regardless of dimensions, set both limits to 0x7fffffffL. */ if (png_ptr == NULL) return; @@ -1262,7 +1393,7 @@ void PNGAPI png_set_chunk_cache_max (png_structp png_ptr, png_uint_32 user_chunk_cache_max) { - if (png_ptr) + if (png_ptr != NULL) png_ptr->user_chunk_cache_max = user_chunk_cache_max; } @@ -1271,7 +1402,7 @@ void PNGAPI png_set_chunk_malloc_max (png_structp png_ptr, png_alloc_size_t user_chunk_malloc_max) { - if (png_ptr) + if (png_ptr != NULL) png_ptr->user_chunk_malloc_max = user_chunk_malloc_max; } #endif /* ?PNG_SET_USER_LIMITS_SUPPORTED */ @@ -1283,7 +1414,7 @@ png_set_benign_errors(png_structp png_ptr, int allowed) { png_debug(1, "in png_set_benign_errors"); - if (allowed) + if (allowed != 0) png_ptr->flags |= PNG_FLAG_BENIGN_ERRORS_WARN; else @@ -1300,12 +1431,11 @@ png_set_check_for_invalid_index(png_structp png_ptr, int allowed) { png_debug(1, "in png_set_check_for_invalid_index"); - if (allowed) + if (allowed != 0) png_ptr->num_palette_max = 0; else png_ptr->num_palette_max = -1; } #endif - #endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */ diff --git a/3rdparty/libpng/pngstruct.h b/3rdparty/libpng/pngstruct.h index db0d4e4948..52eef80e69 100644 --- a/3rdparty/libpng/pngstruct.h +++ b/3rdparty/libpng/pngstruct.h @@ -1,12 +1,11 @@ /* pngstruct.h - header file for PNG reference library * - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.23 [July 23, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * - * Last changed in libpng 1.5.9 [February 18, 2012] - * * This code is released under the libpng license. * For conditions of distribution and use, see the disclaimer * and license in png.h @@ -237,16 +236,6 @@ struct png_struct_def png_uint_16p hist; /* histogram */ #endif -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - png_byte heuristic_method; /* heuristic for row filter selection */ - png_byte num_prev_filters; /* number of weights for previous rows */ - png_bytep prev_filters; /* filter type(s) of previous row(s) */ - png_uint_16p filter_weights; /* weight(s) for previous line(s) */ - png_uint_16p inv_filter_weights; /* 1/weight(s) for previous line(s) */ - png_uint_16p filter_costs; /* relative filter calculation cost */ - png_uint_16p inv_filter_costs; /* 1/relative filter calculation cost */ -#endif - #ifdef PNG_TIME_RFC1123_SUPPORTED /* This is going to be unused in libpng16 and removed from libpng17 */ char time_buffer[29]; /* String to hold RFC 1123 time text */ @@ -352,7 +341,13 @@ struct png_struct_def /* New member added in libpng-1.5.6 */ png_bytep big_prev_row; +/* New member added in libpng-1.5.7 */ void (*read_filter[PNG_FILTER_VALUE_LAST-1])(png_row_infop row_info, png_bytep row, png_const_bytep prev_row); + + /* Options */ +#ifdef PNG_SET_OPTION_SUPPORTED + png_byte options; /* On/off state (up to 4 options) */ +#endif }; #endif /* PNGSTRUCT_H */ diff --git a/3rdparty/libpng/pngtrans.c b/3rdparty/libpng/pngtrans.c index ee60957fc1..a5df5afe00 100644 --- a/3rdparty/libpng/pngtrans.c +++ b/3rdparty/libpng/pngtrans.c @@ -1,8 +1,8 @@ /* pngtrans.c - transforms the data in a row (used by both readers and writers) * - * Last changed in libpng 1.5.11 [June 14, 2012] - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.19 [August 21, 2014] + * Copyright (c) 1998-2002,2004,2006-2014 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -452,7 +452,7 @@ png_do_strip_channel(png_row_infop row_info, png_bytep row, int at_start) { if (row_info->bit_depth == 8) { - if (at_start) /* Skip initial filler */ + if (at_start != 0) /* Skip initial filler */ ++sp; else /* Skip initial channel and, for sp, the filler */ sp += 2, ++dp; @@ -466,7 +466,7 @@ png_do_strip_channel(png_row_infop row_info, png_bytep row, int at_start) else if (row_info->bit_depth == 16) { - if (at_start) /* Skip initial filler */ + if (at_start != 0) /* Skip initial filler */ sp += 2; else /* Skip initial channel and, for sp, the filler */ sp += 4, dp += 2; @@ -492,7 +492,7 @@ png_do_strip_channel(png_row_infop row_info, png_bytep row, int at_start) { if (row_info->bit_depth == 8) { - if (at_start) /* Skip initial filler */ + if (at_start != 0) /* Skip initial filler */ ++sp; else /* Skip initial channels and, for sp, the filler */ sp += 4, dp += 3; @@ -506,7 +506,7 @@ png_do_strip_channel(png_row_infop row_info, png_bytep row, int at_start) else if (row_info->bit_depth == 16) { - if (at_start) /* Skip initial filler */ + if (at_start != 0) /* Skip initial filler */ sp += 2; else /* Skip initial channels and, for sp, the filler */ sp += 8, dp += 6; diff --git a/3rdparty/libpng/pngwio.c b/3rdparty/libpng/pngwio.c index 95ffb3429f..cc55521452 100644 --- a/3rdparty/libpng/pngwio.c +++ b/3rdparty/libpng/pngwio.c @@ -1,8 +1,8 @@ /* pngwio.c - functions for data output * - * Last changed in libpng 1.5.0 [January 6, 2011] - * Copyright (c) 1998-2011 Glenn Randers-Pehrson + * Last changed in libpng 1.5.18 [February 6, 2014] + * Copyright (c) 1998-2002,2004,2006-2014 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -207,6 +207,8 @@ png_set_write_fn(png_structp png_ptr, png_voidp io_ptr, # else png_ptr->output_flush_fn = output_flush_fn; # endif +#else + PNG_UNUSED(output_flush_fn) #endif /* PNG_WRITE_FLUSH_SUPPORTED */ /* It is an error to read while writing a png file */ diff --git a/3rdparty/libpng/pngwrite.c b/3rdparty/libpng/pngwrite.c index 2a72ad33f4..776c23603a 100644 --- a/3rdparty/libpng/pngwrite.c +++ b/3rdparty/libpng/pngwrite.c @@ -1,8 +1,8 @@ /* pngwrite.c - general routines to write a PNG file * - * Last changed in libpng 1.5.11 [June 14, 2012] - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.23 [July 23, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -34,85 +34,87 @@ png_write_info_before_PLTE(png_structp png_ptr, png_infop info_ptr) if (!(png_ptr->mode & PNG_WROTE_INFO_BEFORE_PLTE)) { - /* Write PNG signature */ - png_write_sig(png_ptr); + /* Write PNG signature */ + png_write_sig(png_ptr); #ifdef PNG_MNG_FEATURES_SUPPORTED - if ((png_ptr->mode&PNG_HAVE_PNG_SIGNATURE) && \ - (png_ptr->mng_features_permitted)) - { - png_warning(png_ptr, "MNG features are not allowed in a PNG datastream"); - png_ptr->mng_features_permitted = 0; - } + if ((png_ptr->mode&PNG_HAVE_PNG_SIGNATURE) && \ + (png_ptr->mng_features_permitted)) + { + png_warning(png_ptr, + "MNG features are not allowed in a PNG datastream"); + png_ptr->mng_features_permitted = 0; + } #endif - /* Write IHDR information. */ - png_write_IHDR(png_ptr, info_ptr->width, info_ptr->height, - info_ptr->bit_depth, info_ptr->color_type, info_ptr->compression_type, - info_ptr->filter_type, + /* Write IHDR information. */ + png_write_IHDR(png_ptr, info_ptr->width, info_ptr->height, + info_ptr->bit_depth, info_ptr->color_type, + info_ptr->compression_type, info_ptr->filter_type, #ifdef PNG_WRITE_INTERLACING_SUPPORTED - info_ptr->interlace_type); + info_ptr->interlace_type #else - 0); + 0 #endif - /* The rest of these check to see if the valid field has the appropriate - * flag set, and if it does, writes the chunk. - */ + ); + /* The rest of these check to see if the valid field has the appropriate + * flag set, and if it does, writes the chunk. + */ #ifdef PNG_WRITE_gAMA_SUPPORTED - if (info_ptr->valid & PNG_INFO_gAMA) - png_write_gAMA_fixed(png_ptr, info_ptr->gamma); + if (info_ptr->valid & PNG_INFO_gAMA) + png_write_gAMA_fixed(png_ptr, info_ptr->gamma); #endif #ifdef PNG_WRITE_sRGB_SUPPORTED - if (info_ptr->valid & PNG_INFO_sRGB) - png_write_sRGB(png_ptr, (int)info_ptr->srgb_intent); + if (info_ptr->valid & PNG_INFO_sRGB) + png_write_sRGB(png_ptr, (int)info_ptr->srgb_intent); #endif #ifdef PNG_WRITE_iCCP_SUPPORTED - if (info_ptr->valid & PNG_INFO_iCCP) - png_write_iCCP(png_ptr, info_ptr->iccp_name, PNG_COMPRESSION_TYPE_BASE, - (png_charp)info_ptr->iccp_profile, (int)info_ptr->iccp_proflen); + if (info_ptr->valid & PNG_INFO_iCCP) + png_write_iCCP(png_ptr, info_ptr->iccp_name, PNG_COMPRESSION_TYPE_BASE, + (png_charp)info_ptr->iccp_profile, (int)info_ptr->iccp_proflen); #endif #ifdef PNG_WRITE_sBIT_SUPPORTED - if (info_ptr->valid & PNG_INFO_sBIT) - png_write_sBIT(png_ptr, &(info_ptr->sig_bit), info_ptr->color_type); + if (info_ptr->valid & PNG_INFO_sBIT) + png_write_sBIT(png_ptr, &(info_ptr->sig_bit), info_ptr->color_type); #endif #ifdef PNG_WRITE_cHRM_SUPPORTED - if (info_ptr->valid & PNG_INFO_cHRM) - png_write_cHRM_fixed(png_ptr, - info_ptr->x_white, info_ptr->y_white, - info_ptr->x_red, info_ptr->y_red, - info_ptr->x_green, info_ptr->y_green, - info_ptr->x_blue, info_ptr->y_blue); + if (info_ptr->valid & PNG_INFO_cHRM) + png_write_cHRM_fixed(png_ptr, + info_ptr->x_white, info_ptr->y_white, + info_ptr->x_red, info_ptr->y_red, + info_ptr->x_green, info_ptr->y_green, + info_ptr->x_blue, info_ptr->y_blue); #endif #ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED - if (info_ptr->unknown_chunks_num) - { - png_unknown_chunk *up; - - png_debug(5, "writing extra chunks"); - - for (up = info_ptr->unknown_chunks; - up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num; - up++) + if (info_ptr->unknown_chunks_num) { - int keep = png_handle_as_unknown(png_ptr, up->name); + png_unknown_chunk *up; - if (keep != PNG_HANDLE_CHUNK_NEVER && - up->location && - !(up->location & PNG_HAVE_PLTE) && - !(up->location & PNG_HAVE_IDAT) && - !(up->location & PNG_AFTER_IDAT) && - ((up->name[3] & 0x20) || keep == PNG_HANDLE_CHUNK_ALWAYS || - (png_ptr->flags & PNG_FLAG_KEEP_UNSAFE_CHUNKS))) + png_debug(5, "writing extra chunks"); + + for (up = info_ptr->unknown_chunks; + up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num; + up++) { - if (up->size == 0) - png_warning(png_ptr, "Writing zero-length unknown chunk"); + int keep = png_handle_as_unknown(png_ptr, up->name); - png_write_chunk(png_ptr, up->name, up->data, up->size); + if (keep != PNG_HANDLE_CHUNK_NEVER && + up->location && + !(up->location & PNG_HAVE_PLTE) && + !(up->location & PNG_HAVE_IDAT) && + !(up->location & PNG_AFTER_IDAT) && + ((up->name[3] & 0x20) || keep == PNG_HANDLE_CHUNK_ALWAYS || + (png_ptr->flags & PNG_FLAG_KEEP_UNSAFE_CHUNKS))) + { + if (up->size == 0) + png_warning(png_ptr, "Writing zero-length unknown chunk"); + + png_write_chunk(png_ptr, up->name, up->data, up->size); + } } } - } #endif png_ptr->mode |= PNG_WROTE_INFO_BEFORE_PLTE; } @@ -223,11 +225,14 @@ png_write_info(png_structp png_ptr, png_infop info_ptr) info_ptr->text[i].lang, info_ptr->text[i].lang_key, info_ptr->text[i].text); + /* Mark this chunk as written */ + if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; + else + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; #else - png_warning(png_ptr, "Unable to write international text"); + png_warning(png_ptr, "Unable to write international text"); #endif - /* Mark this chunk as written */ - info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; } /* If we want a compressed text chunk */ @@ -238,11 +243,11 @@ png_write_info(png_structp png_ptr, png_infop info_ptr) png_write_zTXt(png_ptr, info_ptr->text[i].key, info_ptr->text[i].text, 0, info_ptr->text[i].compression); + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; #else png_warning(png_ptr, "Unable to write compressed text"); #endif - /* Mark this chunk as written */ - info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; } else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) @@ -340,11 +345,11 @@ png_write_end(png_structp png_ptr, png_infop info_ptr) info_ptr->text[i].lang, info_ptr->text[i].lang_key, info_ptr->text[i].text); + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; #else png_warning(png_ptr, "Unable to write international text"); #endif - /* Mark this chunk as written */ - info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; } else if (info_ptr->text[i].compression >= PNG_TEXT_COMPRESSION_zTXt) @@ -354,11 +359,11 @@ png_write_end(png_structp png_ptr, png_infop info_ptr) png_write_zTXt(png_ptr, info_ptr->text[i].key, info_ptr->text[i].text, 0, info_ptr->text[i].compression); + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; #else png_warning(png_ptr, "Unable to write compressed text"); #endif - /* Mark this chunk as written */ - info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; } else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) @@ -367,12 +372,11 @@ png_write_end(png_structp png_ptr, png_infop info_ptr) /* Write uncompressed chunk */ png_write_tEXt(png_ptr, info_ptr->text[i].key, info_ptr->text[i].text, 0); + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; #else png_warning(png_ptr, "Unable to write uncompressed text"); #endif - - /* Mark this chunk as written */ - info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; } } #endif @@ -420,7 +424,6 @@ png_write_end(png_structp png_ptr, png_infop info_ptr) } #ifdef PNG_CONVERT_tIME_SUPPORTED -/* "tm" structure is not supported on WindowsCE */ void PNGAPI png_convert_from_struct_tm(png_timep ptime, PNG_CONST struct tm FAR * ttime) { @@ -456,9 +459,6 @@ png_create_write_struct,(png_const_charp user_png_ver, png_voidp error_ptr, warn_fn, NULL, NULL, NULL)); } -/* Alternate initialize png_ptr structure, and allocate any memory needed */ -static void png_reset_filter_heuristics(png_structp png_ptr); /* forward decl */ - PNG_FUNCTION(png_structp,PNGAPI png_create_write_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr, @@ -500,14 +500,13 @@ png_create_write_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, */ #ifdef USE_FAR_KEYWORD if (setjmp(tmp_jmpbuf)) + png_memcpy(png_jmpbuf(png_ptr), tmp_jmpbuf, png_sizeof(jmp_buf)); + PNG_ABORT(); #else if (setjmp(png_jmpbuf(png_ptr))) /* sets longjmp to match setjmp */ -#endif -#ifdef USE_FAR_KEYWORD - png_memcpy(png_jmpbuf(png_ptr), tmp_jmpbuf, png_sizeof(jmp_buf)); -#endif PNG_ABORT(); #endif +#endif #ifdef PNG_USER_MEM_SUPPORTED png_set_mem_fn(png_ptr, mem_ptr, malloc_fn, free_fn); @@ -520,7 +519,7 @@ png_create_write_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, /* Initialize zbuf - compression buffer */ png_ptr->zbuf_size = PNG_ZBUF_SIZE; - if (!png_cleanup_needed) + if (png_cleanup_needed == 0) { png_ptr->zbuf = (png_bytep)png_malloc_warn(png_ptr, png_ptr->zbuf_size); @@ -528,7 +527,7 @@ png_create_write_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, png_cleanup_needed = 1; } - if (png_cleanup_needed) + if (png_cleanup_needed != 0) { /* Clean up PNG structure and deallocate any memory. */ png_free(png_ptr, png_ptr->zbuf); @@ -544,10 +543,6 @@ png_create_write_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, png_set_write_fn(png_ptr, NULL, NULL, NULL); -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - png_reset_filter_heuristics(png_ptr); -#endif - return (png_ptr); } @@ -764,7 +759,7 @@ png_write_row(png_structp png_ptr, png_const_bytep row) { png_do_write_interlace(&row_info, png_ptr->row_buf + 1, png_ptr->pass); /* This should always get caught above, but still ... */ - if (!(row_info.width)) + if (row_info.width == 0) { png_write_finish_row(png_ptr); return; @@ -864,7 +859,7 @@ png_write_flush(png_structp png_ptr) png_error(png_ptr, "zlib error"); } - if (!(png_ptr->zstream.avail_out)) + if ((png_ptr->zstream.avail_out) == 0) { /* Write the IDAT and reset the zlib output buffer */ png_write_IDAT(png_ptr, png_ptr->zbuf, png_ptr->zbuf_size); @@ -982,13 +977,6 @@ png_write_destroy(png_structp png_ptr) png_free(png_ptr, png_ptr->paeth_row); #endif -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - /* Use this to save a little code space, it doesn't free the filter_costs */ - png_reset_filter_heuristics(png_ptr); - png_free(png_ptr, png_ptr->filter_costs); - png_free(png_ptr, png_ptr->inv_filter_costs); -#endif - #ifdef PNG_SETJMP_SUPPORTED /* Reset structure */ png_memcpy(tmp_jmp, png_ptr->longjmp_buffer, png_sizeof(jmp_buf)); @@ -1042,6 +1030,7 @@ png_set_filter(png_structp png_ptr, int method, int filters) case 5: case 6: case 7: png_warning(png_ptr, "Unknown row filter for method 0"); + /* FALL THROUGH */ #endif /* PNG_WRITE_FILTER_SUPPORTED */ case PNG_FILTER_VALUE_NONE: png_ptr->do_filter = PNG_FILTER_NONE; break; @@ -1078,6 +1067,7 @@ png_set_filter(png_structp png_ptr, int method, int filters) */ if (png_ptr->row_buf != NULL) { + png_ptr->do_filter = PNG_FILTER_NONE; #ifdef PNG_WRITE_FILTER_SUPPORTED if ((png_ptr->do_filter & PNG_FILTER_SUB) && png_ptr->sub_row == NULL) { @@ -1138,8 +1128,8 @@ png_set_filter(png_structp png_ptr, int method, int filters) } if (png_ptr->do_filter == PNG_NO_FILTERS) -#endif /* PNG_WRITE_FILTER_SUPPORTED */ png_ptr->do_filter = PNG_FILTER_NONE; +#endif /* PNG_WRITE_FILTER_SUPPORTED */ } } else @@ -1153,122 +1143,7 @@ png_set_filter(png_structp png_ptr, int method, int filters) * filtered data going to zlib more consistent, hopefully resulting in * better compression. */ -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* GRR 970116 */ -/* Convenience reset API. */ -static void -png_reset_filter_heuristics(png_structp png_ptr) -{ - /* Clear out any old values in the 'weights' - this must be done because if - * the app calls set_filter_heuristics multiple times with different - * 'num_weights' values we would otherwise potentially have wrong sized - * arrays. - */ - png_ptr->num_prev_filters = 0; - png_ptr->heuristic_method = PNG_FILTER_HEURISTIC_UNWEIGHTED; - if (png_ptr->prev_filters != NULL) - { - png_bytep old = png_ptr->prev_filters; - png_ptr->prev_filters = NULL; - png_free(png_ptr, old); - } - if (png_ptr->filter_weights != NULL) - { - png_uint_16p old = png_ptr->filter_weights; - png_ptr->filter_weights = NULL; - png_free(png_ptr, old); - } - - if (png_ptr->inv_filter_weights != NULL) - { - png_uint_16p old = png_ptr->inv_filter_weights; - png_ptr->inv_filter_weights = NULL; - png_free(png_ptr, old); - } - - /* Leave the filter_costs - this array is fixed size. */ -} - -static int -png_init_filter_heuristics(png_structp png_ptr, int heuristic_method, - int num_weights) -{ - if (png_ptr == NULL) - return 0; - - /* Clear out the arrays */ - png_reset_filter_heuristics(png_ptr); - - /* Check arguments; the 'reset' function makes the correct settings for the - * unweighted case, but we must handle the weight case by initializing the - * arrays for the caller. - */ - if (heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int i; - - if (num_weights > 0) - { - png_ptr->prev_filters = (png_bytep)png_malloc(png_ptr, - (png_uint_32)(png_sizeof(png_byte) * num_weights)); - - /* To make sure that the weighting starts out fairly */ - for (i = 0; i < num_weights; i++) - { - png_ptr->prev_filters[i] = 255; - } - - png_ptr->filter_weights = (png_uint_16p)png_malloc(png_ptr, - (png_uint_32)(png_sizeof(png_uint_16) * num_weights)); - - png_ptr->inv_filter_weights = (png_uint_16p)png_malloc(png_ptr, - (png_uint_32)(png_sizeof(png_uint_16) * num_weights)); - - for (i = 0; i < num_weights; i++) - { - png_ptr->inv_filter_weights[i] = - png_ptr->filter_weights[i] = PNG_WEIGHT_FACTOR; - } - - /* Safe to set this now */ - png_ptr->num_prev_filters = (png_byte)num_weights; - } - - /* If, in the future, there are other filter methods, this would - * need to be based on png_ptr->filter. - */ - if (png_ptr->filter_costs == NULL) - { - png_ptr->filter_costs = (png_uint_16p)png_malloc(png_ptr, - (png_uint_32)(png_sizeof(png_uint_16) * PNG_FILTER_VALUE_LAST)); - - png_ptr->inv_filter_costs = (png_uint_16p)png_malloc(png_ptr, - (png_uint_32)(png_sizeof(png_uint_16) * PNG_FILTER_VALUE_LAST)); - } - - for (i = 0; i < PNG_FILTER_VALUE_LAST; i++) - { - png_ptr->inv_filter_costs[i] = - png_ptr->filter_costs[i] = PNG_COST_FACTOR; - } - - /* All the arrays are inited, safe to set this: */ - png_ptr->heuristic_method = PNG_FILTER_HEURISTIC_WEIGHTED; - - /* Return the 'ok' code. */ - return 1; - } - else if (heuristic_method == PNG_FILTER_HEURISTIC_DEFAULT || - heuristic_method == PNG_FILTER_HEURISTIC_UNWEIGHTED) - { - return 1; - } - else - { - png_warning(png_ptr, "Unknown filter heuristic method"); - return 0; - } -} - +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* DEPRECATED */ /* Provide floating and fixed point APIs */ #ifdef PNG_FLOATING_POINT_SUPPORTED void PNGAPI @@ -1276,52 +1151,11 @@ png_set_filter_heuristics(png_structp png_ptr, int heuristic_method, int num_weights, png_const_doublep filter_weights, png_const_doublep filter_costs) { - png_debug(1, "in png_set_filter_heuristics"); - - /* The internal API allocates all the arrays and ensures that the elements of - * those arrays are set to the default value. - */ - if (!png_init_filter_heuristics(png_ptr, heuristic_method, num_weights)) - return; - - /* If using the weighted method copy in the weights. */ - if (heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int i; - for (i = 0; i < num_weights; i++) - { - if (filter_weights[i] <= 0.0) - { - png_ptr->inv_filter_weights[i] = - png_ptr->filter_weights[i] = PNG_WEIGHT_FACTOR; - } - - else - { - png_ptr->inv_filter_weights[i] = - (png_uint_16)(PNG_WEIGHT_FACTOR*filter_weights[i]+.5); - - png_ptr->filter_weights[i] = - (png_uint_16)(PNG_WEIGHT_FACTOR/filter_weights[i]+.5); - } - } - - /* Here is where we set the relative costs of the different filters. We - * should take the desired compression level into account when setting - * the costs, so that Paeth, for instance, has a high relative cost at low - * compression levels, while it has a lower relative cost at higher - * compression settings. The filter types are in order of increasing - * relative cost, so it would be possible to do this with an algorithm. - */ - for (i = 0; i < PNG_FILTER_VALUE_LAST; i++) if (filter_costs[i] >= 1.0) - { - png_ptr->inv_filter_costs[i] = - (png_uint_16)(PNG_COST_FACTOR / filter_costs[i] + .5); - - png_ptr->filter_costs[i] = - (png_uint_16)(PNG_COST_FACTOR * filter_costs[i] + .5); - } - } + PNG_UNUSED(png_ptr) + PNG_UNUSED(heuristic_method) + PNG_UNUSED(num_weights) + PNG_UNUSED(filter_weights) + PNG_UNUSED(filter_costs) } #endif /* FLOATING_POINT */ @@ -1331,67 +1165,16 @@ png_set_filter_heuristics_fixed(png_structp png_ptr, int heuristic_method, int num_weights, png_const_fixed_point_p filter_weights, png_const_fixed_point_p filter_costs) { - png_debug(1, "in png_set_filter_heuristics_fixed"); - - /* The internal API allocates all the arrays and ensures that the elements of - * those arrays are set to the default value. - */ - if (!png_init_filter_heuristics(png_ptr, heuristic_method, num_weights)) - return; - - /* If using the weighted method copy in the weights. */ - if (heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int i; - for (i = 0; i < num_weights; i++) - { - if (filter_weights[i] <= 0) - { - png_ptr->inv_filter_weights[i] = - png_ptr->filter_weights[i] = PNG_WEIGHT_FACTOR; - } - - else - { - png_ptr->inv_filter_weights[i] = (png_uint_16) - ((PNG_WEIGHT_FACTOR*filter_weights[i]+PNG_FP_HALF)/PNG_FP_1); - - png_ptr->filter_weights[i] = (png_uint_16)((PNG_WEIGHT_FACTOR* - PNG_FP_1+(filter_weights[i]/2))/filter_weights[i]); - } - } - - /* Here is where we set the relative costs of the different filters. We - * should take the desired compression level into account when setting - * the costs, so that Paeth, for instance, has a high relative cost at low - * compression levels, while it has a lower relative cost at higher - * compression settings. The filter types are in order of increasing - * relative cost, so it would be possible to do this with an algorithm. - */ - for (i = 0; i < PNG_FILTER_VALUE_LAST; i++) - if (filter_costs[i] >= PNG_FP_1) - { - png_uint_32 tmp; - - /* Use a 32 bit unsigned temporary here because otherwise the - * intermediate value will be a 32 bit *signed* integer (ANSI rules) - * and this will get the wrong answer on division. - */ - tmp = PNG_COST_FACTOR*PNG_FP_1 + (filter_costs[i]/2); - tmp /= filter_costs[i]; - - png_ptr->inv_filter_costs[i] = (png_uint_16)tmp; - - tmp = PNG_COST_FACTOR * filter_costs[i] + PNG_FP_HALF; - tmp /= PNG_FP_1; - - png_ptr->filter_costs[i] = (png_uint_16)tmp; - } - } + PNG_UNUSED(png_ptr) + PNG_UNUSED(heuristic_method) + PNG_UNUSED(num_weights) + PNG_UNUSED(filter_weights) + PNG_UNUSED(filter_costs) } #endif /* FIXED_POINT */ -#endif /* PNG_WRITE_WEIGHTED_FILTER_SUPPORTED */ +#endif /* WRITE_WEIGHTED_FILTER */ +#ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED void PNGAPI png_set_compression_level(png_structp png_ptr, int level) { @@ -1470,6 +1253,7 @@ png_set_compression_method(png_structp png_ptr, int method) png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_METHOD; png_ptr->zlib_method = method; } +#endif /* WRITE_CUSTOMIZE_COMPRESSION */ /* The following were added to libpng-1.5.4 */ #ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED @@ -1527,10 +1311,10 @@ png_set_text_compression_window_bits(png_structp png_ptr, int window_bits) #ifndef WBITS_8_OK /* Avoid libpng bug with 256-byte windows */ if (window_bits == 8) - { - png_warning(png_ptr, "Text compression window is being reset to 512"); - window_bits = 9; - } + { + png_warning(png_ptr, "Text compression window is being reset to 512"); + window_bits = 9; + } #endif png_ptr->flags |= PNG_FLAG_ZTXT_CUSTOM_WINDOW_BITS; diff --git a/3rdparty/libpng/pngwtran.c b/3rdparty/libpng/pngwtran.c index 96608efcb4..24c436ffe4 100644 --- a/3rdparty/libpng/pngwtran.c +++ b/3rdparty/libpng/pngwtran.c @@ -1,8 +1,8 @@ /* pngwtran.c - transforms the data in a row for PNG writers * - * Last changed in libpng 1.5.6 [November 3, 2011] - * Copyright (c) 1998-2011 Glenn Randers-Pehrson + * Copyright (c) 1998-2002,2004,2006-2012 Glenn Randers-Pehrson + * Copyright (c) 1998-2012 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -45,8 +45,20 @@ png_do_write_transformations(png_structp png_ptr, png_row_infop row_info) #ifdef PNG_WRITE_FILLER_SUPPORTED if (png_ptr->transformations & PNG_FILLER) - png_do_strip_channel(row_info, png_ptr->row_buf + 1, - !(png_ptr->flags & PNG_FLAG_FILLER_AFTER)); + { + if (png_ptr->color_type & (PNG_COLOR_MASK_ALPHA|PNG_COLOR_MASK_PALETTE)) + { + /* GA, RGBA or palette; in any of these cases libpng will not do the + * the correct thing (whatever that might be). + */ + png_warning(png_ptr, "incorrect png_set_filler call ignored"); + png_ptr->transformations &= ~PNG_FILLER; + } + + else + png_do_strip_channel(row_info, png_ptr->row_buf + 1, + !(png_ptr->flags & PNG_FLAG_FILLER_AFTER)); + } #endif #ifdef PNG_WRITE_PACKSWAP_SUPPORTED diff --git a/3rdparty/libpng/pngwutil.c b/3rdparty/libpng/pngwutil.c index b49704f1a5..d5f097fb97 100644 --- a/3rdparty/libpng/pngwutil.c +++ b/3rdparty/libpng/pngwutil.c @@ -1,8 +1,8 @@ /* pngwutil.c - utilities to write a PNG file * - * Last changed in libpng 1.5.10 [March 8, 2012] - * Copyright (c) 1998-2012 Glenn Randers-Pehrson + * Last changed in libpng 1.5.26 [December 17, 2015] + * Copyright (c) 1998-2002,2004,2006-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * @@ -30,19 +30,20 @@ png_save_uint_32(png_bytep buf, png_uint_32 i) } #ifdef PNG_SAVE_INT_32_SUPPORTED -/* The png_save_int_32 function assumes integers are stored in two's - * complement format. If this isn't the case, then this routine needs to - * be modified to write data in two's complement format. Note that, - * the following works correctly even if png_int_32 has more than 32 bits - * (compare the more complex code required on read for sign extention.) +/* PNG signed integers are saved in 32-bit 2's complement format. ANSI C-90 + * defines a cast of a signed integer to an unsigned integer either to preserve + * the value, if it is positive, or to calculate: + * + * (UNSIGNED_MAX+1) + integer + * + * Where UNSIGNED_MAX is the appropriate maximum unsigned value, so when the + * negative integral value is added the result will be an unsigned value + * correspnding to the 2's complement representation. */ void PNGAPI png_save_int_32(png_bytep buf, png_int_32 i) { - buf[0] = (png_byte)((i >> 24) & 0xff); - buf[1] = (png_byte)((i >> 16) & 0xff); - buf[2] = (png_byte)((i >> 8) & 0xff); - buf[3] = (png_byte)(i & 0xff); + png_save_uint_32(buf, i); } #endif @@ -315,6 +316,7 @@ png_zlib_release(png_structp png_ptr) if (ret != Z_OK) { +#ifdef PNG_WARNINGS_SUPPORTED png_const_charp err; PNG_WARNING_PARAMETERS(p) @@ -349,6 +351,7 @@ png_zlib_release(png_structp png_ptr) png_formatted_warning(png_ptr, p, "zlib failed to reset compressor: @1(@2): @3"); +#endif } } @@ -460,24 +463,21 @@ png_text_compress(png_structp png_ptr, old_ptr = comp->output_ptr; comp->output_ptr = (png_bytepp)png_malloc(png_ptr, - (png_alloc_size_t) - (comp->max_output_ptr * png_sizeof(png_charpp))); + (comp->max_output_ptr * png_sizeof(png_bytep))); png_memcpy(comp->output_ptr, old_ptr, old_max - * png_sizeof(png_charp)); + * png_sizeof(png_bytep)); png_free(png_ptr, old_ptr); } else comp->output_ptr = (png_bytepp)png_malloc(png_ptr, - (png_alloc_size_t) - (comp->max_output_ptr * png_sizeof(png_charp))); + (comp->max_output_ptr * png_sizeof(png_bytep))); } /* Save the data */ comp->output_ptr[comp->num_output_ptr] = - (png_bytep)png_malloc(png_ptr, - (png_alloc_size_t)png_ptr->zbuf_size); + (png_bytep)png_malloc(png_ptr, png_ptr->zbuf_size); png_memcpy(comp->output_ptr[comp->num_output_ptr], png_ptr->zbuf, png_ptr->zbuf_size); @@ -826,7 +826,7 @@ png_write_IHDR(png_structp png_ptr, png_uint_32 width, png_uint_32 height, png_ptr->zstream.zfree = png_zfree; png_ptr->zstream.opaque = (voidpf)png_ptr; - if (!(png_ptr->do_filter)) + if ((png_ptr->do_filter) == PNG_NO_FILTERS) { if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE || png_ptr->bit_depth < 8) @@ -896,17 +896,20 @@ void /* PRIVATE */ png_write_PLTE(png_structp png_ptr, png_const_colorp palette, png_uint_32 num_pal) { - png_uint_32 i; + png_uint_32 max_palette_length, i; png_const_colorp pal_ptr; png_byte buf[3]; png_debug(1, "in png_write_PLTE"); + max_palette_length = (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) ? + (1 << png_ptr->bit_depth) : PNG_MAX_PALETTE_LENGTH; + if (( #ifdef PNG_MNG_FEATURES_SUPPORTED !(png_ptr->mng_features_permitted & PNG_FLAG_MNG_EMPTY_PLTE) && #endif - num_pal == 0) || num_pal > 256) + num_pal == 0) || num_pal > max_palette_length) { if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) { @@ -1096,7 +1099,6 @@ png_write_iCCP(png_structp png_ptr, png_const_charp name, int compression_type, png_const_charp profile, int profile_len) { png_size_t name_len; - png_charp new_name; compression_state comp; int embedded_profile_len = 0; @@ -1108,8 +1110,7 @@ png_write_iCCP(png_structp png_ptr, png_const_charp name, int compression_type, comp.input = NULL; comp.input_len = 0; - if ((name_len = png_check_keyword(png_ptr, name, &new_name)) == 0) - return; + name_len = png_strlen(name); if (compression_type != PNG_COMPRESSION_TYPE_BASE) png_warning(png_ptr, "Unknown compression type in iCCP chunk"); @@ -1128,8 +1129,6 @@ png_write_iCCP(png_structp png_ptr, png_const_charp name, int compression_type, { png_warning(png_ptr, "Embedded profile length in iCCP chunk is negative"); - - png_free(png_ptr, new_name); return; } @@ -1137,8 +1136,6 @@ png_write_iCCP(png_structp png_ptr, png_const_charp name, int compression_type, { png_warning(png_ptr, "Embedded profile length too large in iCCP chunk"); - - png_free(png_ptr, new_name); return; } @@ -1150,7 +1147,7 @@ png_write_iCCP(png_structp png_ptr, png_const_charp name, int compression_type, profile_len = embedded_profile_len; } - if (profile_len) + if (profile_len != 0) profile_len = png_text_compress(png_ptr, profile, (png_size_t)profile_len, PNG_COMPRESSION_TYPE_BASE, &comp); @@ -1158,18 +1155,21 @@ png_write_iCCP(png_structp png_ptr, png_const_charp name, int compression_type, png_write_chunk_header(png_ptr, png_iCCP, (png_uint_32)(name_len + profile_len + 2)); - new_name[name_len + 1] = 0x00; + png_write_chunk_data(png_ptr, (png_bytep)name, name_len); - png_write_chunk_data(png_ptr, (png_bytep)new_name, - (png_size_t)(name_len + 2)); + { + png_byte buffer[2]; + buffer[0] = 0; /* terminate name */ + buffer[1] = 0xFFU & compression_type; + png_write_chunk_data(png_ptr, buffer, 2); + } - if (profile_len) + if (profile_len != 0) { png_write_compressed_data_out(png_ptr, &comp, profile_len); } png_write_chunk_end(png_ptr); - png_free(png_ptr, new_name); } #endif @@ -1179,7 +1179,6 @@ void /* PRIVATE */ png_write_sPLT(png_structp png_ptr, png_const_sPLT_tp spalette) { png_size_t name_len; - png_charp new_name; png_byte entrybuf[10]; png_size_t entry_size = (spalette->depth == 8 ? 6 : 10); png_size_t palette_size = entry_size * spalette->nentries; @@ -1190,14 +1189,13 @@ png_write_sPLT(png_structp png_ptr, png_const_sPLT_tp spalette) png_debug(1, "in png_write_sPLT"); - if ((name_len = png_check_keyword(png_ptr,spalette->name, &new_name))==0) - return; + name_len = png_strlen(spalette->name); /* Make sure we include the NULL after the name */ png_write_chunk_header(png_ptr, png_sPLT, (png_uint_32)(name_len + 2 + palette_size)); - png_write_chunk_data(png_ptr, (png_bytep)new_name, + png_write_chunk_data(png_ptr, (png_bytep)spalette->name, (png_size_t)(name_len + 1)); png_write_chunk_data(png_ptr, &spalette->depth, (png_size_t)1); @@ -1253,7 +1251,6 @@ png_write_sPLT(png_structp png_ptr, png_const_sPLT_tp spalette) #endif png_write_chunk_end(png_ptr); - png_free(png_ptr, new_name); } #endif @@ -1370,7 +1367,8 @@ png_write_tRNS(png_structp png_ptr, png_const_bytep trans_alpha, } /* Write the chunk out as it is */ - png_write_complete_chunk(png_ptr, png_tRNS, trans_alpha, (png_size_t)num_trans); + png_write_complete_chunk(png_ptr, png_tRNS, trans_alpha, + (png_size_t)num_trans); } else if (color_type == PNG_COLOR_TYPE_GRAY) @@ -1508,138 +1506,6 @@ png_write_hIST(png_structp png_ptr, png_const_uint_16p hist, int num_hist) } #endif -#if defined(PNG_WRITE_TEXT_SUPPORTED) || defined(PNG_WRITE_pCAL_SUPPORTED) || \ - defined(PNG_WRITE_iCCP_SUPPORTED) || defined(PNG_WRITE_sPLT_SUPPORTED) -/* Check that the tEXt or zTXt keyword is valid per PNG 1.0 specification, - * and if invalid, correct the keyword rather than discarding the entire - * chunk. The PNG 1.0 specification requires keywords 1-79 characters in - * length, forbids leading or trailing whitespace, multiple internal spaces, - * and the non-break space (0x80) from ISO 8859-1. Returns keyword length. - * - * The new_key is allocated to hold the corrected keyword and must be freed - * by the calling routine. This avoids problems with trying to write to - * static keywords without having to have duplicate copies of the strings. - */ -png_size_t /* PRIVATE */ -png_check_keyword(png_structp png_ptr, png_const_charp key, png_charpp new_key) -{ - png_size_t key_len; - png_const_charp ikp; - png_charp kp, dp; - int kflag; - int kwarn=0; - - png_debug(1, "in png_check_keyword"); - - *new_key = NULL; - - if (key == NULL || (key_len = png_strlen(key)) == 0) - { - png_warning(png_ptr, "zero length keyword"); - return ((png_size_t)0); - } - - png_debug1(2, "Keyword to be checked is '%s'", key); - - *new_key = (png_charp)png_malloc_warn(png_ptr, (png_uint_32)(key_len + 2)); - - if (*new_key == NULL) - { - png_warning(png_ptr, "Out of memory while procesing keyword"); - return ((png_size_t)0); - } - - /* Replace non-printing characters with a blank and print a warning */ - for (ikp = key, dp = *new_key; *ikp != '\0'; ikp++, dp++) - { - if ((png_byte)*ikp < 0x20 || - ((png_byte)*ikp > 0x7E && (png_byte)*ikp < 0xA1)) - { - PNG_WARNING_PARAMETERS(p) - - png_warning_parameter_unsigned(p, 1, PNG_NUMBER_FORMAT_02x, - (png_byte)*ikp); - png_formatted_warning(png_ptr, p, "invalid keyword character 0x@1"); - *dp = ' '; - } - - else - { - *dp = *ikp; - } - } - *dp = '\0'; - - /* Remove any trailing white space. */ - kp = *new_key + key_len - 1; - if (*kp == ' ') - { - png_warning(png_ptr, "trailing spaces removed from keyword"); - - while (*kp == ' ') - { - *(kp--) = '\0'; - key_len--; - } - } - - /* Remove any leading white space. */ - kp = *new_key; - if (*kp == ' ') - { - png_warning(png_ptr, "leading spaces removed from keyword"); - - while (*kp == ' ') - { - kp++; - key_len--; - } - } - - png_debug1(2, "Checking for multiple internal spaces in '%s'", kp); - - /* Remove multiple internal spaces. */ - for (kflag = 0, dp = *new_key; *kp != '\0'; kp++) - { - if (*kp == ' ' && kflag == 0) - { - *(dp++) = *kp; - kflag = 1; - } - - else if (*kp == ' ') - { - key_len--; - kwarn = 1; - } - - else - { - *(dp++) = *kp; - kflag = 0; - } - } - *dp = '\0'; - if (kwarn) - png_warning(png_ptr, "extra interior spaces removed from keyword"); - - if (key_len == 0) - { - png_free(png_ptr, *new_key); - png_warning(png_ptr, "Zero length keyword"); - } - - if (key_len > 79) - { - png_warning(png_ptr, "keyword length must be 1 - 79 characters"); - (*new_key)[79] = '\0'; - key_len = 79; - } - - return (key_len); -} -#endif - #ifdef PNG_WRITE_tEXt_SUPPORTED /* Write a tEXt chunk */ void /* PRIVATE */ @@ -1647,12 +1513,10 @@ png_write_tEXt(png_structp png_ptr, png_const_charp key, png_const_charp text, png_size_t text_len) { png_size_t key_len; - png_charp new_key; png_debug(1, "in png_write_tEXt"); - if ((key_len = png_check_keyword(png_ptr, key, &new_key))==0) - return; + key_len = strlen(key); if (text == NULL || *text == '\0') text_len = 0; @@ -1669,15 +1533,14 @@ png_write_tEXt(png_structp png_ptr, png_const_charp key, png_const_charp text, * any non-Latin-1 characters except for NEWLINE. ISO PNG will forbid them. * The NUL character is forbidden by PNG-1.0 through PNG-1.2 and ISO PNG. */ - png_write_chunk_data(png_ptr, (png_bytep)new_key, + png_write_chunk_data(png_ptr, (png_bytep)key, (png_size_t)(key_len + 1)); - if (text_len) + if (text_len != 0) png_write_chunk_data(png_ptr, (png_const_bytep)text, (png_size_t)text_len); png_write_chunk_end(png_ptr); - png_free(png_ptr, new_key); } #endif @@ -1689,7 +1552,6 @@ png_write_zTXt(png_structp png_ptr, png_const_charp key, png_const_charp text, { png_size_t key_len; png_byte buf; - png_charp new_key; compression_state comp; png_debug(1, "in png_write_zTXt"); @@ -1700,16 +1562,11 @@ png_write_zTXt(png_structp png_ptr, png_const_charp key, png_const_charp text, comp.input = NULL; comp.input_len = 0; - if ((key_len = png_check_keyword(png_ptr, key, &new_key)) == 0) - { - png_free(png_ptr, new_key); - return; - } + key_len = strlen(key); if (text == NULL || *text == '\0' || compression==PNG_TEXT_COMPRESSION_NONE) { - png_write_tEXt(png_ptr, new_key, text, (png_size_t)0); - png_free(png_ptr, new_key); + png_write_tEXt(png_ptr, key, text, (png_size_t)0); return; } @@ -1724,11 +1581,9 @@ png_write_zTXt(png_structp png_ptr, png_const_charp key, png_const_charp text, (png_uint_32)(key_len+text_len + 2)); /* Write key */ - png_write_chunk_data(png_ptr, (png_bytep)new_key, + png_write_chunk_data(png_ptr, (png_bytep)key, (png_size_t)(key_len + 1)); - png_free(png_ptr, new_key); - buf = (png_byte)compression; /* Write compression */ @@ -1749,8 +1604,6 @@ png_write_iTXt(png_structp png_ptr, int compression, png_const_charp key, png_const_charp lang, png_const_charp lang_key, png_const_charp text) { png_size_t lang_len, key_len, lang_key_len, text_len; - png_charp new_lang; - png_charp new_key = NULL; png_byte cbuf[2]; compression_state comp; @@ -1761,15 +1614,13 @@ png_write_iTXt(png_structp png_ptr, int compression, png_const_charp key, comp.output_ptr = NULL; comp.input = NULL; - if ((key_len = png_check_keyword(png_ptr, key, &new_key)) == 0) - return; + key_len = png_strlen(key); - if ((lang_len = png_check_keyword(png_ptr, lang, &new_lang)) == 0) - { - png_warning(png_ptr, "Empty language field in iTXt chunk"); - new_lang = NULL; + if (lang == NULL) lang_len = 0; - } + + else + lang_len = png_strlen(lang); if (lang_key == NULL) lang_key_len = 0; @@ -1804,7 +1655,7 @@ png_write_iTXt(png_structp png_ptr, int compression, png_const_charp key, * any non-Latin-1 characters except for NEWLINE. ISO PNG will forbid them. * The NUL character is forbidden by PNG-1.0 through PNG-1.2 and ISO PNG. */ - png_write_chunk_data(png_ptr, (png_bytep)new_key, (png_size_t)(key_len + 1)); + png_write_chunk_data(png_ptr, (png_bytep)key, (png_size_t)(key_len + 1)); /* Set the compression flag */ if (compression == PNG_ITXT_COMPRESSION_NONE || @@ -1820,7 +1671,7 @@ png_write_iTXt(png_structp png_ptr, int compression, png_const_charp key, png_write_chunk_data(png_ptr, cbuf, (png_size_t)2); cbuf[0] = 0; - png_write_chunk_data(png_ptr, (new_lang ? (png_const_bytep)new_lang : cbuf), + png_write_chunk_data(png_ptr, (lang ? (png_const_bytep)lang : cbuf), (png_size_t)(lang_len + 1)); png_write_chunk_data(png_ptr, (lang_key ? (png_const_bytep)lang_key : cbuf), @@ -1829,9 +1680,6 @@ png_write_iTXt(png_structp png_ptr, int compression, png_const_charp key, png_write_compressed_data_out(png_ptr, &comp, text_len); png_write_chunk_end(png_ptr); - - png_free(png_ptr, new_key); - png_free(png_ptr, new_lang); } #endif @@ -1865,7 +1713,6 @@ png_write_pCAL(png_structp png_ptr, png_charp purpose, png_int_32 X0, png_size_t purpose_len, units_len, total_len; png_size_tp params_len; png_byte buf[10]; - png_charp new_purpose; int i; png_debug1(1, "in png_write_pCAL (%d parameters)", nparams); @@ -1873,7 +1720,7 @@ png_write_pCAL(png_structp png_ptr, png_charp purpose, png_int_32 X0, if (type >= PNG_EQUATION_LAST) png_warning(png_ptr, "Unrecognized equation type for pCAL chunk"); - purpose_len = png_check_keyword(png_ptr, purpose, &new_purpose) + 1; + purpose_len = strlen(purpose) + 1; png_debug1(3, "pCAL purpose length = %d", (int)purpose_len); units_len = png_strlen(units) + (nparams == 0 ? 0 : 1); png_debug1(3, "pCAL units length = %d", (int)units_len); @@ -1895,7 +1742,7 @@ png_write_pCAL(png_structp png_ptr, png_charp purpose, png_int_32 X0, png_debug1(3, "pCAL total length = %d", (int)total_len); png_write_chunk_header(png_ptr, png_pCAL, (png_uint_32)total_len); - png_write_chunk_data(png_ptr, (png_const_bytep)new_purpose, purpose_len); + png_write_chunk_data(png_ptr, (png_const_bytep)purpose, purpose_len); png_save_int_32(buf, X0); png_save_int_32(buf + 4, X1); buf[8] = (png_byte)type; @@ -1903,8 +1750,6 @@ png_write_pCAL(png_structp png_ptr, png_charp purpose, png_int_32 X0, png_write_chunk_data(png_ptr, buf, (png_size_t)10); png_write_chunk_data(png_ptr, (png_const_bytep)units, (png_size_t)units_len); - png_free(png_ptr, new_purpose); - for (i = 0; i < nparams; i++) { png_write_chunk_data(png_ptr, (png_const_bytep)params[i], params_len[i]); @@ -2429,20 +2274,9 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) png_uint_32 mins, bpp; png_byte filter_to_do = png_ptr->do_filter; png_size_t row_bytes = row_info->rowbytes; -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - int num_p_filters = png_ptr->num_prev_filters; -#endif png_debug(1, "in png_write_find_filter"); -#ifndef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - if (png_ptr->row_number == 0 && filter_to_do == PNG_ALL_FILTERS) - { - /* These will never be selected so we need not test them. */ - filter_to_do &= ~(PNG_FILTER_UP | PNG_FILTER_PAETH); - } -#endif - /* Find out how many bytes offset each pixel is */ bpp = (row_info->pixel_depth + 7) >> 3; @@ -2494,44 +2328,6 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) sum += (v < 128) ? v : 256 - v; } -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - png_uint_32 sumhi, sumlo; - int j; - sumlo = sum & PNG_LOMASK; - sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; /* Gives us some footroom */ - - /* Reduce the sum if we match any of the previous rows */ - for (j = 0; j < num_p_filters; j++) - { - if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_NONE) - { - sumlo = (sumlo * png_ptr->filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - - sumhi = (sumhi * png_ptr->filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - } - } - - /* Factor in the cost of this filter (this is here for completeness, - * but it makes no sense to have a "cost" for the NONE filter, as - * it has the minimum possible computational cost - none). - */ - sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_NONE]) >> - PNG_COST_SHIFT; - - sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_NONE]) >> - PNG_COST_SHIFT; - - if (sumhi > PNG_HIMASK) - sum = PNG_MAXSUM; - - else - sum = (sumhi << PNG_HISHIFT) + sumlo; - } -#endif mins = sum; } @@ -2564,44 +2360,6 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) png_size_t i; int v; -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - /* We temporarily increase the "minimum sum" by the factor we - * would reduce the sum of this filter, so that we can do the - * early exit comparison without scaling the sum each time. - */ - if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int j; - png_uint_32 lmhi, lmlo; - lmlo = lmins & PNG_LOMASK; - lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK; - - for (j = 0; j < num_p_filters; j++) - { - if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_SUB) - { - lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - - lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - } - } - - lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >> - PNG_COST_SHIFT; - - lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >> - PNG_COST_SHIFT; - - if (lmhi > PNG_HIMASK) - lmins = PNG_MAXSUM; - - else - lmins = (lmhi << PNG_HISHIFT) + lmlo; - } -#endif - for (i = 0, rp = row_buf + 1, dp = png_ptr->sub_row + 1; i < bpp; i++, rp++, dp++) { @@ -2621,40 +2379,6 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) break; } -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int j; - png_uint_32 sumhi, sumlo; - sumlo = sum & PNG_LOMASK; - sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; - - for (j = 0; j < num_p_filters; j++) - { - if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_SUB) - { - sumlo = (sumlo * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - - sumhi = (sumhi * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - } - } - - sumlo = (sumlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >> - PNG_COST_SHIFT; - - sumhi = (sumhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >> - PNG_COST_SHIFT; - - if (sumhi > PNG_HIMASK) - sum = PNG_MAXSUM; - - else - sum = (sumhi << PNG_HISHIFT) + sumlo; - } -#endif - if (sum < mins) { mins = sum; @@ -2685,41 +2409,6 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) png_size_t i; int v; - -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int j; - png_uint_32 lmhi, lmlo; - lmlo = lmins & PNG_LOMASK; - lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK; - - for (j = 0; j < num_p_filters; j++) - { - if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_UP) - { - lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - - lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - } - } - - lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_UP]) >> - PNG_COST_SHIFT; - - lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_UP]) >> - PNG_COST_SHIFT; - - if (lmhi > PNG_HIMASK) - lmins = PNG_MAXSUM; - - else - lmins = (lmhi << PNG_HISHIFT) + lmlo; - } -#endif - for (i = 0, rp = row_buf + 1, dp = png_ptr->up_row + 1, pp = prev_row + 1; i < row_bytes; i++) { @@ -2731,40 +2420,6 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) break; } -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int j; - png_uint_32 sumhi, sumlo; - sumlo = sum & PNG_LOMASK; - sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; - - for (j = 0; j < num_p_filters; j++) - { - if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_UP) - { - sumlo = (sumlo * png_ptr->filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - - sumhi = (sumhi * png_ptr->filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - } - } - - sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_UP]) >> - PNG_COST_SHIFT; - - sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_UP]) >> - PNG_COST_SHIFT; - - if (sumhi > PNG_HIMASK) - sum = PNG_MAXSUM; - - else - sum = (sumhi << PNG_HISHIFT) + sumlo; - } -#endif - if (sum < mins) { mins = sum; @@ -2799,40 +2454,6 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) png_size_t i; int v; -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int j; - png_uint_32 lmhi, lmlo; - lmlo = lmins & PNG_LOMASK; - lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK; - - for (j = 0; j < num_p_filters; j++) - { - if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_AVG) - { - lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - - lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - } - } - - lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_AVG]) >> - PNG_COST_SHIFT; - - lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_AVG]) >> - PNG_COST_SHIFT; - - if (lmhi > PNG_HIMASK) - lmins = PNG_MAXSUM; - - else - lmins = (lmhi << PNG_HISHIFT) + lmlo; - } -#endif - for (i = 0, rp = row_buf + 1, dp = png_ptr->avg_row + 1, pp = prev_row + 1; i < bpp; i++) { @@ -2852,40 +2473,6 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) break; } -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int j; - png_uint_32 sumhi, sumlo; - sumlo = sum & PNG_LOMASK; - sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; - - for (j = 0; j < num_p_filters; j++) - { - if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_NONE) - { - sumlo = (sumlo * png_ptr->filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - - sumhi = (sumhi * png_ptr->filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - } - } - - sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_AVG]) >> - PNG_COST_SHIFT; - - sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_AVG]) >> - PNG_COST_SHIFT; - - if (sumhi > PNG_HIMASK) - sum = PNG_MAXSUM; - - else - sum = (sumhi << PNG_HISHIFT) + sumlo; - } -#endif - if (sum < mins) { mins = sum; @@ -2940,40 +2527,6 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) png_size_t i; int v; -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int j; - png_uint_32 lmhi, lmlo; - lmlo = lmins & PNG_LOMASK; - lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK; - - for (j = 0; j < num_p_filters; j++) - { - if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_PAETH) - { - lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - - lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - } - } - - lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_PAETH]) >> - PNG_COST_SHIFT; - - lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_PAETH]) >> - PNG_COST_SHIFT; - - if (lmhi > PNG_HIMASK) - lmins = PNG_MAXSUM; - - else - lmins = (lmhi << PNG_HISHIFT) + lmlo; - } -#endif - for (i = 0, rp = row_buf + 1, dp = png_ptr->paeth_row + 1, pp = prev_row + 1; i < bpp; i++) { @@ -3027,40 +2580,6 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) break; } -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) - { - int j; - png_uint_32 sumhi, sumlo; - sumlo = sum & PNG_LOMASK; - sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; - - for (j = 0; j < num_p_filters; j++) - { - if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_PAETH) - { - sumlo = (sumlo * png_ptr->filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - - sumhi = (sumhi * png_ptr->filter_weights[j]) >> - PNG_WEIGHT_SHIFT; - } - } - - sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_PAETH]) >> - PNG_COST_SHIFT; - - sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_PAETH]) >> - PNG_COST_SHIFT; - - if (sumhi > PNG_HIMASK) - sum = PNG_MAXSUM; - - else - sum = (sumhi << PNG_HISHIFT) + sumlo; - } -#endif - if (sum < mins) { best_row = png_ptr->paeth_row; @@ -3070,26 +2589,8 @@ png_write_find_filter(png_structp png_ptr, png_row_infop row_info) /* Do the actual writing of the filtered row data from the chosen filter. */ png_write_filtered_row(png_ptr, best_row, row_info->rowbytes+1); - -#ifdef PNG_WRITE_FILTER_SUPPORTED -#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED - /* Save the type of filter we picked this time for future calculations */ - if (png_ptr->num_prev_filters > 0) - { - int j; - - for (j = 1; j < num_p_filters; j++) - { - png_ptr->prev_filters[j] = png_ptr->prev_filters[j - 1]; - } - - png_ptr->prev_filters[j] = best_row[0]; - } -#endif -#endif /* PNG_WRITE_FILTER_SUPPORTED */ } - /* Do the actual writing of a previously filtered row. */ static void png_write_filtered_row(png_structp png_ptr, png_bytep filtered_row, diff --git a/3rdparty/readme.txt b/3rdparty/readme.txt index ca46fbd576..3c2e078497 100644 --- a/3rdparty/readme.txt +++ b/3rdparty/readme.txt @@ -14,8 +14,8 @@ libjpeg 8d (8.4) - The Independent JPEG Group's JPEG software. HAVE_JPEG preprocessor flag must be set to make highgui use libjpeg. On UNIX systems configure script takes care of it. ------------------------------------------------------------------------------------ -libpng 1.5.12 - Portable Network Graphics library. - Copyright (c) 2004, 2006-2012 Glenn Randers-Pehrson. +libpng - Portable Network Graphics library. + The license and copyright notes can be found in libpng/LICENSE. See libpng home page http://www.libpng.org for details and links to the source code From 7c64e03cc9a47f09cc441a9514483c246b4b4574 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 29 Jun 2016 12:42:57 +0300 Subject: [PATCH 013/409] libpng: re-apply patch --- 3rdparty/libpng/opencv-libpng.patch | 6 +++--- 3rdparty/libpng/pngpriv.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/3rdparty/libpng/opencv-libpng.patch b/3rdparty/libpng/opencv-libpng.patch index 6ca96392a0..75a6d3121b 100644 --- a/3rdparty/libpng/opencv-libpng.patch +++ b/3rdparty/libpng/opencv-libpng.patch @@ -1,8 +1,8 @@ diff --git a/3rdparty/libpng/pngpriv.h b/3rdparty/libpng/pngpriv.h -index 07b2b0b..e7824b8 100644 +index 8bdcccc..0f1c638 100644 --- a/3rdparty/libpng/pngpriv.h +++ b/3rdparty/libpng/pngpriv.h -@@ -360,7 +360,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; +@@ -372,7 +372,7 @@ /* Memory model/platform independent fns */ #ifndef PNG_ABORT @@ -11,7 +11,7 @@ index 07b2b0b..e7824b8 100644 # define PNG_ABORT() ExitProcess(0) # else # define PNG_ABORT() abort() -@@ -378,7 +378,7 @@ typedef PNG_CONST png_uint_16p FAR * png_const_uint_16pp; +@@ -390,7 +390,7 @@ # define png_memcpy _fmemcpy # define png_memset _fmemset #else diff --git a/3rdparty/libpng/pngpriv.h b/3rdparty/libpng/pngpriv.h index 8bdccccafb..0f1c638e57 100644 --- a/3rdparty/libpng/pngpriv.h +++ b/3rdparty/libpng/pngpriv.h @@ -372,7 +372,7 @@ /* Memory model/platform independent fns */ #ifndef PNG_ABORT -# ifdef _WINDOWS_ +# if defined(_WINDOWS_) && !defined(HAVE_WINRT) # define PNG_ABORT() ExitProcess(0) # else # define PNG_ABORT() abort() @@ -390,7 +390,7 @@ # define png_memcpy _fmemcpy # define png_memset _fmemset #else -# ifdef _WINDOWS_ /* Favor Windows over C runtime fns */ +# if defined(_WINDOWS_) && !defined(HAVE_WINRT) /* Favor Windows over C runtime fns */ # define CVT_PTR(ptr) (ptr) # define CVT_PTR_NOCHECK(ptr) (ptr) # define png_strlen lstrlenA From babaa00ac11e84a4e7932a72a69c7667f7fea725 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 29 Jun 2016 13:18:17 +0300 Subject: [PATCH 014/409] libpng: fix NEON --- 3rdparty/libpng/CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/3rdparty/libpng/CMakeLists.txt b/3rdparty/libpng/CMakeLists.txt index 76ead5d1dd..bd243c34a4 100644 --- a/3rdparty/libpng/CMakeLists.txt +++ b/3rdparty/libpng/CMakeLists.txt @@ -3,7 +3,7 @@ # # ---------------------------------------------------------------------------- -if(NEON) +if(ARM AND ENABLE_NEON AND NOT AARCH64) project(${PNG_LIBRARY} ASM) else() project(${PNG_LIBRARY}) @@ -14,9 +14,11 @@ ocv_include_directories("${CMAKE_CURRENT_SOURCE_DIR}" ${ZLIB_INCLUDE_DIR}) file(GLOB lib_srcs *.c) file(GLOB lib_hdrs *.h) -if(ARM AND NEON) +if(ARM AND ENABLE_NEON AND NOT AARCH64) list(APPEND lib_srcs arm/filter_neon.S arm/arm_init.c) - add_definitions(-DPNG_ARM_NEON) + add_definitions(-DPNG_ARM_NEON_OPT=2) +else() + add_definitions(-DPNG_ARM_NEON_OPT=0) endif() # ---------------------------------------------------------------------------------- From f29fd63f13d9b5995d8e250673132a27f0659e9b Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Fri, 13 May 2016 10:41:06 +0300 Subject: [PATCH 015/409] Enabled build with LLVM libc++ for Android. --- modules/core/include/opencv2/core/operations.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/operations.hpp b/modules/core/include/opencv2/core/operations.hpp index 0ae51c6939..7127f0ce4b 100644 --- a/modules/core/include/opencv2/core/operations.hpp +++ b/modules/core/include/opencv2/core/operations.hpp @@ -46,9 +46,9 @@ #ifndef SKIP_INCLUDES #include #include + #include #endif // SKIP_INCLUDES - #ifdef __cplusplus /////// exchange-add operation for atomic operations on reference counters /////// @@ -66,7 +66,7 @@ #if !(defined WIN32 || defined _WIN32) && (defined __i486__ || defined __i586__ || \ defined __i686__ || defined __MMX__ || defined __SSE__ || defined __ppc__) || \ - (defined __GNUC__ && defined _STLPORT_MAJOR) || \ + defined _STLPORT_MAJOR || defined _LIBCPP_VERSION || \ defined __EMSCRIPTEN__ #define CV_XADD __sync_fetch_and_add From bb5e25ba7c551c244c1f677cd4cba350097deb7c Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 16 Jun 2016 17:39:38 +0300 Subject: [PATCH 016/409] migration: github.com/opencv/opencv --- CONTRIBUTING.md | 2 +- cmake/OpenCVPackaging.cmake | 2 +- cmake/templates/changelog.Debian.in | 2 +- doc/opencv_cheatsheet.tex | 16 ++++++++-------- .../basic_geometric_drawing.rst | 2 +- .../feature_description/feature_description.rst | 2 +- .../feature_flann_matcher.rst | 2 +- .../corner_subpixeles/corner_subpixeles.rst | 2 +- .../generic_corner_detector.rst | 2 +- .../good_features_to_track.rst | 2 +- .../harris_detector/harris_detector.rst | 2 +- .../erosion_dilatation/erosion_dilatation.rst | 2 +- .../gausian_median_blur_bilateral_filter.rst | 2 +- .../back_projection/back_projection.rst | 6 +++--- .../histogram_calculation.rst | 2 +- .../histogram_comparison.rst | 2 +- .../histogram_equalization.rst | 2 +- .../template_matching/template_matching.rst | 2 +- .../imgtrans/canny_detector/canny_detector.rst | 2 +- .../imgtrans/copyMakeBorder/copyMakeBorder.rst | 2 +- .../imgproc/imgtrans/filter_2d/filter_2d.rst | 2 +- .../imgtrans/hough_circle/hough_circle.rst | 4 ++-- .../imgproc/imgtrans/hough_lines/hough_lines.rst | 4 ++-- .../laplace_operator/laplace_operator.rst | 2 +- doc/tutorials/imgproc/imgtrans/remap/remap.rst | 2 +- .../sobel_derivatives/sobel_derivatives.rst | 2 +- .../imgproc/imgtrans/warp_affine/warp_affine.rst | 2 +- .../opening_closing_hats.rst | 2 +- doc/tutorials/imgproc/pyramids/pyramids.rst | 2 +- .../bounding_rects_circles.rst | 2 +- .../bounding_rotated_ellipses.rst | 2 +- .../find_contours/find_contours.rst | 2 +- .../imgproc/shapedescriptors/hull/hull.rst | 2 +- .../imgproc/shapedescriptors/moments/moments.rst | 2 +- .../point_polygon_test/point_polygon_test.rst | 2 +- doc/tutorials/imgproc/threshold/threshold.rst | 2 +- .../clojure_dev_intro/clojure_dev_intro.rst | 2 +- .../arm_crosscompile_with_cmake.rst | 6 +++--- .../introduction/desktop_java/java_dev_intro.rst | 4 ++-- .../introduction/display_image/display_image.rst | 2 +- .../how_to_write_a_tutorial.rst | 2 +- .../introduction/ios_install/ios_install.rst | 4 ++-- .../introduction/linux_install/linux_install.rst | 6 +++--- .../windows_install/windows_install.rst | 4 ++-- .../cascade_classifier/cascade_classifier.rst | 2 +- doc/user_guide/ug_intelperc.rst | 2 +- doc/user_guide/ug_kinect.rst | 2 +- modules/core/doc/basic_structures.rst | 2 +- .../test/test_lshindex_flannbased_matcher.cpp | 2 +- modules/gpu/doc/introduction.rst | 2 +- .../doc/reading_and_writing_images_and_video.rst | 2 +- modules/highgui/doc/user_interface.rst | 2 +- modules/highgui/src/cap_libv4l.cpp | 2 +- modules/highgui/src/cap_v4l.cpp | 2 +- ...structural_analysis_and_shape_descriptors.rst | 2 +- modules/objdetect/doc/cascade_classification.rst | 2 +- modules/python/test/test_digits.py | 2 +- modules/python/test/test_gaussian_mix.py | 2 +- modules/python/test/tests_common.py | 4 ++-- .../CameraCalibrationActivity.java | 2 +- samples/cpp/facial_features.cpp | 4 ++-- samples/cpp/stereo_calib.cpp | 2 +- samples/python/chessboard.py | 2 +- samples/python/cvutils.py | 2 +- samples/python/demhist.py | 2 +- samples/python/dft.py | 2 +- samples/python/distrans.py | 2 +- samples/python/edge.py | 2 +- samples/python/ffilldemo.py | 2 +- samples/python/fitellipse.py | 2 +- samples/python/houghlines.py | 2 +- samples/python/inpaint.py | 2 +- samples/python/logpolar.py | 2 +- samples/python/morphology.py | 2 +- samples/python/numpy_array.py | 2 +- samples/python/watershed.py | 2 +- 76 files changed, 96 insertions(+), 96 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e13c89969..318e9ac8fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,3 @@ ## Contributing guidelines -All guidelines for contributing to the OpenCV repository can be found at [`How to contribute guideline`](https://github.com/Itseez/opencv/wiki/How_to_contribute). +All guidelines for contributing to the OpenCV repository can be found at [`How to contribute guideline`](https://github.com/opencv/opencv/wiki/How_to_contribute). diff --git a/cmake/OpenCVPackaging.cmake b/cmake/OpenCVPackaging.cmake index b57f46595e..92c573dd13 100644 --- a/cmake/OpenCVPackaging.cmake +++ b/cmake/OpenCVPackaging.cmake @@ -340,7 +340,7 @@ if(CPACK_GENERATOR STREQUAL "DEB") set(CHANGELOG_OUT_FILE "${CMAKE_BINARY_DIR}/deb-packages-gen/${comp}/changelog") set(CHANGELOG_OUT_FILE_GZ "${CMAKE_BINARY_DIR}/deb-packages-gen/${comp}/changelog.gz") - file(WRITE ${CHANGELOG_OUT_FILE} "Upstream changelog stub. See https://github.com/Itseez/opencv/wiki/ChangeLog") + file(WRITE ${CHANGELOG_OUT_FILE} "Upstream changelog stub. See https://github.com/opencv/opencv/wiki/ChangeLog") execute_process(COMMAND "${GZIP_TOOL}" "-ncf9" "${CHANGELOG_OUT_FILE}" OUTPUT_FILE "${CHANGELOG_OUT_FILE_GZ}" diff --git a/cmake/templates/changelog.Debian.in b/cmake/templates/changelog.Debian.in index a47ec42bc8..2133f8bbe8 100644 --- a/cmake/templates/changelog.Debian.in +++ b/cmake/templates/changelog.Debian.in @@ -1,5 +1,5 @@ @CHANGELOG_PACKAGE_NAME@ (@CPACK_PACKAGE_VERSION@) unstable; urgency=low - * Debian changelog stub. See https://github.com/Itseez/opencv/wiki/ChangeLog + * Debian changelog stub. See https://github.com/opencv/opencv/wiki/ChangeLog or release notes in user documentation for more details. -- @CPACK_PACKAGE_CONTACT@ @CHANGELOG_PACKAGE_DATE@ diff --git a/doc/opencv_cheatsheet.tex b/doc/opencv_cheatsheet.tex index d6c339916d..e9a8d2be0b 100644 --- a/doc/opencv_cheatsheet.tex +++ b/doc/opencv_cheatsheet.tex @@ -437,7 +437,7 @@ Example. Decimate image by factor of $\sqrt{2}$:\\ \texttt{\href{http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html\#watershed}{watershed()}}, \texttt{\href{http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html\#grabcut}{grabCut()}} & marker-based image segmentation algorithms. - See the samples \texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/watershed.cpp}{watershed.cpp}} and \texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/grabcut.cpp}{grabcut.cpp}}. + See the samples \texttt{\href{https://github.com/opencv/opencv/tree/master/samples/cpp/watershed.cpp}{watershed.cpp}} and \texttt{\href{https://github.com/opencv/opencv/tree/master/samples/cpp/grabcut.cpp}{grabcut.cpp}}. \end{tabular} @@ -465,7 +465,7 @@ Example. Compute Hue-Saturation histogram of an image:\\ \end{tabbing} \subsection{Contours} -See \texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/contours2.cpp}{contours2.cpp}} and \texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/squares.cpp}{squares.cpp}} +See \texttt{\href{https://github.com/opencv/opencv/tree/master/samples/cpp/contours2.cpp}{contours2.cpp}} and \texttt{\href{https://github.com/opencv/opencv/tree/master/samples/cpp/squares.cpp}{squares.cpp}} samples on what are the contours and how to use them. \section{Data I/O} @@ -559,7 +559,7 @@ samples on what are the contours and how to use them. \end{tabular} -See \texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/camshiftdemo.cpp}{camshiftdemo.cpp}} and other \href{https://github.com/Itseez/opencv/tree/master/samples/}{OpenCV samples} on how to use the GUI functions. +See \texttt{\href{https://github.com/opencv/opencv/tree/master/samples/cpp/camshiftdemo.cpp}{camshiftdemo.cpp}} and other \href{https://github.com/opencv/opencv/tree/master/samples/}{OpenCV samples} on how to use the GUI functions. \section{Camera Calibration, Pose Estimation and Depth Estimation} @@ -586,10 +586,10 @@ See \texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/camsh \end{tabular} -To calibrate a camera, you can use \texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/calibration.cpp}{calibration.cpp}} or -\texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/stereo\_calib.cpp}{stereo\_calib.cpp}} samples. +To calibrate a camera, you can use \texttt{\href{https://github.com/opencv/opencv/tree/master/samples/cpp/calibration.cpp}{calibration.cpp}} or +\texttt{\href{https://github.com/opencv/opencv/tree/master/samples/cpp/stereo\_calib.cpp}{stereo\_calib.cpp}} samples. To get the disparity maps and the point clouds, use -\texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/stereo\_match.cpp}{stereo\_match.cpp}} sample. +\texttt{\href{https://github.com/opencv/opencv/tree/master/samples/cpp/stereo\_match.cpp}{stereo\_match.cpp}} sample. \section{Object Detection} @@ -597,9 +597,9 @@ To get the disparity maps and the point clouds, use @{}p{\linewidth-\the\MyLen}@{}} \texttt{\href{http://docs.opencv.org/modules/imgproc/doc/object_detection.html\#matchtemplate}{matchTemplate}} & Compute proximity map for given template.\\ -\texttt{\href{http://docs.opencv.org/modules/objdetect/doc/cascade_classification.html\#cascadeclassifier}{CascadeClassifier}} & Viola's Cascade of Boosted classifiers using Haar or LBP features. Suits for detecting faces, facial features and some other objects without diverse textures. See \texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/c/facedetect.cpp}{facedetect.cpp}}\\ +\texttt{\href{http://docs.opencv.org/modules/objdetect/doc/cascade_classification.html\#cascadeclassifier}{CascadeClassifier}} & Viola's Cascade of Boosted classifiers using Haar or LBP features. Suits for detecting faces, facial features and some other objects without diverse textures. See \texttt{\href{https://github.com/opencv/opencv/tree/master/samples/c/facedetect.cpp}{facedetect.cpp}}\\ -\texttt{{HOGDescriptor}} & N. Dalal's object detector using Histogram-of-Oriented-Gradients (HOG) features. Suits for detecting people, cars and other objects with well-defined silhouettes. See \texttt{\href{https://github.com/Itseez/opencv/tree/master/samples/cpp/peopledetect.cpp}{peopledetect.cpp}}\\ +\texttt{{HOGDescriptor}} & N. Dalal's object detector using Histogram-of-Oriented-Gradients (HOG) features. Suits for detecting people, cars and other objects with well-defined silhouettes. See \texttt{\href{https://github.com/opencv/opencv/tree/master/samples/cpp/peopledetect.cpp}{peopledetect.cpp}}\\ \end{tabular} diff --git a/doc/tutorials/core/basic_geometric_drawing/basic_geometric_drawing.rst b/doc/tutorials/core/basic_geometric_drawing/basic_geometric_drawing.rst index 83938415c6..4a3ea3bb62 100644 --- a/doc/tutorials/core/basic_geometric_drawing/basic_geometric_drawing.rst +++ b/doc/tutorials/core/basic_geometric_drawing/basic_geometric_drawing.rst @@ -56,7 +56,7 @@ Scalar Code ===== -* This code is in your OpenCV sample folder. Otherwise you can grab it from `here `_ +* This code is in your OpenCV sample folder. Otherwise you can grab it from `here `_ Explanation ============= diff --git a/doc/tutorials/features2d/feature_description/feature_description.rst b/doc/tutorials/features2d/feature_description/feature_description.rst index 422647a1d0..2e33523c86 100644 --- a/doc/tutorials/features2d/feature_description/feature_description.rst +++ b/doc/tutorials/features2d/feature_description/feature_description.rst @@ -23,7 +23,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.rst b/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.rst index e4630c97ae..11154f7b2c 100644 --- a/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.rst +++ b/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.rst @@ -19,7 +19,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. literalinclude:: ../../../../samples/cpp/tutorial_code/features2D/SURF_FlannMatcher.cpp :language: cpp diff --git a/doc/tutorials/features2d/trackingmotion/corner_subpixeles/corner_subpixeles.rst b/doc/tutorials/features2d/trackingmotion/corner_subpixeles/corner_subpixeles.rst index a98f4d979d..424d744456 100644 --- a/doc/tutorials/features2d/trackingmotion/corner_subpixeles/corner_subpixeles.rst +++ b/doc/tutorials/features2d/trackingmotion/corner_subpixeles/corner_subpixeles.rst @@ -19,7 +19,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/features2d/trackingmotion/generic_corner_detector/generic_corner_detector.rst b/doc/tutorials/features2d/trackingmotion/generic_corner_detector/generic_corner_detector.rst index 5cfcde2e77..093a43fb29 100644 --- a/doc/tutorials/features2d/trackingmotion/generic_corner_detector/generic_corner_detector.rst +++ b/doc/tutorials/features2d/trackingmotion/generic_corner_detector/generic_corner_detector.rst @@ -20,7 +20,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. literalinclude:: ../../../../../samples/cpp/tutorial_code/TrackingMotion/cornerDetector_Demo.cpp :language: cpp diff --git a/doc/tutorials/features2d/trackingmotion/good_features_to_track/good_features_to_track.rst b/doc/tutorials/features2d/trackingmotion/good_features_to_track/good_features_to_track.rst index f7be02a111..ce7e329861 100644 --- a/doc/tutorials/features2d/trackingmotion/good_features_to_track/good_features_to_track.rst +++ b/doc/tutorials/features2d/trackingmotion/good_features_to_track/good_features_to_track.rst @@ -18,7 +18,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/features2d/trackingmotion/harris_detector/harris_detector.rst b/doc/tutorials/features2d/trackingmotion/harris_detector/harris_detector.rst index 0f6f99617d..300839f31a 100644 --- a/doc/tutorials/features2d/trackingmotion/harris_detector/harris_detector.rst +++ b/doc/tutorials/features2d/trackingmotion/harris_detector/harris_detector.rst @@ -151,7 +151,7 @@ How does it work? Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.rst b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.rst index 64f80e4aa1..5ba21f622e 100644 --- a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.rst +++ b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.rst @@ -70,7 +70,7 @@ Erosion Code ====== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/gausian_median_blur_bilateral_filter/gausian_median_blur_bilateral_filter.rst b/doc/tutorials/imgproc/gausian_median_blur_bilateral_filter/gausian_median_blur_bilateral_filter.rst index ceb3e7bee2..0a7b10384c 100644 --- a/doc/tutorials/imgproc/gausian_median_blur_bilateral_filter/gausian_median_blur_bilateral_filter.rst +++ b/doc/tutorials/imgproc/gausian_median_blur_bilateral_filter/gausian_median_blur_bilateral_filter.rst @@ -115,7 +115,7 @@ Code * Applies 4 different kinds of filters (explained in Theory) and show the filtered images sequentially * **Downloadable code**: - Click `here `_ + Click `here `_ * **Code at glance:** diff --git a/doc/tutorials/imgproc/histograms/back_projection/back_projection.rst b/doc/tutorials/imgproc/histograms/back_projection/back_projection.rst index 5062b7b300..629bdcf7bb 100644 --- a/doc/tutorials/imgproc/histograms/back_projection/back_projection.rst +++ b/doc/tutorials/imgproc/histograms/back_projection/back_projection.rst @@ -99,9 +99,9 @@ Code * **Downloadable code**: - a. Click `here `_ for the basic version (explained in this tutorial). - b. For stuff slightly fancier (using H-S histograms and floodFill to define a mask for the skin area) you can check the `improved demo `_ - c. ...or you can always check out the classical `camshiftdemo `_ in samples. + a. Click `here `_ for the basic version (explained in this tutorial). + b. For stuff slightly fancier (using H-S histograms and floodFill to define a mask for the skin area) you can check the `improved demo `_ + c. ...or you can always check out the classical `camshiftdemo `_ in samples. * **Code at glance:** diff --git a/doc/tutorials/imgproc/histograms/histogram_calculation/histogram_calculation.rst b/doc/tutorials/imgproc/histograms/histogram_calculation/histogram_calculation.rst index decbd33983..821decded2 100644 --- a/doc/tutorials/imgproc/histograms/histogram_calculation/histogram_calculation.rst +++ b/doc/tutorials/imgproc/histograms/histogram_calculation/histogram_calculation.rst @@ -82,7 +82,7 @@ Code * Plot the three histograms in a window * **Downloadable code**: - Click `here `_ + Click `here `_ * **Code at glance:** diff --git a/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.rst b/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.rst index 5eb72d64d8..536bea087e 100644 --- a/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.rst +++ b/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.rst @@ -80,7 +80,7 @@ Code * Display the numerical matching parameters obtained. * **Downloadable code**: - Click `here `_ + Click `here `_ * **Code at glance:** diff --git a/doc/tutorials/imgproc/histograms/histogram_equalization/histogram_equalization.rst b/doc/tutorials/imgproc/histograms/histogram_equalization/histogram_equalization.rst index a042714a5b..9310d2dd2c 100644 --- a/doc/tutorials/imgproc/histograms/histogram_equalization/histogram_equalization.rst +++ b/doc/tutorials/imgproc/histograms/histogram_equalization/histogram_equalization.rst @@ -83,7 +83,7 @@ Code * Display the source and equalized images in a window. * **Downloadable code**: - Click `here `_ + Click `here `_ * **Code at glance:** diff --git a/doc/tutorials/imgproc/histograms/template_matching/template_matching.rst b/doc/tutorials/imgproc/histograms/template_matching/template_matching.rst index 32878df541..c8127e92b9 100644 --- a/doc/tutorials/imgproc/histograms/template_matching/template_matching.rst +++ b/doc/tutorials/imgproc/histograms/template_matching/template_matching.rst @@ -125,7 +125,7 @@ Code * Draw a rectangle around the area corresponding to the highest match * **Downloadable code**: - Click `here `_ + Click `here `_ * **Code at glance:** diff --git a/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.rst b/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.rst index e0ee43bcc6..6280fe10db 100644 --- a/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.rst +++ b/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.rst @@ -86,7 +86,7 @@ Code * Applies the *Canny Detector* and generates a **mask** (bright lines representing the edges on a black background). * Applies the mask obtained on the original image and display it in a window. -#. The tutorial code's is shown lines below. You can also download it from `here `_ +#. The tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/imgtrans/copyMakeBorder/copyMakeBorder.rst b/doc/tutorials/imgproc/imgtrans/copyMakeBorder/copyMakeBorder.rst index 8c2c0adc31..540becf434 100644 --- a/doc/tutorials/imgproc/imgtrans/copyMakeBorder/copyMakeBorder.rst +++ b/doc/tutorials/imgproc/imgtrans/copyMakeBorder/copyMakeBorder.rst @@ -47,7 +47,7 @@ Code The user chooses either option by pressing 'c' (constant) or 'r' (replicate) * The program finishes when the user presses 'ESC' -#. The tutorial code's is shown lines below. You can also download it from `here `_ +#. The tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/imgtrans/filter_2d/filter_2d.rst b/doc/tutorials/imgproc/imgtrans/filter_2d/filter_2d.rst index 5802c82cdf..e61fc8f29e 100644 --- a/doc/tutorials/imgproc/imgtrans/filter_2d/filter_2d.rst +++ b/doc/tutorials/imgproc/imgtrans/filter_2d/filter_2d.rst @@ -72,7 +72,7 @@ Code * The filter output (with each kernel) will be shown during 500 milliseconds -#. The tutorial code's is shown lines below. You can also download it from `here `_ +#. The tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.rst b/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.rst index db63ab12ac..02f35bc9dd 100644 --- a/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.rst +++ b/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.rst @@ -40,9 +40,9 @@ Code * Display the detected circle in a window. .. |TutorialHoughCirclesSimpleDownload| replace:: here - .. _TutorialHoughCirclesSimpleDownload: https://github.com/Itseez/opencv/tree/master/samples/cpp/houghcircles.cpp + .. _TutorialHoughCirclesSimpleDownload: https://github.com/opencv/opencv/tree/master/samples/cpp/houghcircles.cpp .. |TutorialHoughCirclesFancyDownload| replace:: here - .. _TutorialHoughCirclesFancyDownload: https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/HoughCircle_Demo.cpp + .. _TutorialHoughCirclesFancyDownload: https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/HoughCircle_Demo.cpp #. The sample code that we will explain can be downloaded from |TutorialHoughCirclesSimpleDownload|_. A slightly fancier version (which shows both Hough standard and probabilistic with trackbars for changing the threshold values) can be found |TutorialHoughCirclesFancyDownload|_. diff --git a/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.rst b/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.rst index 09f6592800..ac3d67551e 100644 --- a/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.rst +++ b/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.rst @@ -89,9 +89,9 @@ Code ====== .. |TutorialHoughLinesSimpleDownload| replace:: here -.. _TutorialHoughLinesSimpleDownload: https://github.com/Itseez/opencv/tree/master/samples/cpp/houghlines.cpp +.. _TutorialHoughLinesSimpleDownload: https://github.com/opencv/opencv/tree/master/samples/cpp/houghlines.cpp .. |TutorialHoughLinesFancyDownload| replace:: here -.. _TutorialHoughLinesFancyDownload: https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp +.. _TutorialHoughLinesFancyDownload: https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp #. **What does this program do?** diff --git a/doc/tutorials/imgproc/imgtrans/laplace_operator/laplace_operator.rst b/doc/tutorials/imgproc/imgtrans/laplace_operator/laplace_operator.rst index c83dcc6b1d..0f15df1c9c 100644 --- a/doc/tutorials/imgproc/imgtrans/laplace_operator/laplace_operator.rst +++ b/doc/tutorials/imgproc/imgtrans/laplace_operator/laplace_operator.rst @@ -55,7 +55,7 @@ Code * Applies a Laplacian operator to the grayscale image and stores the output image * Display the result in a window -#. The tutorial code's is shown lines below. You can also download it from `here `_ +#. The tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/imgtrans/remap/remap.rst b/doc/tutorials/imgproc/imgtrans/remap/remap.rst index b3426c84f8..0c10996d65 100644 --- a/doc/tutorials/imgproc/imgtrans/remap/remap.rst +++ b/doc/tutorials/imgproc/imgtrans/remap/remap.rst @@ -59,7 +59,7 @@ Code * Each second, apply 1 of 4 different remapping processes to the image and display them indefinitely in a window. * Wait for the user to exit the program -#. The tutorial code's is shown lines below. You can also download it from `here `_ +#. The tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/imgtrans/sobel_derivatives/sobel_derivatives.rst b/doc/tutorials/imgproc/imgtrans/sobel_derivatives/sobel_derivatives.rst index 8d06c81bc2..3a258e5252 100644 --- a/doc/tutorials/imgproc/imgtrans/sobel_derivatives/sobel_derivatives.rst +++ b/doc/tutorials/imgproc/imgtrans/sobel_derivatives/sobel_derivatives.rst @@ -121,7 +121,7 @@ Code * Applies the *Sobel Operator* and generates as output an image with the detected *edges* bright on a darker background. -#. The tutorial code's is shown lines below. You can also download it from `here `_ +#. The tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/imgtrans/warp_affine/warp_affine.rst b/doc/tutorials/imgproc/imgtrans/warp_affine/warp_affine.rst index 49e42d32e7..feddbbcde1 100644 --- a/doc/tutorials/imgproc/imgtrans/warp_affine/warp_affine.rst +++ b/doc/tutorials/imgproc/imgtrans/warp_affine/warp_affine.rst @@ -93,7 +93,7 @@ Code * Applies a Rotation to the image after being transformed. This rotation is with respect to the image center * Waits until the user exits the program -#. The tutorial code's is shown lines below. You can also download it from `here `_ +#. The tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/opening_closing_hats/opening_closing_hats.rst b/doc/tutorials/imgproc/opening_closing_hats/opening_closing_hats.rst index 8af5112bc2..abe7b496fe 100644 --- a/doc/tutorials/imgproc/opening_closing_hats/opening_closing_hats.rst +++ b/doc/tutorials/imgproc/opening_closing_hats/opening_closing_hats.rst @@ -111,7 +111,7 @@ Black Hat Code ====== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/pyramids/pyramids.rst b/doc/tutorials/imgproc/pyramids/pyramids.rst index e9c91144c8..3a1c4d083d 100644 --- a/doc/tutorials/imgproc/pyramids/pyramids.rst +++ b/doc/tutorials/imgproc/pyramids/pyramids.rst @@ -80,7 +80,7 @@ Gaussian Pyramid Code ====== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/shapedescriptors/bounding_rects_circles/bounding_rects_circles.rst b/doc/tutorials/imgproc/shapedescriptors/bounding_rects_circles/bounding_rects_circles.rst index 92755a3ba2..214f577a38 100644 --- a/doc/tutorials/imgproc/shapedescriptors/bounding_rects_circles/bounding_rects_circles.rst +++ b/doc/tutorials/imgproc/shapedescriptors/bounding_rects_circles/bounding_rects_circles.rst @@ -21,7 +21,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/shapedescriptors/bounding_rotated_ellipses/bounding_rotated_ellipses.rst b/doc/tutorials/imgproc/shapedescriptors/bounding_rotated_ellipses/bounding_rotated_ellipses.rst index 2009ce7987..0ba6ffbc5a 100644 --- a/doc/tutorials/imgproc/shapedescriptors/bounding_rotated_ellipses/bounding_rotated_ellipses.rst +++ b/doc/tutorials/imgproc/shapedescriptors/bounding_rotated_ellipses/bounding_rotated_ellipses.rst @@ -21,7 +21,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/shapedescriptors/find_contours/find_contours.rst b/doc/tutorials/imgproc/shapedescriptors/find_contours/find_contours.rst index fd78c783a8..3f5f8c8cbb 100644 --- a/doc/tutorials/imgproc/shapedescriptors/find_contours/find_contours.rst +++ b/doc/tutorials/imgproc/shapedescriptors/find_contours/find_contours.rst @@ -19,7 +19,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/shapedescriptors/hull/hull.rst b/doc/tutorials/imgproc/shapedescriptors/hull/hull.rst index e4a97fb1aa..49b4ec3fad 100644 --- a/doc/tutorials/imgproc/shapedescriptors/hull/hull.rst +++ b/doc/tutorials/imgproc/shapedescriptors/hull/hull.rst @@ -19,7 +19,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/shapedescriptors/moments/moments.rst b/doc/tutorials/imgproc/shapedescriptors/moments/moments.rst index 7fd1a92721..0b6de7f778 100644 --- a/doc/tutorials/imgproc/shapedescriptors/moments/moments.rst +++ b/doc/tutorials/imgproc/shapedescriptors/moments/moments.rst @@ -21,7 +21,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/shapedescriptors/point_polygon_test/point_polygon_test.rst b/doc/tutorials/imgproc/shapedescriptors/point_polygon_test/point_polygon_test.rst index cd96586534..7bbacb6cbc 100644 --- a/doc/tutorials/imgproc/shapedescriptors/point_polygon_test/point_polygon_test.rst +++ b/doc/tutorials/imgproc/shapedescriptors/point_polygon_test/point_polygon_test.rst @@ -19,7 +19,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/imgproc/threshold/threshold.rst b/doc/tutorials/imgproc/threshold/threshold.rst index 4f7ab92670..66bf03423b 100644 --- a/doc/tutorials/imgproc/threshold/threshold.rst +++ b/doc/tutorials/imgproc/threshold/threshold.rst @@ -130,7 +130,7 @@ Threshold to Zero, Inverted Code ====== -The tutorial code's is shown lines below. You can also download it from `here `_ +The tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/introduction/clojure_dev_intro/clojure_dev_intro.rst b/doc/tutorials/introduction/clojure_dev_intro/clojure_dev_intro.rst index 248abdf6d2..ed4dc21d9c 100644 --- a/doc/tutorials/introduction/clojure_dev_intro/clojure_dev_intro.rst +++ b/doc/tutorials/introduction/clojure_dev_intro/clojure_dev_intro.rst @@ -51,7 +51,7 @@ OpenCV on Mac OS X: cd ~/ mkdir opt - git clone https://github.com/Itseez/opencv.git + git clone https://github.com/opencv/opencv.git cd opencv git checkout 2.4 mkdir build diff --git a/doc/tutorials/introduction/crosscompilation/arm_crosscompile_with_cmake.rst b/doc/tutorials/introduction/crosscompilation/arm_crosscompile_with_cmake.rst index 87f6d9d4d6..2b472d59a8 100644 --- a/doc/tutorials/introduction/crosscompilation/arm_crosscompile_with_cmake.rst +++ b/doc/tutorials/introduction/crosscompilation/arm_crosscompile_with_cmake.rst @@ -42,7 +42,7 @@ Getting OpenCV Source Code ========================== You can use the latest stable OpenCV version available in *sourceforge* or you can grab the latest -snapshot from our `Git repository `_. +snapshot from our `Git repository `_. Getting the Latest Stable OpenCV Version @@ -56,14 +56,14 @@ Getting the Latest Stable OpenCV Version Getting the Cutting-edge OpenCV from the Git Repository ------------------------------------------------------- -Launch Git client and clone `OpenCV repository `_ +Launch Git client and clone `OpenCV repository `_ In Linux it can be achieved with the following command in Terminal: .. code-block:: bash cd ~/ - git clone https://github.com/Itseez/opencv.git + git clone https://github.com/opencv/opencv.git Building OpenCV diff --git a/doc/tutorials/introduction/desktop_java/java_dev_intro.rst b/doc/tutorials/introduction/desktop_java/java_dev_intro.rst index d5cb31f894..983ef49d81 100644 --- a/doc/tutorials/introduction/desktop_java/java_dev_intro.rst +++ b/doc/tutorials/introduction/desktop_java/java_dev_intro.rst @@ -41,7 +41,7 @@ The most simple way to get it is downloading the appropriate package of **versio For other OSes it's required to build OpenCV from sources. Another option to get OpenCV sources is to clone `OpenCV git repository -`_. +`_. In order to build OpenCV with Java bindings you need :abbr:`JDK (Java Development Kit)` (we recommend `Oracle/Sun JDK 6 or 7 `_), `Apache Ant `_ and `Python` v2.6 or higher to be installed. @@ -53,7 +53,7 @@ Let's build OpenCV: .. code-block:: bash - git clone git://github.com/Itseez/opencv.git + git clone git://github.com/opencv/opencv.git cd opencv git checkout 2.4 mkdir build diff --git a/doc/tutorials/introduction/display_image/display_image.rst b/doc/tutorials/introduction/display_image/display_image.rst index 9e80818137..b32f18aa2c 100644 --- a/doc/tutorials/introduction/display_image/display_image.rst +++ b/doc/tutorials/introduction/display_image/display_image.rst @@ -17,7 +17,7 @@ In this tutorial you will learn how to: Source Code =========== -Download the source code from `here `_. +Download the source code from `here `_. .. literalinclude:: ../../../../samples/cpp/tutorial_code/introduction/display_image/display_image.cpp :language: cpp diff --git a/doc/tutorials/introduction/how_to_write_a_tutorial/how_to_write_a_tutorial.rst b/doc/tutorials/introduction/how_to_write_a_tutorial/how_to_write_a_tutorial.rst index e66054ec8f..c78e3c2924 100644 --- a/doc/tutorials/introduction/how_to_write_a_tutorial/how_to_write_a_tutorial.rst +++ b/doc/tutorials/introduction/how_to_write_a_tutorial/how_to_write_a_tutorial.rst @@ -64,7 +64,7 @@ In our world, achieving some tasks is possible in multiple ways. However, some o Now the best option would be to do the integration yourself. For this, you first need to have the source code. I recommend following the guides for your operating system on acquiring OpenCV sources. For Linux users look :ref:`here ` and for :ref:`Windows here `. You must also install python and sphinx with its dependencies in order to be able to build the documentation. -Once you have downloaded the repository to your hard drive you can take a look in the OpenCV directory to make sure you have both the samples and doc folder present. Anyone may download the latest source files from :file:`git://github.com/Itseez/opencv.git`. Nevertheless, not everyone has upload (commit/submit) rights. This is to protect the integrity of the library. If you plan doing more than one tutorial, and would like to have an account with commit user rights you should first register an account at http://code.opencv.org/ and then contact OpenCV administrator -delete-admin@-delete-opencv.org. Otherwise, you can just send the resulting files to us at -delete-admin@-delete-opencv.org and we'll add it. +Once you have downloaded the repository to your hard drive you can take a look in the OpenCV directory to make sure you have both the samples and doc folder present. Anyone may download the latest source files from :file:`git://github.com/opencv/opencv.git`. Nevertheless, not everyone has upload (commit/submit) rights. This is to protect the integrity of the library. If you plan doing more than one tutorial, and would like to have an account with commit user rights you should first register an account at http://code.opencv.org/ and then contact OpenCV administrator -delete-admin@-delete-opencv.org. Otherwise, you can just send the resulting files to us at -delete-admin@-delete-opencv.org and we'll add it. Format the Source Code ====================== diff --git a/doc/tutorials/introduction/ios_install/ios_install.rst b/doc/tutorials/introduction/ios_install/ios_install.rst index 2973b7ec2b..e3d36dff3c 100644 --- a/doc/tutorials/introduction/ios_install/ios_install.rst +++ b/doc/tutorials/introduction/ios_install/ios_install.rst @@ -12,14 +12,14 @@ Required Packages Getting the Cutting-edge OpenCV from Git Repository --------------------------------------------------- -Launch GIT client and clone OpenCV repository from `here `_ +Launch GIT client and clone OpenCV repository from `here `_ In MacOS it can be done using the following command in Terminal: .. code-block:: bash cd ~/ - git clone https://github.com/Itseez/opencv.git + git clone https://github.com/opencv/opencv.git Building OpenCV from Source, using CMake and Command Line diff --git a/doc/tutorials/introduction/linux_install/linux_install.rst b/doc/tutorials/introduction/linux_install/linux_install.rst index e367cd8258..e2ee91b5f2 100644 --- a/doc/tutorials/introduction/linux_install/linux_install.rst +++ b/doc/tutorials/introduction/linux_install/linux_install.rst @@ -29,7 +29,7 @@ The packages can be installed using a terminal and the following commands or by Getting OpenCV Source Code ========================== -You can use the latest stable OpenCV version available in *sourceforge* or you can grab the latest snapshot from our `Git repository `_. +You can use the latest stable OpenCV version available in *sourceforge* or you can grab the latest snapshot from our `Git repository `_. Getting the Latest Stable OpenCV Version ---------------------------------------- @@ -42,14 +42,14 @@ Getting the Latest Stable OpenCV Version Getting the Cutting-edge OpenCV from the Git Repository ------------------------------------------------------- -Launch Git client and clone `OpenCV repository `_ +Launch Git client and clone `OpenCV repository `_ In Linux it can be achieved with the following command in Terminal: .. code-block:: bash cd ~/ - git clone https://github.com/Itseez/opencv.git + git clone https://github.com/opencv/opencv.git Building OpenCV from Source Using CMake, Using the Command Line diff --git a/doc/tutorials/introduction/windows_install/windows_install.rst b/doc/tutorials/introduction/windows_install/windows_install.rst index abb39cd13e..3fda01c086 100644 --- a/doc/tutorials/introduction/windows_install/windows_install.rst +++ b/doc/tutorials/introduction/windows_install/windows_install.rst @@ -46,7 +46,7 @@ You may find the content of this tutorial also inside the following videos: `Par .. warning:: These videos above are long-obsolete and contain inaccurate information. Be careful, since solutions described in those videos are no longer supported and may even break your install. -If you are building your own libraries you can take the source files from our `Git repository `_. +If you are building your own libraries you can take the source files from our `Git repository `_. Building the OpenCV library from scratch requires a couple of tools installed beforehand: @@ -128,7 +128,7 @@ Building the library #. Choose a directory in your file system, where you will download the OpenCV libraries to. I recommend creating a new one that has short path and no special charachters in it, for example :file:`D:/OpenCV`. For this tutorial I'll suggest you do so. If you use your own path and know, what you're doing -- it's OK. - a) Clone the repository to the selected directory. After clicking *Clone* button, a window will appear where you can select from what repository you want to download source files (https://github.com/Itseez/opencv.git) and to what directory (:file:`D:/OpenCV`). + a) Clone the repository to the selected directory. After clicking *Clone* button, a window will appear where you can select from what repository you want to download source files (https://github.com/opencv/opencv.git) and to what directory (:file:`D:/OpenCV`). #) Push the OK button and be patient as the repository is quite a heavy download. It will take some time depending on your Internet connection. diff --git a/doc/tutorials/objdetect/cascade_classifier/cascade_classifier.rst b/doc/tutorials/objdetect/cascade_classifier/cascade_classifier.rst index ea229ca9de..1338249147 100644 --- a/doc/tutorials/objdetect/cascade_classifier/cascade_classifier.rst +++ b/doc/tutorials/objdetect/cascade_classifier/cascade_classifier.rst @@ -22,7 +22,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ . The second version (using LBP for face detection) can be `found here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ . The second version (using LBP for face detection) can be `found here `_ .. code-block:: cpp diff --git a/doc/user_guide/ug_intelperc.rst b/doc/user_guide/ug_intelperc.rst index f08d8f2c64..04a10a1a95 100644 --- a/doc/user_guide/ug_intelperc.rst +++ b/doc/user_guide/ug_intelperc.rst @@ -76,4 +76,4 @@ Since two types of sensor's data generators are supported (image generator and d For more information please refer to the example of usage intelperc_capture.cpp_ in ``opencv/samples/cpp`` folder. -.. _intelperc_capture.cpp: https://github.com/Itseez/opencv/tree/master/samples/cpp/intelperc_capture.cpp +.. _intelperc_capture.cpp: https://github.com/opencv/opencv/tree/master/samples/cpp/intelperc_capture.cpp diff --git a/doc/user_guide/ug_kinect.rst b/doc/user_guide/ug_kinect.rst index cb15ac42db..15e3274210 100644 --- a/doc/user_guide/ug_kinect.rst +++ b/doc/user_guide/ug_kinect.rst @@ -130,4 +130,4 @@ Flags specifing the needed generator type must be used in combination with parti For more information please refer to the example of usage openni_capture.cpp_ in ``opencv/samples/cpp`` folder. -.. _openni_capture.cpp: https://github.com/Itseez/opencv/tree/master/samples/cpp/openni_capture.cpp +.. _openni_capture.cpp: https://github.com/opencv/opencv/tree/master/samples/cpp/openni_capture.cpp diff --git a/modules/core/doc/basic_structures.rst b/modules/core/doc/basic_structures.rst index 9b37bd38f7..6344182a19 100644 --- a/modules/core/doc/basic_structures.rst +++ b/modules/core/doc/basic_structures.rst @@ -2898,5 +2898,5 @@ The above methods are usually enough for users. If you want to make your own alg * Make a class and specify ``Algorithm`` as its base class. * The algorithm parameters should be the class members. See ``Algorithm::get()`` for the list of possible types of the parameters. * Add public virtual method ``AlgorithmInfo* info() const;`` to your class. - * Add constructor function, ``AlgorithmInfo`` instance and implement the ``info()`` method. The simplest way is to take https://github.com/Itseez/opencv/tree/master/modules/ml/src/ml_init.cpp as the reference and modify it according to the list of your parameters. + * Add constructor function, ``AlgorithmInfo`` instance and implement the ``info()`` method. The simplest way is to take https://github.com/opencv/opencv/tree/master/modules/ml/src/ml_init.cpp as the reference and modify it according to the list of your parameters. * Add some public function (e.g. ``initModule_()``) that calls info() of your algorithm and put it into the same source file as ``info()`` implementation. This is to force C++ linker to include this object file into the target application. See ``Algorithm::create()`` for details. diff --git a/modules/features2d/test/test_lshindex_flannbased_matcher.cpp b/modules/features2d/test/test_lshindex_flannbased_matcher.cpp index d1c6ea7e43..29f9dbc574 100644 --- a/modules/features2d/test/test_lshindex_flannbased_matcher.cpp +++ b/modules/features2d/test/test_lshindex_flannbased_matcher.cpp @@ -31,7 +31,7 @@ For OpenCV2.4/OpenCV3.0 Test for Pull Request # 3829 - https://github.com/Itseez/opencv/pull/3829 + https://github.com/opencv/opencv/pull/3829 This test code creates brute force matcher for accuracy of reference, and the test target matcher. Then, add() and train() transformed query image descriptors, and some outlier images descriptors to both matchers. diff --git a/modules/gpu/doc/introduction.rst b/modules/gpu/doc/introduction.rst index 776ed2fb48..8b4eb04b8b 100644 --- a/modules/gpu/doc/introduction.rst +++ b/modules/gpu/doc/introduction.rst @@ -59,4 +59,4 @@ While developing algorithms for multiple GPUs, note a data passing overhead. For With this algorithm, a dual GPU gave a 180 % performance increase comparing to the single Fermi GPU. For a source code example, see -https://github.com/Itseez/opencv/tree/master/samples/gpu/. +https://github.com/opencv/opencv/tree/master/samples/gpu/. diff --git a/modules/highgui/doc/reading_and_writing_images_and_video.rst b/modules/highgui/doc/reading_and_writing_images_and_video.rst index a17ad90ebd..c4ac793bca 100644 --- a/modules/highgui/doc/reading_and_writing_images_and_video.rst +++ b/modules/highgui/doc/reading_and_writing_images_and_video.rst @@ -319,7 +319,7 @@ The methods/functions grab the next frame from video file or camera and return t The primary use of the function is in multi-camera environments, especially when the cameras do not have hardware synchronization. That is, you call ``VideoCapture::grab()`` for each camera and after that call the slower method ``VideoCapture::retrieve()`` to decode and get frame from each camera. This way the overhead on demosaicing or motion jpeg decompression etc. is eliminated and the retrieved frames from different cameras will be closer in time. -Also, when a connected camera is multi-head (for example, a stereo camera or a Kinect device), the correct way of retrieving data from it is to call `VideoCapture::grab` first and then call :ocv:func:`VideoCapture::retrieve` one or more times with different values of the ``channel`` parameter. See https://github.com/Itseez/opencv/tree/master/samples/cpp/openni_capture.cpp +Also, when a connected camera is multi-head (for example, a stereo camera or a Kinect device), the correct way of retrieving data from it is to call `VideoCapture::grab` first and then call :ocv:func:`VideoCapture::retrieve` one or more times with different values of the ``channel`` parameter. See https://github.com/opencv/opencv/tree/master/samples/cpp/openni_capture.cpp VideoCapture::retrieve diff --git a/modules/highgui/doc/user_interface.rst b/modules/highgui/doc/user_interface.rst index bc241b81d4..952dead05c 100644 --- a/modules/highgui/doc/user_interface.rst +++ b/modules/highgui/doc/user_interface.rst @@ -224,7 +224,7 @@ Sets mouse handler for the specified window :param winname: Window name - :param onMouse: Mouse callback. See OpenCV samples, such as https://github.com/Itseez/opencv/tree/master/samples/cpp/ffilldemo.cpp, on how to specify and use the callback. + :param onMouse: Mouse callback. See OpenCV samples, such as https://github.com/opencv/opencv/tree/master/samples/cpp/ffilldemo.cpp, on how to specify and use the callback. :param userdata: The optional parameter passed to the callback. diff --git a/modules/highgui/src/cap_libv4l.cpp b/modules/highgui/src/cap_libv4l.cpp index ff153042d1..5f161a535b 100644 --- a/modules/highgui/src/cap_libv4l.cpp +++ b/modules/highgui/src/cap_libv4l.cpp @@ -16,7 +16,7 @@ For Release: OpenCV-Linux Beta4 opencv-0.9.6 Tested On: LMLBT44 with 8 video inputs Problems? Post your questions at answers.opencv.org, Report bugs at code.opencv.org, - Submit your fixes at https://github.com/Itseez/opencv/ + Submit your fixes at https://github.com/opencv/opencv/ Patched Comments: TW: The cv cam utils that came with the initial release of OpenCV for LINUX Beta4 diff --git a/modules/highgui/src/cap_v4l.cpp b/modules/highgui/src/cap_v4l.cpp index 2599cada80..4093219ca6 100644 --- a/modules/highgui/src/cap_v4l.cpp +++ b/modules/highgui/src/cap_v4l.cpp @@ -16,7 +16,7 @@ For Release: OpenCV-Linux Beta4 opencv-0.9.6 Tested On: LMLBT44 with 8 video inputs Problems? Post your questions at answers.opencv.org, Report bugs at code.opencv.org, - Submit your fixes at https://github.com/Itseez/opencv/ + Submit your fixes at https://github.com/opencv/opencv/ Patched Comments: TW: The cv cam utils that came with the initial release of OpenCV for LINUX Beta4 diff --git a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst index 512f4bcddf..dfbdfa917c 100644 --- a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst +++ b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst @@ -296,7 +296,7 @@ Approximates a polygonal curve(s) with the specified precision. The functions ``approxPolyDP`` approximate a curve or a polygon with another curve/polygon with less vertices so that the distance between them is less or equal to the specified precision. It uses the Douglas-Peucker algorithm http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm -See https://github.com/Itseez/opencv/tree/master/samples/cpp/contours2.cpp for the function usage model. +See https://github.com/opencv/opencv/tree/master/samples/cpp/contours2.cpp for the function usage model. ApproxChains diff --git a/modules/objdetect/doc/cascade_classification.rst b/modules/objdetect/doc/cascade_classification.rst index a416392be7..265d2a439e 100644 --- a/modules/objdetect/doc/cascade_classification.rst +++ b/modules/objdetect/doc/cascade_classification.rst @@ -21,7 +21,7 @@ The word "cascade" in the classifier name means that the resultant classifier co The feature used in a particular classifier is specified by its shape (1a, 2b etc.), position within the region of interest and the scale (this scale is not the same as the scale used at the detection stage, though these two scales are multiplied). For example, in the case of the third line feature (2c) the response is calculated as the difference between the sum of image pixels under the rectangle covering the whole feature (including the two white stripes and the black stripe in the middle) and the sum of the image pixels under the black stripe multiplied by 3 in order to compensate for the differences in the size of areas. The sums of pixel values over a rectangular regions are calculated rapidly using integral images (see below and the :ocv:func:`integral` description). To see the object detector at work, have a look at the facedetect demo: -https://github.com/Itseez/opencv/tree/master/samples/cpp/dbt_face_detection.cpp +https://github.com/opencv/opencv/tree/master/samples/cpp/dbt_face_detection.cpp The following reference is for the detection part only. There is a separate application called ``opencv_traincascade`` that can train a cascade of boosted classifiers from a set of samples. diff --git a/modules/python/test/test_digits.py b/modules/python/test/test_digits.py index e30361e0eb..3236276173 100644 --- a/modules/python/test/test_digits.py +++ b/modules/python/test/test_digits.py @@ -58,7 +58,7 @@ def deskew(img): class StatModel(object): def load(self, fn): - self.model.load(fn) # Known bug: https://github.com/Itseez/opencv/issues/4969 + self.model.load(fn) # Known bug: https://github.com/opencv/opencv/issues/4969 def save(self, fn): self.model.save(fn) diff --git a/modules/python/test/test_gaussian_mix.py b/modules/python/test/test_gaussian_mix.py index 498cf3862f..0a146f3d68 100644 --- a/modules/python/test/test_gaussian_mix.py +++ b/modules/python/test/test_gaussian_mix.py @@ -41,7 +41,7 @@ class gaussian_mix_test(NewOpenCVTests): em = cv2.EM(cluster_n,cv2.EM_COV_MAT_GENERIC) em.train(points) means = em.getMat("means") - covs = em.getMatVector("covs") # Known bug: https://github.com/Itseez/opencv/pull/4232 + covs = em.getMatVector("covs") # Known bug: https://github.com/opencv/opencv/pull/4232 found_distrs = zip(means, covs) matches_count = 0 diff --git a/modules/python/test/tests_common.py b/modules/python/test/tests_common.py index a32f2889c2..791773a7cf 100644 --- a/modules/python/test/tests_common.py +++ b/modules/python/test/tests_common.py @@ -21,7 +21,7 @@ class OpenCVTests(unittest.TestCase): # path to local repository folder containing 'samples' folder repoPath = None # github repository url - repoUrl = 'https://raw.github.com/Itseez/opencv/2.4' + repoUrl = 'https://raw.github.com/opencv/opencv/2.4' # path to local folder containing 'camera_calibration.tar.gz' dataPath = None # data url @@ -126,7 +126,7 @@ class NewOpenCVTests(unittest.TestCase): repoPath = None extraTestDataPath = None # github repository url - repoUrl = 'https://raw.github.com/Itseez/opencv/master' + repoUrl = 'https://raw.github.com/opencv/opencv/master' def get_sample(self, filename, iscolor = cv2.IMREAD_COLOR): if not filename in self.image_cache: diff --git a/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrationActivity.java b/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrationActivity.java index 33c9bbbf46..cb1d52be42 100644 --- a/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrationActivity.java +++ b/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrationActivity.java @@ -2,7 +2,7 @@ // http://docs.opencv.org/doc/tutorials/calib3d/camera_calibration/camera_calibration.html // // It uses standard OpenCV asymmetric circles grid pattern 11x4: -// https://github.com/Itseez/opencv/blob/2.4/doc/acircles_pattern.png. +// https://github.com/opencv/opencv/blob/2.4/doc/acircles_pattern.png. // The results are the camera matrix and 5 distortion coefficients. // // Tap on highlighted pattern to capture pattern corners for calibration. diff --git a/samples/cpp/facial_features.cpp b/samples/cpp/facial_features.cpp index 9cfbd85c7f..490f492cfd 100644 --- a/samples/cpp/facial_features.cpp +++ b/samples/cpp/facial_features.cpp @@ -117,10 +117,10 @@ static void help() "\tThis will detect only the face in image.jpg.\n"; cout << " \n\nThe classifiers for face and eyes can be downloaded from : " - " \nhttps://github.com/Itseez/opencv/tree/master/data/haarcascades"; + " \nhttps://github.com/opencv/opencv/tree/master/data/haarcascades"; cout << "\n\nThe classifiers for nose and mouth can be downloaded from : " - " \nhttps://github.com/Itseez/opencv_contrib/tree/master/modules/face/data/cascades\n"; + " \nhttps://github.com/opencv/opencv_contrib/tree/master/modules/face/data/cascades\n"; } static void detectFaces(Mat& img, vector >& faces, string cascade_path) diff --git a/samples/cpp/stereo_calib.cpp b/samples/cpp/stereo_calib.cpp index 6ae20c75da..64f67a4778 100644 --- a/samples/cpp/stereo_calib.cpp +++ b/samples/cpp/stereo_calib.cpp @@ -19,7 +19,7 @@ Online docs: http://docs.opencv.org Q&A forum: http://answers.opencv.org Issue tracker: http://code.opencv.org - GitHub: https://github.com/Itseez/opencv/ + GitHub: https://github.com/opencv/opencv/ ************************************************** */ #include "opencv2/calib3d/calib3d.hpp" diff --git a/samples/python/chessboard.py b/samples/python/chessboard.py index c108784930..9eaab17334 100755 --- a/samples/python/chessboard.py +++ b/samples/python/chessboard.py @@ -15,7 +15,7 @@ if __name__ == "__main__": im = cv.LoadImageM(fileName, False) im3 = cv.LoadImageM(fileName, True) except: # if local copy cannot be opened, try downloading it - url = 'https://raw.github.com/Itseez/opencv/master/samples/cpp/left01.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/cpp/left01.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/cvutils.py b/samples/python/cvutils.py index 4827d9d657..025c7c4cea 100755 --- a/samples/python/cvutils.py +++ b/samples/python/cvutils.py @@ -11,7 +11,7 @@ def load_sample(name=None): try: img0 = cv.LoadImage(name, cv.CV_LOAD_IMAGE_COLOR) except IOError: - urlbase = 'https://raw.github.com/Itseez/opencv/master/samples/c/' + urlbase = 'https://raw.github.com/opencv/opencv/master/samples/c/' file = name.split('/')[-1] filedata = urllib2.urlopen(urlbase+file).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) diff --git a/samples/python/demhist.py b/samples/python/demhist.py index 59f7e1f782..11745dbeb8 100755 --- a/samples/python/demhist.py +++ b/samples/python/demhist.py @@ -68,7 +68,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: src_image = cv.GetMat(cv.LoadImage(sys.argv[1], 0)) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/baboon.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/baboon.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/dft.py b/samples/python/dft.py index 2bce090da1..7cbec960a8 100755 --- a/samples/python/dft.py +++ b/samples/python/dft.py @@ -51,7 +51,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: im = cv.LoadImage( sys.argv[1], cv.CV_LOAD_IMAGE_GRAYSCALE) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/baboon.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/baboon.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/distrans.py b/samples/python/distrans.py index c7a61c6dbb..6fc92c52bb 100755 --- a/samples/python/distrans.py +++ b/samples/python/distrans.py @@ -42,7 +42,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: gray = cv.LoadImage(sys.argv[1], cv.CV_LOAD_IMAGE_GRAYSCALE) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/stuff.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/stuff.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/edge.py b/samples/python/edge.py index 194d2474f9..84ab838a0a 100755 --- a/samples/python/edge.py +++ b/samples/python/edge.py @@ -32,7 +32,7 @@ if __name__ == '__main__': if len(sys.argv) > 1: im = cv.LoadImage( sys.argv[1], cv.CV_LOAD_IMAGE_COLOR) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/fruits.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/fruits.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/ffilldemo.py b/samples/python/ffilldemo.py index 1ad3755c20..29eda6df4b 100755 --- a/samples/python/ffilldemo.py +++ b/samples/python/ffilldemo.py @@ -73,7 +73,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: im = cv.LoadImage( sys.argv[1], cv.CV_LOAD_IMAGE_COLOR) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/fruits.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/fruits.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/fitellipse.py b/samples/python/fitellipse.py index 2a1673b7f0..f77ba13c70 100755 --- a/samples/python/fitellipse.py +++ b/samples/python/fitellipse.py @@ -82,7 +82,7 @@ if __name__ == '__main__': if len(sys.argv) > 1: source_image = cv.LoadImage(sys.argv[1], cv.CV_LOAD_IMAGE_GRAYSCALE) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/stuff.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/stuff.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/houghlines.py b/samples/python/houghlines.py index 00d8b1b06c..0625e7361c 100755 --- a/samples/python/houghlines.py +++ b/samples/python/houghlines.py @@ -14,7 +14,7 @@ if __name__ == "__main__": filename = sys.argv[1] src = cv.LoadImage(filename, cv.CV_LOAD_IMAGE_GRAYSCALE) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/cpp/building.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/cpp/building.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/inpaint.py b/samples/python/inpaint.py index fd2d5f22c4..9c1b71ee12 100755 --- a/samples/python/inpaint.py +++ b/samples/python/inpaint.py @@ -27,7 +27,7 @@ if __name__=="__main__": if len(sys.argv) > 1: img0 = cv.LoadImage( sys.argv[1], cv.CV_LOAD_IMAGE_COLOR) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/fruits.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/fruits.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/logpolar.py b/samples/python/logpolar.py index 13ee5d94ee..5e5a6996dd 100755 --- a/samples/python/logpolar.py +++ b/samples/python/logpolar.py @@ -23,7 +23,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: src = cv.LoadImage( sys.argv[1], cv.CV_LOAD_IMAGE_COLOR) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/fruits.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/fruits.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/morphology.py b/samples/python/morphology.py index a1817bf304..2f6abce1dc 100755 --- a/samples/python/morphology.py +++ b/samples/python/morphology.py @@ -31,7 +31,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: src = cv.LoadImage(sys.argv[1], cv.CV_LOAD_IMAGE_COLOR) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/fruits.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/fruits.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/numpy_array.py b/samples/python/numpy_array.py index 76ef14201f..db0ac293cb 100755 --- a/samples/python/numpy_array.py +++ b/samples/python/numpy_array.py @@ -22,7 +22,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: img0 = cv.LoadImageM( sys.argv[1], cv.CV_LOAD_IMAGE_COLOR) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/lena.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/lena.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) diff --git a/samples/python/watershed.py b/samples/python/watershed.py index 5134ed34e9..c989ac19aa 100755 --- a/samples/python/watershed.py +++ b/samples/python/watershed.py @@ -27,7 +27,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: img0 = cv.LoadImage( sys.argv[1], cv.CV_LOAD_IMAGE_COLOR) else: - url = 'https://raw.github.com/Itseez/opencv/master/samples/c/fruits.jpg' + url = 'https://raw.github.com/opencv/opencv/master/samples/c/fruits.jpg' filedata = urllib2.urlopen(url).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) From 9e652161081fc9f6da0ddd0f4330f259bbab94b9 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Fri, 15 Jul 2016 15:11:36 +0300 Subject: [PATCH 017/409] set threshold to 1 in sanity test for GPU rotate on 8UC1 to fix difference with CUDA 8.0 results --- modules/gpu/perf/perf_imgproc.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/gpu/perf/perf_imgproc.cpp b/modules/gpu/perf/perf_imgproc.cpp index 4117cc6367..2e0aa9f7b6 100644 --- a/modules/gpu/perf/perf_imgproc.cpp +++ b/modules/gpu/perf/perf_imgproc.cpp @@ -1373,7 +1373,10 @@ PERF_TEST_P(Sz_Depth_Cn_Inter, ImgProc_Rotate, TEST_CYCLE() cv::gpu::rotate(d_src, dst, size, 30.0, 0, 0, interpolation); - GPU_SANITY_CHECK(dst, 1e-3, ERROR_RELATIVE); + if (depth == CV_8U) + GPU_SANITY_CHECK(dst, 1); + else + GPU_SANITY_CHECK(dst, 1e-3, ERROR_RELATIVE); } else { From 7b2ef647aac2ad54a5152146af5270fafea9c31b Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Sat, 16 Jul 2016 14:26:31 +0300 Subject: [PATCH 018/409] fixed memory corruption when normal dist. params have less channels than target matrix; test added --- modules/core/src/rand.cpp | 14 +++++++------- modules/core/test/test_rand.cpp | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/modules/core/src/rand.cpp b/modules/core/src/rand.cpp index 73dff184b5..eed4684a54 100644 --- a/modules/core/src/rand.cpp +++ b/modules/core/src/rand.cpp @@ -622,7 +622,7 @@ void RNG::fill( InputOutputArray _mat, int disttype, int ptype = depth == CV_64F ? CV_64F : CV_32F; int esz = (int)CV_ELEM_SIZE(ptype); - if( _param1.isContinuous() && _param1.type() == ptype ) + if( _param1.isContinuous() && _param1.type() == ptype && n1 >= cn) mean = _param1.data; else { @@ -635,18 +635,18 @@ void RNG::fill( InputOutputArray _mat, int disttype, for( j = n1*esz; j < cn*esz; j++ ) mean[j] = mean[j - n1*esz]; - if( _param2.isContinuous() && _param2.type() == ptype ) + if( _param2.isContinuous() && _param2.type() == ptype && n2 >= cn) stddev = _param2.data; else { - Mat tmp(_param2.size(), ptype, parambuf + cn); + Mat tmp(_param2.size(), ptype, parambuf + MAX(n1, cn)); _param2.convertTo(tmp, ptype); - stddev = (uchar*)(parambuf + cn); + stddev = (uchar*)(parambuf + MAX(n1, cn)); } - if( n1 < cn ) - for( j = n1*esz; j < cn*esz; j++ ) - stddev[j] = stddev[j - n1*esz]; + if( n2 < cn ) + for( j = n2*esz; j < cn*esz; j++ ) + stddev[j] = stddev[j - n2*esz]; stdmtx = _param2.rows == cn && _param2.cols == cn; scaleFunc = randnScaleTab[depth]; diff --git a/modules/core/test/test_rand.cpp b/modules/core/test/test_rand.cpp index 1d9b3dd0d1..5ce5ae85ac 100644 --- a/modules/core/test/test_rand.cpp +++ b/modules/core/test/test_rand.cpp @@ -365,3 +365,20 @@ TEST(Core_RNG_MT19937, regression) ASSERT_EQ(expected[i], actual[i]); } } + + +TEST(Core_Rand, Regression_Stack_Corruption) +{ + int bufsz = 128; //enough for 14 doubles + AutoBuffer buffer(bufsz); + size_t offset = 0; + cv::Mat_ x(2, 3, (cv::Point2d*)(buffer+offset)); offset += x.total()*x.elemSize(); + double& param1 = *(double*)(buffer+offset); offset += sizeof(double); + double& param2 = *(double*)(buffer+offset); offset += sizeof(double); + param1 = -9; param2 = 2; + + cv::theRNG().fill(x, cv::RNG::NORMAL, param1, param2); + + ASSERT_EQ(param1, -9); + ASSERT_EQ(param2, 2); +} From 87d06eab828bc6fce18fc7762f5ad9b7359dac64 Mon Sep 17 00:00:00 2001 From: catree Date: Sun, 17 Jul 2016 23:06:20 +0200 Subject: [PATCH 019/409] Add bib reference for overall hitrate and false alarm rate for the train cascade user guide (OpenCV 2.4). --- doc/opencv.bib | 1 + doc/user_guide/ug_traincascade.rst | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/opencv.bib b/doc/opencv.bib index 83f986855a..8ecb0a98c1 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -316,6 +316,7 @@ # '''[Teh89]''' C.H. Teh, R.T. Chin. On the Detection of Dominant Points on Digital Curves. - IEEE Tr. PAMI, 1989, v.11, No.8, p. 859-872. # '''[Trucco98]''' Emanuele Trucco, Alessandro Verri. Introductory Techniques for 3-D Computer Vision. Prentice Hall, Inc., 1998. # '''[Viola01]''' Paul Viola and Michael J. Jones. Rapid Object Detection using a Boosted Cascade of Simple Features. IEEE CVPR, 2001.<
> The paper is available online at http://www.ai.mit.edu/people/viola/ +# '''[Viola04]''' Paul Viola and Michael J. Jones. Robust real-time face detection. International Journal of Computer Vision, 57(2):137–154, 2004.<
> The paper is available online at http://www.vision.caltech.edu/html-files/EE148-2005-Spring/pprs/viola04ijcv.pdf # '''[Welch95]''' Greg Welch, Gary Bishop. An Introduction To the Kalman Filter. Technical Report TR95-041, University of North Carolina at Chapel Hill, 1995.<
> Online version is available at http://www.cs.unc.edu/~welch/kalman/kalmanIntro.html # '''[Williams92]''' D. J. Williams and M. Shah. A Fast Algorithm for Active Contours and Curvature Estimation. CVGIP: Image Understanding, Vol. 55, No. 1, pp. 14-26, Jan., 1992. http://www.cs.ucf.edu/~vision/papers/shah/92/WIS92A.pdf. # '''[Yuen03]''' H.K. Yuen, J. Princen, J. Illingworth and J. Kittler. Comparative study of Hough Transform methods for circle finding.<
>http://www.sciencedirect.com/science/article/B6V09-48TCV4N-5Y/2/91f551d124777f7a4cf7b18325235673 diff --git a/doc/user_guide/ug_traincascade.rst b/doc/user_guide/ug_traincascade.rst index 49cdbd698a..a29ad488da 100644 --- a/doc/user_guide/ug_traincascade.rst +++ b/doc/user_guide/ug_traincascade.rst @@ -326,11 +326,11 @@ Command line arguments of ``opencv_traincascade`` application grouped by purpose * ``-minHitRate `` - Minimal desired hit rate for each stage of the classifier. Overall hit rate may be estimated as (min_hit_rate^number_of_stages). + Minimal desired hit rate for each stage of the classifier. Overall hit rate may be estimated as (min_hit_rate ^ number_of_stages) [Viola2004]_. * ``-maxFalseAlarmRate `` - Maximal desired false alarm rate for each stage of the classifier. Overall false alarm rate may be estimated as (max_false_alarm_rate^number_of_stages). + Maximal desired false alarm rate for each stage of the classifier. Overall false alarm rate may be estimated as (max_false_alarm_rate ^ number_of_stages) [Viola2004]_. * ``-weightTrimRate `` @@ -362,7 +362,9 @@ After the ``opencv_traincascade`` application has finished its work, the trained Training is finished and you can test you cascade classifier! -.. [Viola2001] Paul Viola, Michael Jones. *Rapid Object Detection using a Boosted Cascade of Simple Features*. Conference on Computer Vision and Pattern Recognition (CVPR), 2001, pp. 511-518. +.. [Viola2001] Paul Viola, Michael J. Jones. *Rapid Object Detection using a Boosted Cascade of Simple Features*. Conference on Computer Vision and Pattern Recognition (CVPR), 2001, pp. 511-518. + +.. [Viola2004] Paul Viola, Michael J. Jones. *Robust real-time face detection*. International Journal of Computer Vision, 57(2):137–154, 2004. .. [Rainer2002] Rainer Lienhart and Jochen Maydt. *An Extended Set of Haar-like Features for Rapid Object Detection*. Submitted to ICIP2002. From d99821aa44682d28c980351abe227c5ef87a5d41 Mon Sep 17 00:00:00 2001 From: Philippe FOUBERT Date: Sun, 17 Jul 2016 12:22:37 +0200 Subject: [PATCH 020/409] Resolves issue #6931 --- modules/core/include/opencv2/core/version.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/version.hpp b/modules/core/include/opencv2/core/version.hpp index 2dbb3c3403..39e66583d2 100644 --- a/modules/core/include/opencv2/core/version.hpp +++ b/modules/core/include/opencv2/core/version.hpp @@ -55,7 +55,7 @@ #define CVAUX_STR_EXP(__A) #__A #define CVAUX_STR(__A) CVAUX_STR_EXP(__A) -#define CVAUX_STRW_EXP(__A) L#__A +#define CVAUX_STRW_EXP(__A) L ## #__A #define CVAUX_STRW(__A) CVAUX_STRW_EXP(__A) #if CV_VERSION_REVISION From 0af9bf457ae81f76b45f982e8816a4ba1b0173f8 Mon Sep 17 00:00:00 2001 From: lewgate Date: Sun, 31 Jul 2016 16:22:53 +0800 Subject: [PATCH 021/409] Fix a latent bug for seam_finders.cpp when computing the source and destination points of the seam --- modules/stitching/src/seam_finders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/stitching/src/seam_finders.cpp b/modules/stitching/src/seam_finders.cpp index 234f2047e6..b01e82d712 100644 --- a/modules/stitching/src/seam_finders.cpp +++ b/modules/stitching/src/seam_finders.cpp @@ -630,7 +630,7 @@ bool DpSeamFinder::getSeamTips(int comp1, int comp2, Point &p1, Point &p2) { double size1 = static_cast(points[i].size()), size2 = static_cast(points[j].size()); double cx1 = cvRound(sum[i].x / size1), cy1 = cvRound(sum[i].y / size1); - double cx2 = cvRound(sum[j].x / size2), cy2 = cvRound(sum[j].y / size1); + double cx2 = cvRound(sum[j].x / size2), cy2 = cvRound(sum[j].y / size2); double dist = (cx1 - cx2) * (cx1 - cx2) + (cy1 - cy2) * (cy1 - cy2); if (dist > maxDist) From ba14895fd7239ff680ca2795dd3986f597e1498a Mon Sep 17 00:00:00 2001 From: catree Date: Mon, 8 Aug 2016 04:06:46 +0200 Subject: [PATCH 022/409] Add figure for the pinhole camera model in the 2.4 documentation. --- ...camera_calibration_and_3d_reconstruction.rst | 5 +++++ .../calib3d/doc/pics/pinhole_camera_model.png | Bin 0 -> 97238 bytes 2 files changed, 5 insertions(+) create mode 100644 modules/calib3d/doc/pics/pinhole_camera_model.png diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index 1b8fa52d61..29b458e6ca 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -62,6 +62,11 @@ to the following (when v = f_y*y' + c_y \end{array} +The following figure illustrates the pinhole camera model. + +.. image:: pics/pinhole_camera_model.png + :align: center + Real lenses usually have some distortion, mostly radial distortion and slight tangential distortion. So, the above model is extended as: diff --git a/modules/calib3d/doc/pics/pinhole_camera_model.png b/modules/calib3d/doc/pics/pinhole_camera_model.png new file mode 100644 index 0000000000000000000000000000000000000000..3594de9684da817e29a229a0824988872a8a5823 GIT binary patch literal 97238 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 Vb*d4$tL(X~CS7rik{qQY8MY4f?l!9N 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 tfeLgdJv>BNOX;IAk8GWtD0J>A3kpNTIH` Date: Mon, 8 Aug 2016 10:58:46 +0200 Subject: [PATCH 023/409] fix small typo --- .../features2d/doc/common_interfaces_of_feature_detectors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/features2d/doc/common_interfaces_of_feature_detectors.rst b/modules/features2d/doc/common_interfaces_of_feature_detectors.rst index 9f7b630953..a42ac07e14 100644 --- a/modules/features2d/doc/common_interfaces_of_feature_detectors.rst +++ b/modules/features2d/doc/common_interfaces_of_feature_detectors.rst @@ -147,7 +147,7 @@ for example: ``"GridFAST"``, ``"PyramidSTAR"`` . .. note:: When using the SIFT and SURF detector types be sure to add the following lines to your code * Add an extra include: `#include ` - * Add an initialisation function at the start of your code: `initModules_nonfree();` + * Add an initialisation function at the start of your code: `initModule_nonfree();` FastFeatureDetector ------------------- From a893395da8a6d13b238614eee558f2aabbfcdf35 Mon Sep 17 00:00:00 2001 From: catree Date: Thu, 11 Aug 2016 00:56:24 +0200 Subject: [PATCH 024/409] Fix typo bug in the calculation of some derivatives (dmxdt, dmydt, dmxdr, dmydr) in cvProjectPoints2 function. --- modules/calib3d/src/calibration.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/calib3d/src/calibration.cpp b/modules/calib3d/src/calibration.cpp index d9e821db41..b9cb224973 100644 --- a/modules/calib3d/src/calibration.cpp +++ b/modules/calib3d/src/calibration.cpp @@ -1078,9 +1078,9 @@ CV_IMPL void cvProjectPoints2( const CvMat* objectPoints, double dicdist2_dt = -icdist2*icdist2*(k[5]*dr2dt + 2*k[6]*r2*dr2dt + 3*k[7]*r4*dr2dt); double da1dt = 2*(x*dydt[j] + y*dxdt[j]); double dmxdt = fx*(dxdt[j]*cdist*icdist2 + x*dcdist_dt*icdist2 + x*cdist*dicdist2_dt + - k[2]*da1dt + k[3]*(dr2dt + 2*x*dxdt[j])); + k[2]*da1dt + k[3]*(dr2dt + 4*x*dxdt[j])); double dmydt = fy*(dydt[j]*cdist*icdist2 + y*dcdist_dt*icdist2 + y*cdist*dicdist2_dt + - k[2]*(dr2dt + 2*y*dydt[j]) + k[3]*da1dt); + k[2]*(dr2dt + 4*y*dydt[j]) + k[3]*da1dt); dpdt_p[j] = dmxdt; dpdt_p[dpdt_step+j] = dmydt; } @@ -1116,9 +1116,9 @@ CV_IMPL void cvProjectPoints2( const CvMat* objectPoints, double dicdist2_dr = -icdist2*icdist2*(k[5]*dr2dr + 2*k[6]*r2*dr2dr + 3*k[7]*r4*dr2dr); double da1dr = 2*(x*dydr + y*dxdr); double dmxdr = fx*(dxdr*cdist*icdist2 + x*dcdist_dr*icdist2 + x*cdist*dicdist2_dr + - k[2]*da1dr + k[3]*(dr2dr + 2*x*dxdr)); + k[2]*da1dr + k[3]*(dr2dr + 4*x*dxdr)); double dmydr = fy*(dydr*cdist*icdist2 + y*dcdist_dr*icdist2 + y*cdist*dicdist2_dr + - k[2]*(dr2dr + 2*y*dydr) + k[3]*da1dr); + k[2]*(dr2dr + 4*y*dydr) + k[3]*da1dr); dpdr_p[j] = dmxdr; dpdr_p[dpdr_step+j] = dmydr; } From 0f8023967c05ff0d2fd77889a091807d04ee7c5f Mon Sep 17 00:00:00 2001 From: Asal Mirzaieva Date: Thu, 18 Aug 2016 15:57:20 +0300 Subject: [PATCH 025/409] add reqiured includes to the "Load, Modify, and Save an Image" tutorial resolves #6944 --- doc/tutorials/introduction/load_save_image/load_save_image.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/tutorials/introduction/load_save_image/load_save_image.rst b/doc/tutorials/introduction/load_save_image/load_save_image.rst index bb77583415..15ece4ebd3 100644 --- a/doc/tutorials/introduction/load_save_image/load_save_image.rst +++ b/doc/tutorials/introduction/load_save_image/load_save_image.rst @@ -28,6 +28,8 @@ Here it is: #include #include + #include + #include using namespace cv; From 2d0fe514a01cc6cdcab7255bd454aba2c9f0e763 Mon Sep 17 00:00:00 2001 From: "PkLab.net" Date: Mon, 22 Aug 2016 19:57:42 +0200 Subject: [PATCH 026/409] Fix rho,phi exchange. Backport to 2.4 --- .../imgproc/doc/geometric_transformations.rst | 54 ++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/modules/imgproc/doc/geometric_transformations.rst b/modules/imgproc/doc/geometric_transformations.rst index 56c74ebf5f..50d26157b1 100644 --- a/modules/imgproc/doc/geometric_transformations.rst +++ b/modules/imgproc/doc/geometric_transformations.rst @@ -264,11 +264,11 @@ Remaps an image to polar space. :param src: Source image - :param dst: Destination image + :param dst: Destination image. It will have same size and type as src. :param center: The transformation center; - :param maxRadius: Inverse magnitude scale parameter. See below + :param maxRadius: The radius of the bounding circle to transform. It determines the inverse magnitude scale parameter too. See below :param flags: A combination of interpolation methods and the following optional flags: @@ -283,7 +283,7 @@ The function ``cvLinearPolar`` transforms the source image using the following t .. math:: - dst( \phi , \rho ) = src(x,y) + dst( \rho , \phi ) = src(x,y) * @@ -291,20 +291,34 @@ The function ``cvLinearPolar`` transforms the source image using the following t .. math:: - dst(x,y) = src( \phi , \rho ) + dst(x,y) = src( \rho , \phi ) where .. math:: - \rho = (src.width/maxRadius) \cdot \sqrt{x^2 + y^2} , \phi =atan(y/x) + \begin{array}{l} + I = (dx,dy) = (x - center.x,y - center.y) \\ + \rho = Kx \cdot \texttt{magnitude} (I) ,\\ + \phi = Ky \cdot \texttt{angle} (I)_{0..360 deg} + \end{array} +and -The function can not operate in-place. + .. math:: + + \begin{array}{l} + Kx = src.cols / maxRadius \\ + Ky = src.rows / 360 + \end{array} .. note:: + * The function can not operate in-place. + + * To calculate magnitude and angle in degrees :ocv:func:`cvCartToPolar` is used internally thus angles are measured from 0 to 360 with accuracy about 0.3 degrees. + * An example using the LinearPolar operation can be found at opencv_source_code/samples/c/polar_transforms.c @@ -319,11 +333,11 @@ Remaps an image to log-polar space. :param src: Source image - :param dst: Destination image + :param dst: Destination image. It will have same size and type as src. :param center: The transformation center; where the output precision is maximal - :param M: Magnitude scale parameter. See below + :param M: Magnitude scale parameter. It determines the radius of the bounding circle to transform too. See below :param flags: A combination of interpolation methods and the following optional flags: @@ -338,7 +352,7 @@ The function ``cvLogPolar`` transforms the source image using the following tran .. math:: - dst( \phi , \rho ) = src(x,y) + dst( \rho , \phi ) = src(x, y) * @@ -346,20 +360,36 @@ The function ``cvLogPolar`` transforms the source image using the following tran .. math:: - dst(x,y) = src( \phi , \rho ) + dst(x,y) = src( \rho , \phi ) where .. math:: - \rho = M \cdot \log{\sqrt{x^2 + y^2}} , \phi =atan(y/x) + \begin{array}{l} + I = (dx,dy) = (x - center.x,y - center.y) \\ + \rho = M \cdot log_e(\texttt{magnitude} (I)) ,\\ + \phi = Ky \cdot \texttt{angle} (I)_{0..360 deg} + \end{array} +and -The function emulates the human "foveal" vision and can be used for fast scale and rotation-invariant template matching, for object tracking and so forth. The function can not operate in-place. + .. math:: + + \begin{array}{l} + M = src.cols / log_e(maxRadius) \\ + Ky = src.rows / 360 + \end{array} + +The function emulates the human "foveal" vision and can be used for fast scale and rotation-invariant template matching, for object tracking and so forth. .. note:: + * The function can not operate in-place. + + * To calculate magnitude and angle in degrees :ocv:func:`cvCartToPolar` is used internally thus angles are measured from 0 to 360 with accuracy about 0.3 degrees. + * An example using the geometric logpolar operation in 4 applications can be found at opencv_source_code/samples/cpp/logpolar_bsm.cpp remap From a6e5ebafcf1ba5fa4b27f23b0581dee917e3198c Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 31 Aug 2016 15:18:25 +0300 Subject: [PATCH 027/409] calib3d: fix cornerSubPix memory error --- modules/calib3d/test/test_cornerssubpix.cpp | 14 ++++++++++++++ modules/imgproc/src/cornersubpix.cpp | 9 +++++++++ modules/imgproc/src/samplers.cpp | 2 ++ 3 files changed, 25 insertions(+) diff --git a/modules/calib3d/test/test_cornerssubpix.cpp b/modules/calib3d/test/test_cornerssubpix.cpp index eb07b47cbd..79ba4c2ffe 100644 --- a/modules/calib3d/test/test_cornerssubpix.cpp +++ b/modules/calib3d/test/test_cornerssubpix.cpp @@ -239,4 +239,18 @@ void CV_ChessboardSubpixelTest::generateIntrinsicParams() TEST(Calib3d_ChessboardSubPixDetector, accuracy) { CV_ChessboardSubpixelTest test; test.safe_run(); } +TEST(Calib3d_CornerSubPix, regression_7204) +{ + cv::Mat image(cv::Size(70, 38), CV_8UC1, cv::Scalar::all(0)); + image(cv::Rect(65, 26, 5, 5)).setTo(cv::Scalar::all(255)); + image(cv::Rect(55, 31, 8, 1)).setTo(cv::Scalar::all(255)); + image(cv::Rect(56, 35, 14, 2)).setTo(cv::Scalar::all(255)); + image(cv::Rect(66, 24, 4, 2)).setTo(cv::Scalar::all(255)); + image.at(24, 69) = 0; + std::vector corners; + corners.push_back(cv::Point2f(65, 30)); + cv::cornerSubPix(image, corners, cv::Size(3, 3), cv::Size(-1, -1), + cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1)); +} + /* End of file. */ diff --git a/modules/imgproc/src/cornersubpix.cpp b/modules/imgproc/src/cornersubpix.cpp index 1f55fa7513..013b245787 100644 --- a/modules/imgproc/src/cornersubpix.cpp +++ b/modules/imgproc/src/cornersubpix.cpp @@ -234,6 +234,15 @@ cvFindCornerSubPix( const void* srcarr, CvPoint2D32f* corners, err = (cI2.x - cI.x) * (cI2.x - cI.x) + (cI2.y - cI.y) * (cI2.y - cI.y); cI = cI2; + + /* if new point is too far from initial, it means poor convergence. */ + if (fabs(cI.x - cT.x) > win.width || fabs(cI.y - cT.y) > win.height) + { + cI = cT; + break; + } + cI.x = std::max(0.0f, std::min((float)size.width, cI.x)); + cI.y = std::max(0.0f, std::min((float)size.height, cI.y)); } while( ++iter < max_iters && err > eps ); diff --git a/modules/imgproc/src/samplers.cpp b/modules/imgproc/src/samplers.cpp index f1c11447bd..1f881c0d0d 100644 --- a/modules/imgproc/src/samplers.cpp +++ b/modules/imgproc/src/samplers.cpp @@ -392,6 +392,8 @@ CvStatus CV_STDCALL icvGetRectSubPix_8u32f_C1R ip.x = cvFloor( center.x ); ip.y = cvFloor( center.y ); + CV_DbgAssert(fabs(center.x - ip.x) <= 1.0f && fabs(center.y - ip.y) <= 1.0f); + if( win_size.width <= 0 || win_size.height <= 0 ) return CV_BADRANGE_ERR; From be7c924e7cd746c6febcbe45a515bee164c70453 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Fri, 2 Sep 2016 01:50:54 +0300 Subject: [PATCH 028/409] integer overflow fixed in getContinuousSize() --- modules/core/src/precomp.hpp | 47 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/modules/core/src/precomp.hpp b/modules/core/src/precomp.hpp index c53224e0aa..6359c38354 100644 --- a/modules/core/src/precomp.hpp +++ b/modules/core/src/precomp.hpp @@ -127,39 +127,46 @@ template struct OpMax T operator ()(const T a, const T b) const { return std::max(a, b); } }; -inline Size getContinuousSize( const Mat& m1, int widthScale=1 ) +inline Size getContinuousSize_(int flags, int cols, int rows, int widthScale) { - return m1.isContinuous() ? Size(m1.cols*m1.rows*widthScale, 1) : - Size(m1.cols*widthScale, m1.rows); + int64 sz = (int64)cols * rows * widthScale; + return (flags & Mat::CONTINUOUS_FLAG) != 0 && + (int)sz == sz ? Size((int)sz, 1) : Size(cols * widthScale, rows); } -inline Size getContinuousSize( const Mat& m1, const Mat& m2, int widthScale=1 ) +inline Size getContinuousSize(const Mat& m1, int widthScale = 1) { - return (m1.flags & m2.flags & Mat::CONTINUOUS_FLAG) != 0 ? - Size(m1.cols*m1.rows*widthScale, 1) : Size(m1.cols*widthScale, m1.rows); + return getContinuousSize_(m1.flags, + m1.cols, m1.rows, widthScale); } -inline Size getContinuousSize( const Mat& m1, const Mat& m2, - const Mat& m3, int widthScale=1 ) +inline Size getContinuousSize(const Mat& m1, const Mat& m2, int widthScale = 1) { - return (m1.flags & m2.flags & m3.flags & Mat::CONTINUOUS_FLAG) != 0 ? - Size(m1.cols*m1.rows*widthScale, 1) : Size(m1.cols*widthScale, m1.rows); + return getContinuousSize_(m1.flags & m2.flags, + m1.cols, m1.rows, widthScale); } -inline Size getContinuousSize( const Mat& m1, const Mat& m2, - const Mat& m3, const Mat& m4, - int widthScale=1 ) +inline Size getContinuousSize(const Mat& m1, const Mat& m2, + const Mat& m3, int widthScale = 1) { - return (m1.flags & m2.flags & m3.flags & m4.flags & Mat::CONTINUOUS_FLAG) != 0 ? - Size(m1.cols*m1.rows*widthScale, 1) : Size(m1.cols*widthScale, m1.rows); + return getContinuousSize_(m1.flags & m2.flags & m3.flags, + m1.cols, m1.rows, widthScale); } -inline Size getContinuousSize( const Mat& m1, const Mat& m2, - const Mat& m3, const Mat& m4, - const Mat& m5, int widthScale=1 ) +inline Size getContinuousSize(const Mat& m1, const Mat& m2, + const Mat& m3, const Mat& m4, + int widthScale = 1) { - return (m1.flags & m2.flags & m3.flags & m4.flags & m5.flags & Mat::CONTINUOUS_FLAG) != 0 ? - Size(m1.cols*m1.rows*widthScale, 1) : Size(m1.cols*widthScale, m1.rows); + return getContinuousSize_(m1.flags & m2.flags & m3.flags & m4.flags, + m1.cols, m1.rows, widthScale); +} + +inline Size getContinuousSize(const Mat& m1, const Mat& m2, + const Mat& m3, const Mat& m4, + const Mat& m5, int widthScale = 1) +{ + return getContinuousSize_(m1.flags & m2.flags & m3.flags & m4.flags & m5.flags, + m1.cols, m1.rows, widthScale); } struct NoVec From 726efee4d2a4b4f075a9e37fca445b800186fddb Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Sat, 3 Sep 2016 20:28:54 +0300 Subject: [PATCH 029/409] fixed http://code.opencv.org/issues/4278 --- modules/core/src/matmul.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/core/src/matmul.cpp b/modules/core/src/matmul.cpp index d537b7dced..4be9c4daa3 100644 --- a/modules/core/src/matmul.cpp +++ b/modules/core/src/matmul.cpp @@ -1013,7 +1013,7 @@ void cv::gemm( InputArray matA, InputArray matB, double alpha, GEMMBlockMulFunc blockMulFunc; GEMMStoreFunc storeFunc; Mat *matD = &D, tmat; - int tmat_size = 0; + size_t tmat_size = 0; const uchar* Cdata = C.data; size_t Cstep = C.data ? (size_t)C.step : 0; AutoBuffer buf; @@ -1046,7 +1046,7 @@ void cv::gemm( InputArray matA, InputArray matB, double alpha, if( D.data == A.data || D.data == B.data ) { - tmat_size = d_size.width*d_size.height*CV_ELEM_SIZE(type); + tmat_size = (size_t)d_size.width*d_size.height*CV_ELEM_SIZE(type); // Allocate tmat later, once the size of buf is known matD = &tmat; } @@ -1137,7 +1137,7 @@ void cv::gemm( InputArray matA, InputArray matB, double alpha, int is_b_t = flags & GEMM_2_T; int elem_size = CV_ELEM_SIZE(type); int dk0_1, dk0_2; - int a_buf_size = 0, b_buf_size, d_buf_size; + size_t a_buf_size = 0, b_buf_size, d_buf_size; uchar* a_buf = 0; uchar* b_buf = 0; uchar* d_buf = 0; @@ -1178,8 +1178,8 @@ void cv::gemm( InputArray matA, InputArray matB, double alpha, dn0 = block_size / dk0; dk0_1 = (dn0+dn0/8+2) & -2; - b_buf_size = (dk0+dk0/8+1)*dk0_1*elem_size; - d_buf_size = (dk0+dk0/8+1)*dk0_1*work_elem_size; + b_buf_size = (size_t)(dk0+dk0/8+1)*dk0_1*elem_size; + d_buf_size = (size_t)(dk0+dk0/8+1)*dk0_1*work_elem_size; if( is_a_t ) { From b28d13430c78e9df7e6e2a50b34c0fbd717ff8d1 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Sat, 3 Sep 2016 21:49:54 +0300 Subject: [PATCH 030/409] fixed http://code.opencv.org/issues/3828 --- modules/core/src/stat.cpp | 5 +++++ modules/core/test/test_arithm.cpp | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index 3c24f02541..e88d175108 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -2587,6 +2587,11 @@ void cv::findNonZero( InputArray _src, OutputArray _idx ) Mat src = _src.getMat(); CV_Assert( src.type() == CV_8UC1 ); int n = countNonZero(src); + if (n == 0) + { + _idx.release(); + return; + } if( _idx.kind() == _InputArray::MAT && !_idx.getMatRef().isContinuous() ) _idx.release(); _idx.create(n, 1, CV_32SC2); diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index ac88615955..57adb89210 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -1810,3 +1810,13 @@ TEST(MinMaxLoc, Mat_IntMax_Without_Mask) ASSERT_EQ(Point(0, 0), minLoc); ASSERT_EQ(Point(0, 0), maxLoc); } + + +TEST(Core_FindNonZero, singular) +{ + Mat img(10, 10, CV_8U, Scalar::all(0)); + vector pts, pts2(10); + findNonZero(img, pts); + findNonZero(img, pts2); + ASSERT_TRUE(pts.empty() && pts2.empty()); +} \ No newline at end of file From 0a3a2df4333bfbf6a43f96a3fa2415f17c7145b9 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Sat, 3 Sep 2016 21:58:19 +0300 Subject: [PATCH 031/409] fixed U non-orthogonality in SVD (http://code.opencv.org/issues/3801) --- modules/core/src/lapack.cpp | 2 +- modules/core/test/test_mat.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/core/src/lapack.cpp b/modules/core/src/lapack.cpp index 7ee84a6d45..0168610572 100644 --- a/modules/core/src/lapack.cpp +++ b/modules/core/src/lapack.cpp @@ -688,7 +688,7 @@ JacobiSVDImpl_(_Tp* At, size_t astep, _Tp* _W, _Tp* Vt, size_t vstep, At[i*astep + k] = t; asum += std::abs(t); } - asum = asum ? 1/asum : 0; + asum = asum > eps * 100 ? 1 / asum : 0; for( k = 0; k < m; k++ ) At[i*astep + k] *= asum; } diff --git a/modules/core/test/test_mat.cpp b/modules/core/test/test_mat.cpp index f854abed7e..0aef147b60 100644 --- a/modules/core/test/test_mat.cpp +++ b/modules/core/test/test_mat.cpp @@ -919,6 +919,20 @@ TEST(Core_Mat, copyNx1ToVector) ASSERT_PRED_FORMAT2(cvtest::MatComparator(0, 0), ref_dst16, cv::Mat_(dst16)); } +TEST(Core_SVD, orthogonality) +{ + for (int i = 0; i < 2; i++) + { + int type = i == 0 ? CV_32F : CV_64F; + Mat mat_D(2, 2, type); + mat_D.setTo(88.); + Mat mat_U, mat_W; + SVD::compute(mat_D, mat_W, mat_U, noArray(), SVD::FULL_UV); + mat_U *= mat_U.t(); + ASSERT_LT(norm(mat_U, Mat::eye(2, 2, type), NORM_INF), 1e-5); + } +} + TEST(Core_Mat, multiDim) { int d[]={3,3,3}; From c7045184ce220a04796d8e9072ac42d2d362dc71 Mon Sep 17 00:00:00 2001 From: Bernd Kuhls Date: Tue, 6 Sep 2016 11:49:00 +0200 Subject: [PATCH 032/409] types_c.h: Fix compiling VFP assembler code Replace asm by __asm__ according to https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html#Alternate-Keywords as suggested by Arnout Vandecappelle: http://lists.busybox.net/pipermail/buildroot/2016-September/171491.html to fix build errors in ffmpeg with opencv2 support detected by buildroot autobuilders: http://autobuild.buildroot.net/results/c32/c32a21240a9933796ee850349a62ff3c2314f25c/build-end.log Signed-off-by: Bernd Kuhls --- modules/core/include/opencv2/core/types_c.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/types_c.h b/modules/core/include/opencv2/core/types_c.h index c21cd2c758..771715d5df 100644 --- a/modules/core/include/opencv2/core/types_c.h +++ b/modules/core/include/opencv2/core/types_c.h @@ -318,7 +318,7 @@ enum { int res; \ float temp; \ (void)temp; \ - asm(_asm_string : [res] "=r" (res), [temp] "=w" (temp) : [value] "w" (_value)); \ + __asm__(_asm_string : [res] "=r" (res), [temp] "=w" (temp) : [value] "w" (_value)); \ return res; // 2. version for double #ifdef __clang__ From cb52d249fed9c72fb2bb75a7c5f46a915ec209b6 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Tue, 6 Sep 2016 14:09:44 +0300 Subject: [PATCH 033/409] fixed optional args processing in SVD::compute() --- modules/core/src/lapack.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/core/src/lapack.cpp b/modules/core/src/lapack.cpp index 0168610572..3084a5354b 100644 --- a/modules/core/src/lapack.cpp +++ b/modules/core/src/lapack.cpp @@ -1560,18 +1560,18 @@ static void _SVDcompute( InputArray _aarr, OutputArray _w, JacobiSVD(temp_a.ptr(), temp_u.step, temp_w.ptr(), temp_v.ptr(), temp_v.step, m, n, compute_uv ? urows : 0); } - temp_w.copyTo(_w); + if(_w.needed()) temp_w.copyTo(_w); if( compute_uv ) { if( !at ) { - transpose(temp_u, _u); - temp_v.copyTo(_vt); + if(_u.needed()) transpose(temp_u, _u); + if(_vt.needed()) temp_v.copyTo(_vt); } else { - transpose(temp_v, _u); - temp_u.copyTo(_vt); + if(_u.needed()) transpose(temp_v, _u); + if(_vt.needed()) temp_u.copyTo(_vt); } } } From 98408257430e4f7cda150d8398be8b7024c11745 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Mon, 5 Sep 2016 21:55:17 +0300 Subject: [PATCH 034/409] added test checking http://code.opencv.org/issues/4050 --- modules/core/test/test_mat.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/core/test/test_mat.cpp b/modules/core/test/test_mat.cpp index 0aef147b60..d5b7ab97e1 100644 --- a/modules/core/test/test_mat.cpp +++ b/modules/core/test/test_mat.cpp @@ -919,6 +919,12 @@ TEST(Core_Mat, copyNx1ToVector) ASSERT_PRED_FORMAT2(cvtest::MatComparator(0, 0), ref_dst16, cv::Mat_(dst16)); } +TEST(Core_InputArray, empty) +{ + vector > data; + ASSERT_TRUE(_InputArray(data).empty()); +} + TEST(Core_SVD, orthogonality) { for (int i = 0; i < 2; i++) From 6d691f2e05aa1062e3381373eba2e494d5955678 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Mon, 5 Sep 2016 22:14:47 +0300 Subject: [PATCH 035/409] added test for http://code.opencv.org/issues/1918 --- modules/core/test/test_mat.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/modules/core/test/test_mat.cpp b/modules/core/test/test_mat.cpp index d5b7ab97e1..0d35e60780 100644 --- a/modules/core/test/test_mat.cpp +++ b/modules/core/test/test_mat.cpp @@ -925,6 +925,19 @@ TEST(Core_InputArray, empty) ASSERT_TRUE(_InputArray(data).empty()); } + +TEST(Core_CopyMask, bug1918) +{ + Mat_ tmpSrc(100, 100); + tmpSrc = 124; + Mat_ tmpMask(100, 100); + tmpMask = 255; + Mat_ tmpDst(100, 100); + tmpDst = 2; + tmpSrc.copyTo(tmpDst, tmpMask); + ASSERT_EQ(sum(tmpDst)[0], 124 * 100 * 100); +} + TEST(Core_SVD, orthogonality) { for (int i = 0; i < 2; i++) From 27dd27400a5b16825741fa3c8aeb37ae5c77c51a Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Mon, 5 Sep 2016 22:32:57 +0300 Subject: [PATCH 036/409] fixed invalid output of cv::dft when using DFT_ROWS + DFT_COMPLEX_OUTPUT (http://code.opencv.org/issues/3428) --- modules/core/src/dxt.cpp | 83 +++++++++++++++++++--------------- modules/core/test/test_dxt.cpp | 21 +++++++++ 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/modules/core/src/dxt.cpp b/modules/core/src/dxt.cpp index 033bf45120..79cb31ceed 100644 --- a/modules/core/src/dxt.cpp +++ b/modules/core/src/dxt.cpp @@ -1473,6 +1473,47 @@ typedef IppStatus (CV_STDCALL* IppDFTGetSizeFunc)(int, int, IppHintAlgorithm, in typedef IppStatus (CV_STDCALL* IppDFTInitFunc)(int, int, IppHintAlgorithm, void*, uchar*); #endif +namespace cv +{ +static void complementComplexOutput(Mat& dst, int len, int dft_dims) +{ + int i, n = dst.cols; + size_t elem_size = dst.elemSize1(); + if (elem_size == sizeof(float)) + { + float* p0 = dst.ptr(); + size_t dstep = dst.step / sizeof(p0[0]); + for (i = 0; i < len; i++) + { + float* p = p0 + dstep*i; + float* q = dft_dims == 1 || i == 0 || i * 2 == len ? p : p0 + dstep*(len - i); + + for (int j = 1; j < (n + 1) / 2; j++) + { + p[(n - j) * 2] = q[j * 2]; + p[(n - j) * 2 + 1] = -q[j * 2 + 1]; + } + } + } + else + { + double* p0 = dst.ptr(); + size_t dstep = dst.step / sizeof(p0[0]); + for (i = 0; i < len; i++) + { + double* p = p0 + dstep*i; + double* q = dft_dims == 1 || i == 0 || i * 2 == len ? p : p0 + dstep*(len - i); + + for (int j = 1; j < (n + 1) / 2; j++) + { + p[(n - j) * 2] = q[j * 2]; + p[(n - j) * 2 + 1] = -q[j * 2 + 1]; + } + } + } +} +} + void cv::dft( InputArray _src0, OutputArray _dst, int flags, int nonzero_rows ) { static DFTFunc dft_tbl[6] = @@ -1688,8 +1729,12 @@ void cv::dft( InputArray _src0, OutputArray _dst, int flags, int nonzero_rows ) memset( dptr0, 0, dst_full_len ); } - if( stage != 1 ) + if (stage != 1) + { + if (!inv && real_transform && dst.channels() == 2) + complementComplexOutput(dst, nonzero_rows, 1); break; + } src = dst; } else @@ -1831,41 +1876,7 @@ void cv::dft( InputArray _src0, OutputArray _dst, int flags, int nonzero_rows ) if( stage != 0 ) { if( !inv && real_transform && dst.channels() == 2 && len > 1 ) - { - int n = dst.cols; - if( elem_size == (int)sizeof(float) ) - { - float* p0 = (float*)dst.data; - size_t dstep = dst.step/sizeof(p0[0]); - for( i = 0; i < len; i++ ) - { - float* p = p0 + dstep*i; - float* q = i == 0 || i*2 == len ? p : p0 + dstep*(len-i); - - for( int j = 1; j < (n+1)/2; j++ ) - { - p[(n-j)*2] = q[j*2]; - p[(n-j)*2+1] = -q[j*2+1]; - } - } - } - else - { - double* p0 = (double*)dst.data; - size_t dstep = dst.step/sizeof(p0[0]); - for( i = 0; i < len; i++ ) - { - double* p = p0 + dstep*i; - double* q = i == 0 || i*2 == len ? p : p0 + dstep*(len-i); - - for( int j = 1; j < (n+1)/2; j++ ) - { - p[(n-j)*2] = q[j*2]; - p[(n-j)*2+1] = -q[j*2+1]; - } - } - } - } + complementComplexOutput(dst, len, 2); break; } src = dst; diff --git a/modules/core/test/test_dxt.cpp b/modules/core/test/test_dxt.cpp index 1c0c7b00bf..33ddb02660 100644 --- a/modules/core/test/test_dxt.cpp +++ b/modules/core/test/test_dxt.cpp @@ -866,3 +866,24 @@ protected: }; TEST(Core_DFT, complex_output) { Core_DFTComplexOutputTest test; test.safe_run(); } + +TEST(Core_DFT, complex_output2) +{ + for (int i = 0; i < 100; i++) + { + int type = theRNG().uniform(0, 2) ? CV_64F : CV_32F; + int m = theRNG().uniform(1, 10); + int n = theRNG().uniform(1, 10); + Mat x(m, n, type), out; + randu(x, -1., 1.); + dft(x, out, DFT_ROWS | DFT_COMPLEX_OUTPUT); + double nrm = norm(out, NORM_INF); + double thresh = n*m * 2; + if (nrm > thresh) + { + cout << "x: " << x << endl; + cout << "out: " << out << endl; + ASSERT_LT(nrm, thresh); + } + } +} From b8b7f155a5be52a0bb85a748fe69cac447928ccb Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Mon, 5 Sep 2016 22:33:31 +0300 Subject: [PATCH 037/409] fixed random failures in Core_DFT.complex_output2 test (the case of input_mat.cols == 1) --- modules/core/src/dxt.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/core/src/dxt.cpp b/modules/core/src/dxt.cpp index 79cb31ceed..90eb392f58 100644 --- a/modules/core/src/dxt.cpp +++ b/modules/core/src/dxt.cpp @@ -1070,11 +1070,12 @@ RealDFT( const T* src, T* dst, int n, int nf, int* factors, const int* itab, } } - if( complex_output && (n & 1) == 0 ) + if (complex_output && ((n & 1) == 0 || n == 1)) { dst[-1] = dst[0]; dst[0] = 0; - dst[n] = 0; + if (n > 1) + dst[n] = 0; } } From c68b9328ca85ad0be1fb756b6db4551e2ed74aae Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 14 Sep 2016 20:00:28 +0300 Subject: [PATCH 038/409] android: workaround for Android NDK-8e clang ICE --- modules/ocl/src/stereo_csbp.cpp | 144 ++++++++++++++++---------------- 1 file changed, 71 insertions(+), 73 deletions(-) diff --git a/modules/ocl/src/stereo_csbp.cpp b/modules/ocl/src/stereo_csbp.cpp index 409d6f21c2..ff78a19c3b 100644 --- a/modules/ocl/src/stereo_csbp.cpp +++ b/modules/ocl/src/stereo_csbp.cpp @@ -76,11 +76,11 @@ namespace cv /////////////////////////////////init_data_cost////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////// static void init_data_cost_caller(const oclMat &left, const oclMat &right, oclMat &temp, - StereoConstantSpaceBP &rthis, + StereoConstantSpaceBP *pThis, int msg_step, int h, int w, int level) { Context *clCxt = left.clCxt; - int data_type = rthis.msg_type; + int data_type = pThis->msg_type; int channels = left.oclchannels(); string kernelName = get_kernel_name("init_data_cost_", data_type); @@ -104,12 +104,12 @@ namespace cv openCLSafeCall(clSetKernelArg(kernel, 5, sizeof(cl_int), (void *)&level)); openCLSafeCall(clSetKernelArg(kernel, 6, sizeof(cl_int), (void *)&channels)); openCLSafeCall(clSetKernelArg(kernel, 7, sizeof(cl_int), (void *)&msg_step)); - openCLSafeCall(clSetKernelArg(kernel, 8, sizeof(cl_float), (void *)&rthis.data_weight)); - openCLSafeCall(clSetKernelArg(kernel, 9, sizeof(cl_float), (void *)&rthis.max_data_term)); + openCLSafeCall(clSetKernelArg(kernel, 8, sizeof(cl_float), (void *)&pThis->data_weight)); + openCLSafeCall(clSetKernelArg(kernel, 9, sizeof(cl_float), (void *)&pThis->max_data_term)); openCLSafeCall(clSetKernelArg(kernel, 10, sizeof(cl_int), (void *)&cdisp_step1)); - openCLSafeCall(clSetKernelArg(kernel, 11, sizeof(cl_int), (void *)&rthis.min_disp_th)); + openCLSafeCall(clSetKernelArg(kernel, 11, sizeof(cl_int), (void *)&pThis->min_disp_th)); openCLSafeCall(clSetKernelArg(kernel, 12, sizeof(cl_int), (void *)&left.step)); - openCLSafeCall(clSetKernelArg(kernel, 13, sizeof(cl_int), (void *)&rthis.ndisp)); + openCLSafeCall(clSetKernelArg(kernel, 13, sizeof(cl_int), (void *)&pThis->ndisp)); openCLSafeCall(clEnqueueNDRangeKernel(*(cl_command_queue*)getClCommandQueuePtr(), kernel, 2, NULL, globalThreads, localThreads, 0, NULL, NULL)); @@ -118,12 +118,12 @@ namespace cv } static void init_data_cost_reduce_caller(const oclMat &left, const oclMat &right, oclMat &temp, - StereoConstantSpaceBP &rthis, + StereoConstantSpaceBP *pThis, int msg_step, int h, int w, int level) { Context *clCxt = left.clCxt; - int data_type = rthis.msg_type; + int data_type = pThis->msg_type; int channels = left.oclchannels(); int win_size = (int)std::pow(2.f, level); @@ -135,7 +135,7 @@ namespace cv //size_t blockSize = threadsNum; size_t localThreads[3] = {(size_t)win_size, 1, (size_t)threadsNum / win_size}; size_t globalThreads[3] = { w *localThreads[0], - h * divUp(rthis.ndisp, localThreads[2]) *localThreads[1], 1 * localThreads[2] + h * divUp(pThis->ndisp, localThreads[2]) *localThreads[1], 1 * localThreads[2] }; int local_mem_size = threadsNum * sizeof(float); @@ -153,11 +153,11 @@ namespace cv openCLSafeCall(clSetKernelArg(kernel, 7, sizeof(cl_int), (void *)&h)); openCLSafeCall(clSetKernelArg(kernel, 8, sizeof(cl_int), (void *)&win_size)); openCLSafeCall(clSetKernelArg(kernel, 9, sizeof(cl_int), (void *)&channels)); - openCLSafeCall(clSetKernelArg(kernel, 10, sizeof(cl_int), (void *)&rthis.ndisp)); + openCLSafeCall(clSetKernelArg(kernel, 10, sizeof(cl_int), (void *)&pThis->ndisp)); openCLSafeCall(clSetKernelArg(kernel, 11, sizeof(cl_int), (void *)&left.step)); - openCLSafeCall(clSetKernelArg(kernel, 12, sizeof(cl_float), (void *)&rthis.data_weight)); - openCLSafeCall(clSetKernelArg(kernel, 13, sizeof(cl_float), (void *)&rthis.max_data_term)); - openCLSafeCall(clSetKernelArg(kernel, 14, sizeof(cl_int), (void *)&rthis.min_disp_th)); + openCLSafeCall(clSetKernelArg(kernel, 12, sizeof(cl_float), (void *)&pThis->data_weight)); + openCLSafeCall(clSetKernelArg(kernel, 13, sizeof(cl_float), (void *)&pThis->max_data_term)); + openCLSafeCall(clSetKernelArg(kernel, 14, sizeof(cl_int), (void *)&pThis->min_disp_th)); openCLSafeCall(clSetKernelArg(kernel, 15, sizeof(cl_int), (void *)&cdisp_step1)); openCLSafeCall(clSetKernelArg(kernel, 16, sizeof(cl_int), (void *)&msg_step)); openCLSafeCall(clEnqueueNDRangeKernel(*(cl_command_queue*)getClCommandQueuePtr(), kernel, 3, NULL, @@ -167,11 +167,11 @@ namespace cv } static void get_first_initial_local_caller(uchar *data_cost_selected, uchar *disp_selected_pyr, - oclMat &temp, StereoConstantSpaceBP &rthis, + oclMat &temp, StereoConstantSpaceBP *pThis, int h, int w, int nr_plane, int msg_step) { Context *clCxt = temp.clCxt; - int data_type = rthis.msg_type; + int data_type = pThis->msg_type; string kernelName = get_kernel_name("get_first_k_initial_local_", data_type); @@ -191,7 +191,7 @@ namespace cv openCLSafeCall(clSetKernelArg(kernel, 5, sizeof(cl_int), (void *)&nr_plane)); openCLSafeCall(clSetKernelArg(kernel, 6, sizeof(cl_int), (void *)&msg_step)); openCLSafeCall(clSetKernelArg(kernel, 7, sizeof(cl_int), (void *)&disp_step)); - openCLSafeCall(clSetKernelArg(kernel, 8, sizeof(cl_int), (void *)&rthis.ndisp)); + openCLSafeCall(clSetKernelArg(kernel, 8, sizeof(cl_int), (void *)&pThis->ndisp)); openCLSafeCall(clEnqueueNDRangeKernel(*(cl_command_queue*)getClCommandQueuePtr(), kernel, 2, NULL, globalThreads, localThreads, 0, NULL, NULL)); @@ -199,11 +199,11 @@ namespace cv openCLSafeCall(clReleaseKernel(kernel)); } static void get_first_initial_global_caller(uchar *data_cost_selected, uchar *disp_selected_pyr, - oclMat &temp, StereoConstantSpaceBP &rthis, + oclMat &temp, StereoConstantSpaceBP *pThis, int h, int w, int nr_plane, int msg_step) { Context *clCxt = temp.clCxt; - int data_type = rthis.msg_type; + int data_type = pThis->msg_type; string kernelName = get_kernel_name("get_first_k_initial_global_", data_type); @@ -226,7 +226,7 @@ namespace cv openCLSafeCall(clSetKernelArg(kernel, 5, sizeof(cl_int), (void *)&nr_plane)); openCLSafeCall(clSetKernelArg(kernel, 6, sizeof(cl_int), (void *)&msg_step)); openCLSafeCall(clSetKernelArg(kernel, 7, sizeof(cl_int), (void *)&disp_step)); - openCLSafeCall(clSetKernelArg(kernel, 8, sizeof(cl_int), (void *)&rthis.ndisp)); + openCLSafeCall(clSetKernelArg(kernel, 8, sizeof(cl_int), (void *)&pThis->ndisp)); openCLSafeCall(clEnqueueNDRangeKernel(*(cl_command_queue*)getClCommandQueuePtr(), kernel, 2, NULL, globalThreads, localThreads, 0, NULL, NULL)); @@ -234,23 +234,23 @@ namespace cv openCLSafeCall(clReleaseKernel(kernel)); } - static void init_data_cost(const oclMat &left, const oclMat &right, oclMat &temp, StereoConstantSpaceBP &rthis, + static void init_data_cost(const oclMat &left, const oclMat &right, oclMat &temp, StereoConstantSpaceBP *pThis, uchar *disp_selected_pyr, uchar *data_cost_selected, size_t msg_step, int h, int w, int level, int nr_plane) { if(level <= 1) - init_data_cost_caller(left, right, temp, rthis, msg_step, h, w, level); + init_data_cost_caller(left, right, temp, pThis, msg_step, h, w, level); else - init_data_cost_reduce_caller(left, right, temp, rthis, msg_step, h, w, level); + init_data_cost_reduce_caller(left, right, temp, pThis, msg_step, h, w, level); - if(rthis.use_local_init_data_cost == true) + if(pThis->use_local_init_data_cost == true) { - get_first_initial_local_caller(data_cost_selected, disp_selected_pyr, temp, rthis, h, w, nr_plane, msg_step); + get_first_initial_local_caller(data_cost_selected, disp_selected_pyr, temp, pThis, h, w, nr_plane, msg_step); } else { - get_first_initial_global_caller(data_cost_selected, disp_selected_pyr, temp, rthis, h, w, + get_first_initial_global_caller(data_cost_selected, disp_selected_pyr, temp, pThis, h, w, nr_plane, msg_step); } } @@ -259,13 +259,13 @@ namespace cv ///////////////////////////////////compute_data_cost////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// static void compute_data_cost_caller(uchar *disp_selected_pyr, uchar *data_cost, - StereoConstantSpaceBP &rthis, int msg_step1, + StereoConstantSpaceBP *pThis, int msg_step1, int msg_step2, const oclMat &left, const oclMat &right, int h, int w, int h2, int level, int nr_plane) { Context *clCxt = left.clCxt; int channels = left.oclchannels(); - int data_type = rthis.msg_type; + int data_type = pThis->msg_type; string kernelName = get_kernel_name("compute_data_cost_", data_type); @@ -290,10 +290,10 @@ namespace cv openCLSafeCall(clSetKernelArg(kernel, 10, sizeof(cl_int), (void *)&msg_step2)); openCLSafeCall(clSetKernelArg(kernel, 11, sizeof(cl_int), (void *)&disp_step1)); openCLSafeCall(clSetKernelArg(kernel, 12, sizeof(cl_int), (void *)&disp_step2)); - openCLSafeCall(clSetKernelArg(kernel, 13, sizeof(cl_float), (void *)&rthis.data_weight)); - openCLSafeCall(clSetKernelArg(kernel, 14, sizeof(cl_float), (void *)&rthis.max_data_term)); + openCLSafeCall(clSetKernelArg(kernel, 13, sizeof(cl_float), (void *)&pThis->data_weight)); + openCLSafeCall(clSetKernelArg(kernel, 14, sizeof(cl_float), (void *)&pThis->max_data_term)); openCLSafeCall(clSetKernelArg(kernel, 15, sizeof(cl_int), (void *)&left.step)); - openCLSafeCall(clSetKernelArg(kernel, 16, sizeof(cl_int), (void *)&rthis.min_disp_th)); + openCLSafeCall(clSetKernelArg(kernel, 16, sizeof(cl_int), (void *)&pThis->min_disp_th)); openCLSafeCall(clEnqueueNDRangeKernel(*(cl_command_queue*)getClCommandQueuePtr(), kernel, 2, NULL, globalThreads, localThreads, 0, NULL, NULL)); @@ -301,12 +301,12 @@ namespace cv openCLSafeCall(clReleaseKernel(kernel)); } static void compute_data_cost_reduce_caller(uchar *disp_selected_pyr, uchar *data_cost, - StereoConstantSpaceBP &rthis, int msg_step1, + StereoConstantSpaceBP *pThis, int msg_step1, int msg_step2, const oclMat &left, const oclMat &right, int h, int w, int h2, int level, int nr_plane) { Context *clCxt = left.clCxt; - int data_type = rthis.msg_type; + int data_type = pThis->msg_type; int channels = left.oclchannels(); int win_size = (int)std::pow(2.f, level); @@ -341,25 +341,25 @@ namespace cv openCLSafeCall(clSetKernelArg(kernel, 13, sizeof(cl_int), (void *)&msg_step2)); openCLSafeCall(clSetKernelArg(kernel, 14, sizeof(cl_int), (void *)&disp_step1)); openCLSafeCall(clSetKernelArg(kernel, 15, sizeof(cl_int), (void *)&disp_step2)); - openCLSafeCall(clSetKernelArg(kernel, 16, sizeof(cl_float), (void *)&rthis.data_weight)); - openCLSafeCall(clSetKernelArg(kernel, 17, sizeof(cl_float), (void *)&rthis.max_data_term)); + openCLSafeCall(clSetKernelArg(kernel, 16, sizeof(cl_float), (void *)&pThis->data_weight)); + openCLSafeCall(clSetKernelArg(kernel, 17, sizeof(cl_float), (void *)&pThis->max_data_term)); openCLSafeCall(clSetKernelArg(kernel, 18, sizeof(cl_int), (void *)&left.step)); - openCLSafeCall(clSetKernelArg(kernel, 19, sizeof(cl_int), (void *)&rthis.min_disp_th)); + openCLSafeCall(clSetKernelArg(kernel, 19, sizeof(cl_int), (void *)&pThis->min_disp_th)); openCLSafeCall(clEnqueueNDRangeKernel(*(cl_command_queue*)getClCommandQueuePtr(), kernel, 3, NULL, globalThreads, localThreads, 0, NULL, NULL)); clFinish(*(cl_command_queue*)getClCommandQueuePtr()); openCLSafeCall(clReleaseKernel(kernel)); } - static void compute_data_cost(uchar *disp_selected_pyr, uchar *data_cost, StereoConstantSpaceBP &rthis, + static void compute_data_cost(uchar *disp_selected_pyr, uchar *data_cost, StereoConstantSpaceBP *pThis, int msg_step1, int msg_step2, const oclMat &left, const oclMat &right, int h, int w, int h2, int level, int nr_plane) { if(level <= 1) - compute_data_cost_caller(disp_selected_pyr, data_cost, rthis, msg_step1, msg_step2, + compute_data_cost_caller(disp_selected_pyr, data_cost, pThis, msg_step1, msg_step2, left, right, h, w, h2, level, nr_plane); else - compute_data_cost_reduce_caller(disp_selected_pyr, data_cost, rthis, msg_step1, msg_step2, + compute_data_cost_reduce_caller(disp_selected_pyr, data_cost, pThis, msg_step1, msg_step2, left, right, h, w, h2, level, nr_plane); } //////////////////////////////////////////////////////////////////////////////////////////////// @@ -368,12 +368,12 @@ namespace cv static void init_message(uchar *u_new, uchar *d_new, uchar *l_new, uchar *r_new, uchar *u_cur, uchar *d_cur, uchar *l_cur, uchar *r_cur, uchar *disp_selected_pyr_new, uchar *disp_selected_pyr_cur, - uchar *data_cost_selected, uchar *data_cost, oclMat &temp, StereoConstantSpaceBP rthis, + uchar *data_cost_selected, uchar *data_cost, oclMat &temp, StereoConstantSpaceBP *pThis, size_t msg_step1, size_t msg_step2, int h, int w, int nr_plane, int h2, int w2, int nr_plane2) { Context *clCxt = temp.clCxt; - int data_type = rthis.msg_type; + int data_type = pThis->msg_type; string kernelName = get_kernel_name("init_message_", data_type); @@ -419,11 +419,11 @@ namespace cv ///////////////////////////calc_all_iterations//////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// static void calc_all_iterations_caller(uchar *u, uchar *d, uchar *l, uchar *r, uchar *data_cost_selected, - uchar *disp_selected_pyr, oclMat &temp, StereoConstantSpaceBP rthis, + uchar *disp_selected_pyr, oclMat &temp, StereoConstantSpaceBP *pThis, int msg_step, int h, int w, int nr_plane, int i) { Context *clCxt = temp.clCxt; - int data_type = rthis.msg_type; + int data_type = pThis->msg_type; string kernelName = get_kernel_name("compute_message_", data_type); @@ -447,10 +447,10 @@ namespace cv openCLSafeCall(clSetKernelArg(kernel, 8, sizeof(cl_int), (void *)&w)); openCLSafeCall(clSetKernelArg(kernel, 9, sizeof(cl_int), (void *)&nr_plane)); openCLSafeCall(clSetKernelArg(kernel, 10, sizeof(cl_int), (void *)&i)); - openCLSafeCall(clSetKernelArg(kernel, 11, sizeof(cl_float), (void *)&rthis.max_disc_term)); + openCLSafeCall(clSetKernelArg(kernel, 11, sizeof(cl_float), (void *)&pThis->max_disc_term)); openCLSafeCall(clSetKernelArg(kernel, 12, sizeof(cl_int), (void *)&disp_step)); openCLSafeCall(clSetKernelArg(kernel, 13, sizeof(cl_int), (void *)&msg_step)); - openCLSafeCall(clSetKernelArg(kernel, 14, sizeof(cl_float), (void *)&rthis.disc_single_jump)); + openCLSafeCall(clSetKernelArg(kernel, 14, sizeof(cl_float), (void *)&pThis->disc_single_jump)); openCLSafeCall(clEnqueueNDRangeKernel(*(cl_command_queue*)getClCommandQueuePtr(), kernel, 2, NULL, globalThreads, localThreads, 0, NULL, NULL)); @@ -458,11 +458,11 @@ namespace cv openCLSafeCall(clReleaseKernel(kernel)); } static void calc_all_iterations(uchar *u, uchar *d, uchar *l, uchar *r, uchar *data_cost_selected, - uchar *disp_selected_pyr, oclMat &temp, StereoConstantSpaceBP rthis, + uchar *disp_selected_pyr, oclMat &temp, StereoConstantSpaceBP *pThis, int msg_step, int h, int w, int nr_plane) { - for(int t = 0; t < rthis.iters; t++) - calc_all_iterations_caller(u, d, l, r, data_cost_selected, disp_selected_pyr, temp, rthis, + for(int t = 0; t < pThis->iters; t++) + calc_all_iterations_caller(u, d, l, r, data_cost_selected, disp_selected_pyr, temp, pThis, msg_step, h, w, nr_plane, t & 1); } @@ -470,11 +470,11 @@ namespace cv //////////////////////////compute_disp//////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// static void compute_disp(uchar *u, uchar *d, uchar *l, uchar *r, uchar *data_cost_selected, - uchar *disp_selected_pyr, StereoConstantSpaceBP &rthis, size_t msg_step, + uchar *disp_selected_pyr, StereoConstantSpaceBP *pThis, size_t msg_step, oclMat &disp, int nr_plane) { Context *clCxt = disp.clCxt; - int data_type = rthis.msg_type; + int data_type = pThis->msg_type; string kernelName = get_kernel_name("compute_disp_", data_type); @@ -550,20 +550,20 @@ cv::ocl::StereoConstantSpaceBP::StereoConstantSpaceBP(int ndisp_, int iters_, in : ndisp(ndisp_), iters(iters_), levels(levels_), nr_plane(nr_plane_), max_data_term(max_data_term_), data_weight(data_weight_), max_disc_term(max_disc_term_), disc_single_jump(disc_single_jump_), min_disp_th(min_disp_th_), - msg_type(msg_type_), use_local_init_data_cost(true) + msg_type(msg_type_), use_local_init_data_cost(TRUE) { CV_Assert(msg_type_ == CV_32F || msg_type_ == CV_16S); } template -static void csbp_operator(StereoConstantSpaceBP &rthis, oclMat u[2], oclMat d[2], oclMat l[2], oclMat r[2], +static void csbp_operator(StereoConstantSpaceBP *pThis, oclMat u[2], oclMat d[2], oclMat l[2], oclMat r[2], oclMat disp_selected_pyr[2], oclMat &data_cost, oclMat &data_cost_selected, oclMat &temp, oclMat &out, const oclMat &left, const oclMat &right, oclMat &disp) { - CV_DbgAssert(0 < rthis.ndisp && 0 < rthis.iters && 0 < rthis.levels && 0 < rthis.nr_plane + CV_DbgAssert(0 < pThis->ndisp && 0 < pThis->iters && 0 < pThis->levels && 0 < pThis->nr_plane && left.rows == right.rows && left.cols == right.cols && left.type() == right.type()); - CV_Assert(rthis.levels <= 8 && (left.type() == CV_8UC1 || left.type() == CV_8UC3)); + CV_Assert(pThis->levels <= 8 && (left.type() == CV_8UC1 || left.type() == CV_8UC3)); const Scalar zero = Scalar::all(0); @@ -571,8 +571,8 @@ static void csbp_operator(StereoConstantSpaceBP &rthis, oclMat u[2], oclMat d[2] int rows = left.rows; int cols = left.cols; - rthis.levels = min(rthis.levels, int(log((double)rthis.ndisp) / log(2.0))); - int levels = rthis.levels; + pThis->levels = min(pThis->levels, int(log((double)pThis->ndisp) / log(2.0))); + int levels = pThis->levels; AutoBuffer buf(levels * 4); @@ -583,7 +583,7 @@ static void csbp_operator(StereoConstantSpaceBP &rthis, oclMat u[2], oclMat d[2] cols_pyr[0] = cols; rows_pyr[0] = rows; - nr_plane_pyr[0] = rthis.nr_plane; + nr_plane_pyr[0] = pThis->nr_plane; const int n = 64; step_pyr[0] = alignSize(cols * sizeof(T), n) / sizeof(T); @@ -617,16 +617,16 @@ static void csbp_operator(StereoConstantSpaceBP &rthis, oclMat u[2], oclMat d[2] data_cost_selected.create(msg_size, DataType::type); Size temp_size = data_cost_size; - if (data_cost_size.width * data_cost_size.height < step_pyr[0] * rows_pyr[levels - 1] * rthis.ndisp) - temp_size = Size(step_pyr[0], rows_pyr[levels - 1] * rthis.ndisp); + if (data_cost_size.width * data_cost_size.height < step_pyr[0] * rows_pyr[levels - 1] * pThis->ndisp) + temp_size = Size(step_pyr[0], rows_pyr[levels - 1] * pThis->ndisp); temp.create(temp_size, DataType::type); temp = zero; ///////////////////////////////// Compute//////////////////////////////////////////////// - //csbp::load_constants(rthis.ndisp, rthis.max_data_term, rthis.data_weight, - // rthis.max_disc_term, rthis.disc_single_jump, rthis.min_disp_th, left, right, temp); + //csbp::load_constants(pThis->ndisp, pThis->max_data_term, pThis->data_weight, + // pThis->max_disc_term, pThis->disc_single_jump, pThis->min_disp_th, left, right, temp); l[0] = zero; d[0] = zero; @@ -650,14 +650,14 @@ static void csbp_operator(StereoConstantSpaceBP &rthis, oclMat u[2], oclMat d[2] { if (i == levels - 1) { - cv::ocl::stereoCSBP::init_data_cost(left, right, temp, rthis, disp_selected_pyr[cur_idx].data, + cv::ocl::stereoCSBP::init_data_cost(left, right, temp, pThis, disp_selected_pyr[cur_idx].data, data_cost_selected.data, step_pyr[0], rows_pyr[i], cols_pyr[i], i, nr_plane_pyr[i]); } else { cv::ocl::stereoCSBP::compute_data_cost( - disp_selected_pyr[cur_idx].data, data_cost.data, rthis, step_pyr[0], + disp_selected_pyr[cur_idx].data, data_cost.data, pThis, step_pyr[0], step_pyr[0], left, right, rows_pyr[i], cols_pyr[i], rows_pyr[i + 1], i, nr_plane_pyr[i + 1]); @@ -666,14 +666,14 @@ static void csbp_operator(StereoConstantSpaceBP &rthis, oclMat u[2], oclMat d[2] cv::ocl::stereoCSBP::init_message(u[new_idx].data, d[new_idx].data, l[new_idx].data, r[new_idx].data, u[cur_idx].data, d[cur_idx].data, l[cur_idx].data, r[cur_idx].data, disp_selected_pyr[new_idx].data, disp_selected_pyr[cur_idx].data, - data_cost_selected.data, data_cost.data, temp, rthis, step_pyr[0], + data_cost_selected.data, data_cost.data, temp, pThis, step_pyr[0], step_pyr[0], rows_pyr[i], cols_pyr[i], nr_plane_pyr[i], rows_pyr[i + 1], cols_pyr[i + 1], nr_plane_pyr[i + 1]); cur_idx = new_idx; } cv::ocl::stereoCSBP::calc_all_iterations(u[cur_idx].data, d[cur_idx].data, l[cur_idx].data, r[cur_idx].data, data_cost_selected.data, disp_selected_pyr[cur_idx].data, temp, - rthis, step_pyr[0], rows_pyr[i], cols_pyr[i], nr_plane_pyr[i]); + pThis, step_pyr[0], rows_pyr[i], cols_pyr[i], nr_plane_pyr[i]); } if (disp.empty()) @@ -683,23 +683,21 @@ static void csbp_operator(StereoConstantSpaceBP &rthis, oclMat u[2], oclMat d[2] out = zero; stereoCSBP::compute_disp(u[cur_idx].data, d[cur_idx].data, l[cur_idx].data, r[cur_idx].data, - data_cost_selected.data, disp_selected_pyr[cur_idx].data, rthis, step_pyr[0], + data_cost_selected.data, disp_selected_pyr[cur_idx].data, pThis, step_pyr[0], out, nr_plane_pyr[0]); if (disp.type() != CV_16S) out.convertTo(disp, disp.type()); } -typedef void (*csbp_operator_t)(StereoConstantSpaceBP &rthis, oclMat u[2], oclMat d[2], oclMat l[2], oclMat r[2], - oclMat disp_selected_pyr[2], oclMat &data_cost, oclMat &data_cost_selected, - oclMat &temp, oclMat &out, const oclMat &left, const oclMat &right, oclMat &disp); - -const static csbp_operator_t operators[] = {0, 0, 0, csbp_operator, 0, csbp_operator, 0, 0}; - void cv::ocl::StereoConstantSpaceBP::operator()(const oclMat &left, const oclMat &right, oclMat &disp) { CV_Assert(msg_type == CV_32F || msg_type == CV_16S); - operators[msg_type](*this, u, d, l, r, disp_selected_pyr, data_cost, data_cost_selected, temp, out, - left, right, disp); + if (msg_type == CV_16S) + csbp_operator(this, u, d, l, r, disp_selected_pyr, data_cost, data_cost_selected, temp, out, + left, right, disp); + else + csbp_operator(this, u, d, l, r, disp_selected_pyr, data_cost, data_cost_selected, temp, out, + left, right, disp); } From 9285dddbaad296e9c8bd5bc9013a68954d0a99e5 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 16 Sep 2016 16:03:35 +0300 Subject: [PATCH 039/409] android: clone build_sdk.py from 3.x --- platforms/android/build_sdk.py | 324 +++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100755 platforms/android/build_sdk.py diff --git a/platforms/android/build_sdk.py b/platforms/android/build_sdk.py new file mode 100755 index 0000000000..7d52eab9c0 --- /dev/null +++ b/platforms/android/build_sdk.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python + +import os, sys, subprocess, argparse, shutil, glob, re +import logging as log +import xml.etree.ElementTree as ET + +class Fail(Exception): + def __init__(self, text=None): + self.t = text + def __str__(self): + return "ERROR" if self.t is None else self.t + +def execute(cmd, shell=False): + try: + log.info("Executing: %s" % cmd) + retcode = subprocess.call(cmd, shell=shell) + if retcode < 0: + raise Fail("Child was terminated by signal:" %s -retcode) + elif retcode > 0: + raise Fail("Child returned: %s" % retcode) + except OSError as e: + raise Fail("Execution failed: %d / %s" % (e.errno, e.strerror)) + +def rm_one(d): + d = os.path.abspath(d) + if os.path.exists(d): + if os.path.isdir(d): + log.info("Removing dir: %s", d) + shutil.rmtree(d) + elif os.path.isfile(d): + log.info("Removing file: %s", d) + os.remove(d) + +def check_dir(d, create=False, clean=False): + d = os.path.abspath(d) + log.info("Check dir %s (create: %s, clean: %s)", d, create, clean) + if os.path.exists(d): + if not os.path.isdir(d): + raise Fail("Not a directory: %s" % d) + if clean: + for x in glob.glob(os.path.join(d, "*")): + rm_one(x) + else: + if create: + os.makedirs(d) + return d + +def determine_engine_version(manifest_path): + with open(manifest_path, "rt") as f: + return re.search(r'android:versionName="(\d+\.\d+)"', f.read(), re.MULTILINE).group(1) + +def determine_opencv_version(version_hpp_path): + # version in 2.4 - CV_VERSION_EPOCH.CV_VERSION_MAJOR.CV_VERSION_MINOR.CV_VERSION_REVISION + # version in master - CV_VERSION_MAJOR.CV_VERSION_MINOR.CV_VERSION_REVISION-CV_VERSION_STATUS + with open(version_hpp_path, "rt") as f: + data = f.read() + major = re.search(r'^#define\W+CV_VERSION_MAJOR\W+(\d+)$', data, re.MULTILINE).group(1) + minor = re.search(r'^#define\W+CV_VERSION_MINOR\W+(\d+)$', data, re.MULTILINE).group(1) + revision = re.search(r'^#define\W+CV_VERSION_REVISION\W+(\d+)$', data, re.MULTILINE).group(1) + version_status = re.search(r'^#define\W+CV_VERSION_STATUS\W+"([^"]*)"$', data, re.MULTILINE).group(1) + return "%(major)s.%(minor)s.%(revision)s%(version_status)s" % locals() + +#=================================================================================================== + +class ABI: + def __init__(self, platform_id, name, toolchain, cmake_name=None): + self.platform_id = platform_id # platform code to add to apk version (for cmake) + self.name = name # general name (official Android ABI identifier) + self.toolchain = toolchain # toolchain identifier (for cmake) + self.cmake_name = cmake_name # name of android toolchain (for cmake) + if self.cmake_name is None: + self.cmake_name = self.name + def __str__(self): + return "%s (%s)" % (self.name, self.toolchain) + def haveIPP(self): + return self.name == "x86" or self.name == "x86_64" + +ABIs = [ + ABI("2", "armeabi-v7a", "arm-linux-androideabi-4.8", cmake_name="armeabi-v7a with NEON"), + ABI("1", "armeabi", "arm-linux-androideabi-4.8"), + ABI("3", "arm64-v8a", "aarch64-linux-android-4.9"), + ABI("5", "x86_64", "x86_64-4.9"), + ABI("4", "x86", "x86-4.8"), + ABI("7", "mips64", "mips64el-linux-android-4.9"), + ABI("6", "mips", "mipsel-linux-android-4.8") +] + +#=================================================================================================== + +class Builder: + def __init__(self, workdir, opencvdir): + self.workdir = check_dir(workdir, create=True) + self.opencvdir = check_dir(opencvdir) + self.extra_modules_path = None + self.libdest = check_dir(os.path.join(self.workdir, "o4a"), create=True, clean=True) + self.docdest = check_dir(os.path.join(self.workdir, "javadoc"), create=True, clean=True) + self.resultdest = check_dir(os.path.join(self.workdir, "OpenCV-android-sdk"), create=True, clean=True) + self.extra_packs = [] + self.opencv_version = determine_opencv_version(os.path.join(self.opencvdir, "modules", "core", "include", "opencv2", "core", "version.hpp")) + self.engine_version = determine_engine_version(os.path.join(self.opencvdir, "platforms", "android", "service", "engine", "AndroidManifest.xml")) + self.use_ccache = True + + def get_toolchain_file(self): + return os.path.join(self.opencvdir, "platforms", "android", "android.toolchain.cmake") + + def get_engine_apk_dest(self, engdest): + return os.path.join(engdest, "platforms", "android", "service", "engine", ".build") + + def add_extra_pack(self, ver, path): + if path is None: + return + self.extra_packs.append((ver, check_dir(path))) + + def clean_library_build_dir(self): + for d in ["CMakeCache.txt", "CMakeFiles/", "bin/", "libs/", "lib/", "package/", "install/samples/"]: + rm_one(d) + + def build_library(self, abi, do_install): + cmd = [ + "cmake", + "-GNinja", + "-DCMAKE_TOOLCHAIN_FILE='%s'" % self.get_toolchain_file(), + "-DWITH_OPENCL=OFF", + "-DWITH_CUDA=OFF", + "-DWITH_IPP=%s" % ("ON" if abi.haveIPP() else "OFF"), + "-DBUILD_EXAMPLES=OFF", + "-DBUILD_TESTS=OFF", + "-DBUILD_PERF_TESTS=OFF", + "-DBUILD_DOCS=OFF", + "-DBUILD_ANDROID_EXAMPLES=ON", + "-DINSTALL_ANDROID_EXAMPLES=ON", + "-DANDROID_STL=gnustl_static", + "-DANDROID_NATIVE_API_LEVEL=9", + "-DANDROID_ABI='%s'" % abi.cmake_name, + "-DWITH_TBB=ON", + "-DANDROID_TOOLCHAIN_NAME=%s" % abi.toolchain + ] + + if self.extra_modules_path is not None: + cmd.append("-DOPENCV_EXTRA_MODULES_PATH='%s'" % self.extra_modules_path) + + cmd.append(self.opencvdir) + + if self.use_ccache == True: + cmd.append("-DNDK_CCACHE=ccache") + if do_install: + cmd.extend(["-DBUILD_TESTS=ON", "-DINSTALL_TESTS=ON"]) + execute(cmd) + if do_install: + execute(["ninja"]) + for c in ["libs", "dev", "java", "samples"]: + execute(["cmake", "-DCOMPONENT=%s" % c, "-P", "cmake_install.cmake"]) + else: + execute(["ninja", "install/strip"]) + + def build_engine(self, abi, engdest): + cmd = [ + "cmake", + "-GNinja", + "-DCMAKE_TOOLCHAIN_FILE='%s'" % self.get_toolchain_file(), + "-DANDROID_ABI='%s'" % abi.cmake_name, + "-DBUILD_ANDROID_SERVICE=ON", + "-DANDROID_PLATFORM_ID=%s" % abi.platform_id, + "-DWITH_CUDA=OFF", + "-DWITH_OPENCL=OFF", + "-DWITH_IPP=OFF", + self.opencvdir + ] + execute(cmd) + apkdest = self.get_engine_apk_dest(engdest) + # Add extra data + apkxmldest = check_dir(os.path.join(apkdest, "res", "xml"), create=True) + apklibdest = check_dir(os.path.join(apkdest, "libs", abi.name), create=True) + for ver, d in self.extra_packs + [("3.1.0", os.path.join(self.libdest, "lib"))]: + r = ET.Element("library", attrib={"version": ver}) + log.info("Adding libraries from %s", d) + + for f in glob.glob(os.path.join(d, abi.name, "*.so")): + log.info("Copy file: %s", f) + shutil.copy2(f, apklibdest) + if "libnative_camera" in f: + continue + log.info("Register file: %s", os.path.basename(f)) + n = ET.SubElement(r, "file", attrib={"name": os.path.basename(f)}) + + if len(list(r)) > 0: + xmlname = os.path.join(apkxmldest, "config%s.xml" % ver.replace(".", "")) + log.info("Generating XML config: %s", xmlname) + ET.ElementTree(r).write(xmlname, encoding="utf-8") + + execute(["ninja", "opencv_engine"]) + execute(["ant", "-f", os.path.join(apkdest, "build.xml"), "debug"], + shell=(sys.platform == 'win32')) + # TODO: Sign apk + + def build_javadoc(self): + classpaths = [os.path.join(self.libdest, "bin", "classes")] + for dir, _, files in os.walk(os.environ["ANDROID_SDK"]): + for f in files: + if f == "android.jar" or f == "annotations.jar": + classpaths.append(os.path.join(dir, f)) + cmd = [ + "javadoc", + "-header", "OpenCV %s" % self.opencv_version, + "-nodeprecated", + "-footer", 'OpenCV %s Documentation' % self.opencv_version, + "-public", + "-sourcepath", os.path.join(self.libdest, "src"), + "-d", self.docdest, + "-classpath", ":".join(classpaths) + ] + for _, dirs, _ in os.walk(os.path.join(self.libdest, "src", "org", "opencv")): + cmd.extend(["org.opencv." + d for d in dirs]) + execute(cmd) + + def gather_results(self, engines): + # Copy all files + root = os.path.join(self.libdest, "install") + for item in os.listdir(root): + name = item + item = os.path.join(root, item) + if os.path.isdir(item): + log.info("Copy dir: %s", item) + shutil.copytree(item, os.path.join(self.resultdest, name)) + elif os.path.isfile(item): + log.info("Copy file: %s", item) + shutil.copy2(item, os.path.join(self.resultdest, name)) + + # Copy engines for all platforms + for abi, engdest in engines: + log.info("Copy engine: %s (%s)", abi, engdest) + f = os.path.join(self.get_engine_apk_dest(engdest), "bin", "opencv_engine-debug.apk") + resname = "OpenCV_%s_Manager_%s_%s.apk" % (self.opencv_version, self.engine_version, abi) + shutil.copy2(f, os.path.join(self.resultdest, "apk", resname)) + + # Copy javadoc + log.info("Copy docs: %s", self.docdest) + shutil.copytree(self.docdest, os.path.join(self.resultdest, "sdk", "java", "javadoc")) + + # Clean samples + path = os.path.join(self.resultdest, "samples") + for item in os.listdir(path): + item = os.path.join(path, item) + if os.path.isdir(item): + for name in ["build.xml", "local.properties", "proguard-project.txt"]: + rm_one(os.path.join(item, name)) + + +#=================================================================================================== + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Build OpenCV for Android SDK') + parser.add_argument("work_dir", help="Working directory (and output)") + parser.add_argument("opencv_dir", help="Path to OpenCV source dir") + parser.add_argument('--ndk_path', help="Path to Android NDK to use for build") + parser.add_argument('--sdk_path', help="Path to Android SDK to use for build") + parser.add_argument("--extra_modules_path", help="Path to extra modules to use for build") + parser.add_argument('--sign_with', help="Sertificate to sign the Manager apk") + parser.add_argument('--build_doc', action="store_true", help="Build javadoc") + parser.add_argument('--no_ccache', action="store_true", help="Do not use ccache during library build") + parser.add_argument('--extra_pack', action='append', help="provide extra OpenCV libraries for Manager apk in form :, for example '2.4.11:unpacked/sdk/native/libs'") + args = parser.parse_args() + + log.basicConfig(format='%(message)s', level=log.DEBUG) + log.debug("Args: %s", args) + + if args.ndk_path is not None: + os.environ["ANDROID_NDK"] = args.ndk_path + if args.sdk_path is not None: + os.environ["ANDROID_SDK"] = args.sdk_path + + log.info("Android NDK path: %s", os.environ["ANDROID_NDK"]) + log.info("Android SDK path: %s", os.environ["ANDROID_SDK"]) + + builder = Builder(args.work_dir, args.opencv_dir) + + if args.extra_modules_path is not None: + builder.extra_modules_path = os.path.abspath(args.extra_modules_path) + + if args.no_ccache: + builder.use_ccache = False + + log.info("Detected OpenCV version: %s", builder.opencv_version) + log.info("Detected Engine version: %s", builder.engine_version) + + if args.extra_pack: + for one in args.extra_pack: + i = one.find(":") + if i > 0 and i < len(one) - 1: + builder.add_extra_pack(one[:i], one[i+1:]) + else: + raise Fail("Bad extra pack provided: %s, should be in form ':'" % one) + + engines = [] + for i, abi in enumerate(ABIs): + do_install = (i == 0) + engdest = check_dir(os.path.join(builder.workdir, "build_service_%s" % abi.name), create=True, clean=True) + + log.info("=====") + log.info("===== Building library for %s", abi) + log.info("=====") + + os.chdir(builder.libdest) + builder.clean_library_build_dir() + builder.build_library(abi, do_install) + + log.info("=====") + log.info("===== Building engine for %s", abi) + log.info("=====") + + os.chdir(engdest) + builder.build_engine(abi, engdest) + engines.append((abi.name, engdest)) + + if args.build_doc: + builder.build_javadoc() + + builder.gather_results(engines) + + log.info("=====") + log.info("===== Build finished") + log.info("=====") + log.info("SDK location: %s", builder.resultdest) + log.info("Documentation location: %s", builder.docdest) From 4d17051f14bda835c463ddd80215c29b307d2a21 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 16 Sep 2016 16:14:54 +0300 Subject: [PATCH 040/409] android: backport Android SDK build script --- cmake/OpenCVDetectAndroidSDK.cmake | 12 ++ cmake/templates/OpenCV.mk.in | 4 + .../src/java/android+OpenCVLoader.java | 13 ++ platforms/android/build_sdk.py | 157 +++++------------- platforms/android/service/CMakeLists.txt | 2 - platforms/android/service/doc/JavaHelper.rst | 8 + platforms/android/service/readme.txt | 2 + samples/android/face-detection/jni/Android.mk | 1 - 8 files changed, 77 insertions(+), 122 deletions(-) mode change 100755 => 100644 platforms/android/build_sdk.py diff --git a/cmake/OpenCVDetectAndroidSDK.cmake b/cmake/OpenCVDetectAndroidSDK.cmake index 38430e214e..4f26a78ece 100644 --- a/cmake/OpenCVDetectAndroidSDK.cmake +++ b/cmake/OpenCVDetectAndroidSDK.cmake @@ -274,6 +274,17 @@ macro(add_android_project target path) file(GLOB_RECURSE android_proj_jni_files "${path}/jni/*.c" "${path}/jni/*.h" "${path}/jni/*.cpp" "${path}/jni/*.hpp") ocv_list_filterout(android_proj_jni_files "\\\\.svn") + foreach(lib "opencv_java") + get_property(f TARGET ${lib} PROPERTY LOCATION) + get_filename_component(f_name ${f} NAME) + add_custom_command( + OUTPUT "${android_proj_bin_dir}/libs/${ANDROID_NDK_ABI_NAME}/${f_name}" + COMMAND ${CMAKE_COMMAND} -E copy "${f}" "${android_proj_bin_dir}/libs/${ANDROID_NDK_ABI_NAME}/${f_name}" + DEPENDS "${lib}" VERBATIM + COMMENT "Embedding ${f}") + list(APPEND android_proj_file_deps "${android_proj_bin_dir}/libs/${ANDROID_NDK_ABI_NAME}/${f_name}") + endforeach() + if(android_proj_jni_files AND EXISTS ${path}/jni/Android.mk AND NOT DEFINED JNI_LIB_NAME) # find local module name in Android.mk file to build native lib file(STRINGS "${path}/jni/Android.mk" JNI_LIB_NAME REGEX "LOCAL_MODULE[ ]*:=[ ]*.*" ) @@ -307,6 +318,7 @@ macro(add_android_project target path) # copy opencv_java, tbb if it is shared and dynamicuda if present if FORCE_EMBED_OPENCV flag is set if(android_proj_FORCE_EMBED_OPENCV) set(native_deps ${android_proj_NATIVE_DEPS}) + list(REMOVE_ITEM native_deps "opencv_java") # filter out gpu module as it is always static library on Android list(REMOVE_ITEM native_deps "opencv_gpu") if(ENABLE_DYNAMIC_CUDA) diff --git a/cmake/templates/OpenCV.mk.in b/cmake/templates/OpenCV.mk.in index 66d476b895..d85ed2bb84 100644 --- a/cmake/templates/OpenCV.mk.in +++ b/cmake/templates/OpenCV.mk.in @@ -110,6 +110,10 @@ else OPENCV_INSTALL_MODULES:=on endif +ifeq ($(OPENCV_INSTALL_MODULES),) + OPENCV_INSTALL_MODULES:=on +endif + define add_opencv_module include $(CLEAR_VARS) LOCAL_MODULE:=opencv_$1 diff --git a/modules/java/generator/src/java/android+OpenCVLoader.java b/modules/java/generator/src/java/android+OpenCVLoader.java index 2c05272c22..c220f19588 100644 --- a/modules/java/generator/src/java/android+OpenCVLoader.java +++ b/modules/java/generator/src/java/android+OpenCVLoader.java @@ -1,12 +1,15 @@ package org.opencv.android; import android.content.Context; +import android.util.Log; /** * Helper class provides common initialization methods for OpenCV library. */ public class OpenCVLoader { + private static final String TAG = "OpenCVLoader"; + /** * OpenCV Library version 2.4.2. */ @@ -62,6 +65,11 @@ public class OpenCVLoader */ public static final String OPENCV_VERSION_2_4_12 = "2.4.12"; + /** + * OpenCV Library version 2.4.13. + */ + public static final String OPENCV_VERSION_2_4_13 = "2.4.13"; + /** * Loads and initializes OpenCV library from current application package. Roughly, it's an analog of system.loadLibrary("opencv_java"). * @return Returns true is initialization of OpenCV was successful. @@ -91,6 +99,11 @@ public class OpenCVLoader public static boolean initAsync(String Version, Context AppContext, LoaderCallbackInterface Callback) { + if (initDebug()) { + Callback.onManagerConnected(LoaderCallbackInterface.SUCCESS); + return true; + } + Log.w(TAG, "OpenCV binaries are not packaged with application. Trying to use OpenCV Manager..."); return AsyncServiceHelper.initOpenCV(Version, AppContext, Callback); } } diff --git a/platforms/android/build_sdk.py b/platforms/android/build_sdk.py old mode 100755 new mode 100644 index 7d52eab9c0..7251948d7b --- a/platforms/android/build_sdk.py +++ b/platforms/android/build_sdk.py @@ -10,13 +10,13 @@ class Fail(Exception): def __str__(self): return "ERROR" if self.t is None else self.t -def execute(cmd, shell=False): +def execute(cmd, shell=False, allowFail=False): try: log.info("Executing: %s" % cmd) retcode = subprocess.call(cmd, shell=shell) if retcode < 0: raise Fail("Child was terminated by signal:" %s -retcode) - elif retcode > 0: + elif retcode > 0 and not allowFail: raise Fail("Child returned: %s" % retcode) except OSError as e: raise Fail("Execution failed: %d / %s" % (e.errno, e.strerror)) @@ -45,44 +45,37 @@ def check_dir(d, create=False, clean=False): os.makedirs(d) return d -def determine_engine_version(manifest_path): - with open(manifest_path, "rt") as f: - return re.search(r'android:versionName="(\d+\.\d+)"', f.read(), re.MULTILINE).group(1) - def determine_opencv_version(version_hpp_path): # version in 2.4 - CV_VERSION_EPOCH.CV_VERSION_MAJOR.CV_VERSION_MINOR.CV_VERSION_REVISION # version in master - CV_VERSION_MAJOR.CV_VERSION_MINOR.CV_VERSION_REVISION-CV_VERSION_STATUS with open(version_hpp_path, "rt") as f: data = f.read() + epoch = re.search(r'^#define\W+CV_VERSION_EPOCH\W+(\d+)$', data, re.MULTILINE).group(1) major = re.search(r'^#define\W+CV_VERSION_MAJOR\W+(\d+)$', data, re.MULTILINE).group(1) minor = re.search(r'^#define\W+CV_VERSION_MINOR\W+(\d+)$', data, re.MULTILINE).group(1) revision = re.search(r'^#define\W+CV_VERSION_REVISION\W+(\d+)$', data, re.MULTILINE).group(1) - version_status = re.search(r'^#define\W+CV_VERSION_STATUS\W+"([^"]*)"$', data, re.MULTILINE).group(1) - return "%(major)s.%(minor)s.%(revision)s%(version_status)s" % locals() + revision = '' if revision == '0' else '.' + revision + return "%(epoch)s.%(major)s.%(minor)s%(revision)s" % locals() #=================================================================================================== class ABI: - def __init__(self, platform_id, name, toolchain, cmake_name=None): + def __init__(self, platform_id, name, toolchain, api_level=8, cmake_name=None): self.platform_id = platform_id # platform code to add to apk version (for cmake) self.name = name # general name (official Android ABI identifier) self.toolchain = toolchain # toolchain identifier (for cmake) + self.api_level = api_level self.cmake_name = cmake_name # name of android toolchain (for cmake) if self.cmake_name is None: self.cmake_name = self.name def __str__(self): return "%s (%s)" % (self.name, self.toolchain) - def haveIPP(self): - return self.name == "x86" or self.name == "x86_64" ABIs = [ - ABI("2", "armeabi-v7a", "arm-linux-androideabi-4.8", cmake_name="armeabi-v7a with NEON"), - ABI("1", "armeabi", "arm-linux-androideabi-4.8"), - ABI("3", "arm64-v8a", "aarch64-linux-android-4.9"), - ABI("5", "x86_64", "x86_64-4.9"), - ABI("4", "x86", "x86-4.8"), - ABI("7", "mips64", "mips64el-linux-android-4.9"), - ABI("6", "mips", "mipsel-linux-android-4.8") + ABI("2", "armeabi-v7a", "arm-linux-androideabi-4.6", cmake_name="armeabi-v7a with NEON"), + ABI("1", "armeabi", "arm-linux-androideabi-4.6"), + ABI("4", "x86", "x86-clang3.1", api_level=9), + ABI("6", "mips", "mipsel-linux-android-4.6", api_level=9) ] #=================================================================================================== @@ -91,38 +84,30 @@ class Builder: def __init__(self, workdir, opencvdir): self.workdir = check_dir(workdir, create=True) self.opencvdir = check_dir(opencvdir) - self.extra_modules_path = None self.libdest = check_dir(os.path.join(self.workdir, "o4a"), create=True, clean=True) self.docdest = check_dir(os.path.join(self.workdir, "javadoc"), create=True, clean=True) self.resultdest = check_dir(os.path.join(self.workdir, "OpenCV-android-sdk"), create=True, clean=True) - self.extra_packs = [] self.opencv_version = determine_opencv_version(os.path.join(self.opencvdir, "modules", "core", "include", "opencv2", "core", "version.hpp")) - self.engine_version = determine_engine_version(os.path.join(self.opencvdir, "platforms", "android", "service", "engine", "AndroidManifest.xml")) self.use_ccache = True def get_toolchain_file(self): return os.path.join(self.opencvdir, "platforms", "android", "android.toolchain.cmake") - def get_engine_apk_dest(self, engdest): - return os.path.join(engdest, "platforms", "android", "service", "engine", ".build") - - def add_extra_pack(self, ver, path): - if path is None: - return - self.extra_packs.append((ver, check_dir(path))) - def clean_library_build_dir(self): for d in ["CMakeCache.txt", "CMakeFiles/", "bin/", "libs/", "lib/", "package/", "install/samples/"]: rm_one(d) - def build_library(self, abi, do_install): + def build_library(self, abi, do_install, build_docs): cmd = [ "cmake", "-GNinja", "-DCMAKE_TOOLCHAIN_FILE='%s'" % self.get_toolchain_file(), - "-DWITH_OPENCL=OFF", - "-DWITH_CUDA=OFF", - "-DWITH_IPP=%s" % ("ON" if abi.haveIPP() else "OFF"), + "-DINSTALL_CREATE_DISTRIB=ON", + #"-DWITH_OPENCL=OFF", + "-DWITH_CUDA=OFF", "-DBUILD_opencv_gpu=OFF", + "-DBUILD_opencv_nonfree=OFF", + "-DWITH_TBB=OFF", + "-DWITH_IPP=OFF", "-DBUILD_EXAMPLES=OFF", "-DBUILD_TESTS=OFF", "-DBUILD_PERF_TESTS=OFF", @@ -130,68 +115,28 @@ class Builder: "-DBUILD_ANDROID_EXAMPLES=ON", "-DINSTALL_ANDROID_EXAMPLES=ON", "-DANDROID_STL=gnustl_static", - "-DANDROID_NATIVE_API_LEVEL=9", + "-DANDROID_NATIVE_API_LEVEL=%s" % abi.api_level, "-DANDROID_ABI='%s'" % abi.cmake_name, - "-DWITH_TBB=ON", "-DANDROID_TOOLCHAIN_NAME=%s" % abi.toolchain ] - if self.extra_modules_path is not None: - cmd.append("-DOPENCV_EXTRA_MODULES_PATH='%s'" % self.extra_modules_path) - cmd.append(self.opencvdir) if self.use_ccache == True: cmd.append("-DNDK_CCACHE=ccache") if do_install: cmd.extend(["-DBUILD_TESTS=ON", "-DINSTALL_TESTS=ON"]) + if do_install and build_docs: + cmd.extend(["-DBUILD_DOCS=ON"]) execute(cmd) if do_install: - execute(["ninja"]) - for c in ["libs", "dev", "java", "samples"]: + execute(["cmake", "--build", "."]) + if do_install and build_docs: + execute(["cmake", "--build", ".", "--target", "docs"]) + for c in ["libs", "dev", "java", "samples"] + (["docs"] if do_install and build_docs else []): execute(["cmake", "-DCOMPONENT=%s" % c, "-P", "cmake_install.cmake"]) else: - execute(["ninja", "install/strip"]) - - def build_engine(self, abi, engdest): - cmd = [ - "cmake", - "-GNinja", - "-DCMAKE_TOOLCHAIN_FILE='%s'" % self.get_toolchain_file(), - "-DANDROID_ABI='%s'" % abi.cmake_name, - "-DBUILD_ANDROID_SERVICE=ON", - "-DANDROID_PLATFORM_ID=%s" % abi.platform_id, - "-DWITH_CUDA=OFF", - "-DWITH_OPENCL=OFF", - "-DWITH_IPP=OFF", - self.opencvdir - ] - execute(cmd) - apkdest = self.get_engine_apk_dest(engdest) - # Add extra data - apkxmldest = check_dir(os.path.join(apkdest, "res", "xml"), create=True) - apklibdest = check_dir(os.path.join(apkdest, "libs", abi.name), create=True) - for ver, d in self.extra_packs + [("3.1.0", os.path.join(self.libdest, "lib"))]: - r = ET.Element("library", attrib={"version": ver}) - log.info("Adding libraries from %s", d) - - for f in glob.glob(os.path.join(d, abi.name, "*.so")): - log.info("Copy file: %s", f) - shutil.copy2(f, apklibdest) - if "libnative_camera" in f: - continue - log.info("Register file: %s", os.path.basename(f)) - n = ET.SubElement(r, "file", attrib={"name": os.path.basename(f)}) - - if len(list(r)) > 0: - xmlname = os.path.join(apkxmldest, "config%s.xml" % ver.replace(".", "")) - log.info("Generating XML config: %s", xmlname) - ET.ElementTree(r).write(xmlname, encoding="utf-8") - - execute(["ninja", "opencv_engine"]) - execute(["ant", "-f", os.path.join(apkdest, "build.xml"), "debug"], - shell=(sys.platform == 'win32')) - # TODO: Sign apk + execute(["cmake", "--build", ".", "--target", "install/strip"]) def build_javadoc(self): classpaths = [os.path.join(self.libdest, "bin", "classes")] @@ -201,6 +146,7 @@ class Builder: classpaths.append(os.path.join(dir, f)) cmd = [ "javadoc", + "-encoding", "UTF-8", "-header", "OpenCV %s" % self.opencv_version, "-nodeprecated", "-footer", 'OpenCV %s Documentation' % self.opencv_version, @@ -211,9 +157,9 @@ class Builder: ] for _, dirs, _ in os.walk(os.path.join(self.libdest, "src", "org", "opencv")): cmd.extend(["org.opencv." + d for d in dirs]) - execute(cmd) + execute(cmd, allowFail=True) # FIXIT javadoc currenly reports some errors - def gather_results(self, engines): + def gather_results(self, with_samples_apk): # Copy all files root = os.path.join(self.libdest, "install") for item in os.listdir(root): @@ -226,13 +172,6 @@ class Builder: log.info("Copy file: %s", item) shutil.copy2(item, os.path.join(self.resultdest, name)) - # Copy engines for all platforms - for abi, engdest in engines: - log.info("Copy engine: %s (%s)", abi, engdest) - f = os.path.join(self.get_engine_apk_dest(engdest), "bin", "opencv_engine-debug.apk") - resname = "OpenCV_%s_Manager_%s_%s.apk" % (self.opencv_version, self.engine_version, abi) - shutil.copy2(f, os.path.join(self.resultdest, "apk", resname)) - # Copy javadoc log.info("Copy docs: %s", self.docdest) shutil.copytree(self.docdest, os.path.join(self.resultdest, "sdk", "java", "javadoc")) @@ -244,6 +183,9 @@ class Builder: if os.path.isdir(item): for name in ["build.xml", "local.properties", "proguard-project.txt"]: rm_one(os.path.join(item, name)) + if not with_samples_apk: + if re.search(r'\.apk$', item): # reduce size of SDK + rm_one(item) #=================================================================================================== @@ -254,11 +196,10 @@ if __name__ == "__main__": parser.add_argument("opencv_dir", help="Path to OpenCV source dir") parser.add_argument('--ndk_path', help="Path to Android NDK to use for build") parser.add_argument('--sdk_path', help="Path to Android SDK to use for build") - parser.add_argument("--extra_modules_path", help="Path to extra modules to use for build") - parser.add_argument('--sign_with', help="Sertificate to sign the Manager apk") - parser.add_argument('--build_doc', action="store_true", help="Build javadoc") + parser.add_argument('--build_doc', action="store_true", help="Build documentation") + parser.add_argument('--build_javadoc', action="store_true", help="Build javadoc") parser.add_argument('--no_ccache', action="store_true", help="Do not use ccache during library build") - parser.add_argument('--extra_pack', action='append', help="provide extra OpenCV libraries for Manager apk in form :, for example '2.4.11:unpacked/sdk/native/libs'") + parser.add_argument('--with_samples_apk', action="store_true", help="Include samples APKs") args = parser.parse_args() log.basicConfig(format='%(message)s', level=log.DEBUG) @@ -274,27 +215,13 @@ if __name__ == "__main__": builder = Builder(args.work_dir, args.opencv_dir) - if args.extra_modules_path is not None: - builder.extra_modules_path = os.path.abspath(args.extra_modules_path) - if args.no_ccache: builder.use_ccache = False log.info("Detected OpenCV version: %s", builder.opencv_version) - log.info("Detected Engine version: %s", builder.engine_version) - if args.extra_pack: - for one in args.extra_pack: - i = one.find(":") - if i > 0 and i < len(one) - 1: - builder.add_extra_pack(one[:i], one[i+1:]) - else: - raise Fail("Bad extra pack provided: %s, should be in form ':'" % one) - - engines = [] for i, abi in enumerate(ABIs): do_install = (i == 0) - engdest = check_dir(os.path.join(builder.workdir, "build_service_%s" % abi.name), create=True, clean=True) log.info("=====") log.info("===== Building library for %s", abi) @@ -302,20 +229,12 @@ if __name__ == "__main__": os.chdir(builder.libdest) builder.clean_library_build_dir() - builder.build_library(abi, do_install) + builder.build_library(abi, do_install, build_docs=args.build_doc) - log.info("=====") - log.info("===== Building engine for %s", abi) - log.info("=====") - - os.chdir(engdest) - builder.build_engine(abi, engdest) - engines.append((abi.name, engdest)) - - if args.build_doc: + if args.build_doc or args.build_javadoc: builder.build_javadoc() - builder.gather_results(engines) + builder.gather_results(with_samples_apk=args.with_samples_apk) log.info("=====") log.info("===== Build finished") diff --git a/platforms/android/service/CMakeLists.txt b/platforms/android/service/CMakeLists.txt index c99b71392f..7b1522bf79 100644 --- a/platforms/android/service/CMakeLists.txt +++ b/platforms/android/service/CMakeLists.txt @@ -2,5 +2,3 @@ if(BUILD_ANDROID_SERVICE) add_subdirectory(engine) #add_subdirectory(engine_test) endif() - -install(FILES "readme.txt" DESTINATION "apk/" COMPONENT libs) diff --git a/platforms/android/service/doc/JavaHelper.rst b/platforms/android/service/doc/JavaHelper.rst index 6be46d61a3..3c3762b9e3 100644 --- a/platforms/android/service/doc/JavaHelper.rst +++ b/platforms/android/service/doc/JavaHelper.rst @@ -79,3 +79,11 @@ OpenCV version constants .. data:: OPENCV_VERSION_2_4_11 OpenCV Library version 2.4.11 + +.. data:: OPENCV_VERSION_2_4_12 + + OpenCV Library version 2.4.12 + +.. data:: OPENCV_VERSION_2_4_13 + + OpenCV Library version 2.4.13 diff --git a/platforms/android/service/readme.txt b/platforms/android/service/readme.txt index 51853c24e5..c8230e38d8 100644 --- a/platforms/android/service/readme.txt +++ b/platforms/android/service/readme.txt @@ -1,6 +1,8 @@ How to select the proper version of OpenCV Manager -------------------------------------------------- +DEPRECATED: This information is outdated since OpenCV 2.4.12 + Since version 1.7 several packages of OpenCV Manager are built. Every package is targeted for some specific hardware platform and includes corresponding OpenCV binaries. So, in most cases OpenCV Manager uses built-in version of OpenCV. Separate package with OpenCV binaries is currently used in diff --git a/samples/android/face-detection/jni/Android.mk b/samples/android/face-detection/jni/Android.mk index 6b6642602d..cf20dff2b5 100644 --- a/samples/android/face-detection/jni/Android.mk +++ b/samples/android/face-detection/jni/Android.mk @@ -3,7 +3,6 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) #OPENCV_CAMERA_MODULES:=off -#OPENCV_INSTALL_MODULES:=off #OPENCV_LIB_TYPE:=SHARED include ../../sdk/native/jni/OpenCV.mk From 2d10336601ba026c29dcb5de08ffd641085a65ca Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 16 Sep 2016 16:10:41 +0300 Subject: [PATCH 041/409] fix documentation --- README.md | 6 +++--- cmake/OpenCVDetectPython.cmake | 7 ++++--- doc/CMakeLists.txt | 4 +--- doc/conf.py | 2 +- .../random_generator_and_text.rst | 2 +- .../feature_detection/feature_detection.rst | 2 +- .../feature_homography/feature_homography.rst | 2 +- .../android_binary_package/O4A_SDK.rst | 6 +----- .../dev_with_OCV_on_Android.rst | 17 +++++++++-------- modules/contrib/doc/facerec/index.rst | 4 ---- samples/cpp/Qt_sample/qt_opengl.cpp | 2 +- 11 files changed, 23 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index e74a329668..787b71f330 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ #### Resources * Homepage: -* Docs: +* Docs: * Q&A forum: -* Issue tracking: +* Issue tracking: #### Contributing -Please read before starting work on a pull request: +Please read before starting work on a pull request: Summary of guidelines: diff --git a/cmake/OpenCVDetectPython.cmake b/cmake/OpenCVDetectPython.cmake index 7ef548f0f1..20df69e170 100644 --- a/cmake/OpenCVDetectPython.cmake +++ b/cmake/OpenCVDetectPython.cmake @@ -123,10 +123,11 @@ if(PYTHON_EXECUTABLE) find_host_program(SPHINX_BUILD sphinx-build) if(SPHINX_BUILD) execute_process(COMMAND "${SPHINX_BUILD}" - OUTPUT_QUIET - ERROR_VARIABLE SPHINX_OUTPUT + ERROR_VARIABLE SPHINX_STDERR + OUTPUT_VARIABLE SPHINX_STDOUT OUTPUT_STRIP_TRAILING_WHITESPACE) - if(SPHINX_OUTPUT MATCHES "Sphinx v([0-9][^ \n]*)") + if(SPHINX_STDERR MATCHES "Sphinx v([0-9][^ \n]*)" + OR SPHINX_STDOUT MATCHES "Sphinx v([0-9][^ \n]*)") set(SPHINX_VERSION "${CMAKE_MATCH_1}") set(HAVE_SPHINX 1) message(STATUS "Found Sphinx ${SPHINX_VERSION}: ${SPHINX_BUILD}") diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 66d2a478a2..29d5a0a1e1 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -40,9 +40,7 @@ endif(HAVE_DOC_GENERATOR) # ========= Sphinx docs ========= if(BUILD_DOCS AND HAVE_SPHINX) - if(NOT INSTALL_CREATE_DISTRIB) - list(APPEND DOC_LIST "${OpenCV_SOURCE_DIR}/doc/haartraining.htm") - endif() + list(APPEND DOC_LIST "${OpenCV_SOURCE_DIR}/doc/haartraining.htm") # build lists of documentation files and generate table of contents for reference manual set(DOC_FAKE_ROOT "${CMAKE_CURRENT_BINARY_DIR}/fake-root") diff --git a/doc/conf.py b/doc/conf.py index 0bf75ad488..430a83579c 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -315,7 +315,7 @@ extlinks = { # 'opencv_group' : ('http://answers.opencv.org/%s', None), 'opencv_qa' : ('http://answers.opencv.org/%s', None), - 'how_to_contribute' : ('http://code.opencv.org/projects/opencv/wiki/How_to_contribute/%s', None), + 'how_to_contribute' : ('https://github.com/opencv/opencv/wiki/How_to_contribute/%s', None), 'cvt_color' : ('http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html?highlight=cvtcolor#cvtcolor%s', None), 'imread' : ('http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html?highlight=imread#imread%s', None), diff --git a/doc/tutorials/core/random_generator_and_text/random_generator_and_text.rst b/doc/tutorials/core/random_generator_and_text/random_generator_and_text.rst index a5c4bb7ed6..3a743f9d33 100644 --- a/doc/tutorials/core/random_generator_and_text/random_generator_and_text.rst +++ b/doc/tutorials/core/random_generator_and_text/random_generator_and_text.rst @@ -22,7 +22,7 @@ Code * In this tutorial, we intend to use *random* values for the drawing parameters. Also, we intend to populate our image with a big number of geometric figures. Since we will be initializing them in a random fashion, this process will be automatic and made by using *loops* . - * This code is in your OpenCV sample folder. Otherwise you can grab it from `here `_ . + * This code is in your OpenCV sample folder. Otherwise you can grab it from `here `_ . Explanation ============ diff --git a/doc/tutorials/features2d/feature_detection/feature_detection.rst b/doc/tutorials/features2d/feature_detection/feature_detection.rst index 51776de1fc..e495d36b45 100644 --- a/doc/tutorials/features2d/feature_detection/feature_detection.rst +++ b/doc/tutorials/features2d/feature_detection/feature_detection.rst @@ -22,7 +22,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/features2d/feature_homography/feature_homography.rst b/doc/tutorials/features2d/feature_homography/feature_homography.rst index e195e53f43..def8759b8f 100644 --- a/doc/tutorials/features2d/feature_homography/feature_homography.rst +++ b/doc/tutorials/features2d/feature_homography/feature_homography.rst @@ -20,7 +20,7 @@ Theory Code ==== -This tutorial code's is shown lines below. You can also download it from `here `_ +This tutorial code's is shown lines below. You can also download it from `here `_ .. code-block:: cpp diff --git a/doc/tutorials/introduction/android_binary_package/O4A_SDK.rst b/doc/tutorials/introduction/android_binary_package/O4A_SDK.rst index f4d957fc1a..8f7a2fbf1b 100644 --- a/doc/tutorials/introduction/android_binary_package/O4A_SDK.rst +++ b/doc/tutorials/introduction/android_binary_package/O4A_SDK.rst @@ -48,11 +48,7 @@ The structure of package contents looks as follows: :: - OpenCV-2.4.11-android-sdk - |_ apk - | |_ OpenCV_2.4.11_binary_pack_armv7a.apk - | |_ OpenCV_2.4.11_Manager_2.20_XXX.apk - | + OpenCV-2.4.13-android-sdk |_ doc |_ samples |_ sdk diff --git a/doc/tutorials/introduction/android_binary_package/dev_with_OCV_on_Android.rst b/doc/tutorials/introduction/android_binary_package/dev_with_OCV_on_Android.rst index c83548e98b..7b9204e0b1 100644 --- a/doc/tutorials/introduction/android_binary_package/dev_with_OCV_on_Android.rst +++ b/doc/tutorials/introduction/android_binary_package/dev_with_OCV_on_Android.rst @@ -55,14 +55,14 @@ Manager to access OpenCV libraries externally installed in the target system. :guilabel:`File -> Import -> Existing project in your workspace`. Press :guilabel:`Browse` button and locate OpenCV4Android SDK - (:file:`OpenCV-2.4.11-android-sdk/sdk`). + (:file:`OpenCV-2.4.13-android-sdk/sdk`). .. image:: images/eclipse_opencv_dependency0.png :alt: Add dependency from OpenCV library :align: center #. In application project add a reference to the OpenCV Java SDK in - :guilabel:`Project -> Properties -> Android -> Library -> Add` select ``OpenCV Library - 2.4.11``. + :guilabel:`Project -> Properties -> Android -> Library -> Add` select ``OpenCV Library - 2.4.13``. .. image:: images/eclipse_opencv_dependency1.png :alt: Add dependency from OpenCV library @@ -128,27 +128,27 @@ described above. #. Add the OpenCV library project to your workspace the same way as for the async initialization above. Use menu :guilabel:`File -> Import -> Existing project in your workspace`, press :guilabel:`Browse` button and select OpenCV SDK path - (:file:`OpenCV-2.4.11-android-sdk/sdk`). + (:file:`OpenCV-2.4.13-android-sdk/sdk`). .. image:: images/eclipse_opencv_dependency0.png :alt: Add dependency from OpenCV library :align: center #. In the application project add a reference to the OpenCV4Android SDK in - :guilabel:`Project -> Properties -> Android -> Library -> Add` select ``OpenCV Library - 2.4.11``; + :guilabel:`Project -> Properties -> Android -> Library -> Add` select ``OpenCV Library - 2.4.13``; .. image:: images/eclipse_opencv_dependency1.png :alt: Add dependency from OpenCV library :align: center #. If your application project **doesn't have a JNI part**, just copy the corresponding OpenCV - native libs from :file:`/sdk/native/libs/` to your + native libs from :file:`/sdk/native/libs/` to your project directory to folder :file:`libs/`. In case of the application project **with a JNI part**, instead of manual libraries copying you need to modify your ``Android.mk`` file: add the following two code lines after the ``"include $(CLEAR_VARS)"`` and before - ``"include path_to_OpenCV-2.4.11-android-sdk/sdk/native/jni/OpenCV.mk"`` + ``"include path_to_OpenCV-2.4.13-android-sdk/sdk/native/jni/OpenCV.mk"`` .. code-block:: make :linenos: @@ -221,7 +221,7 @@ taken: .. code-block:: make - include C:\Work\OpenCV4Android\OpenCV-2.4.11-android-sdk\sdk\native\jni\OpenCV.mk + include C:\Work\OpenCV4Android\OpenCV-2.4.13-android-sdk\sdk\native\jni\OpenCV.mk Should be inserted into the :file:`jni/Android.mk` file **after** this line: @@ -230,7 +230,8 @@ taken: include $(CLEAR_VARS) #. Several variables can be used to customize OpenCV stuff, but you **don't need** to use them when - your application uses the `async initialization` via the `OpenCV Manager` API. + your application uses the `async initialization` via the `OpenCV Manager` API + (It is not recommended since OpenCV 2.4.13). .. note:: These variables should be set **before** the ``"include .../OpenCV.mk"`` line: diff --git a/modules/contrib/doc/facerec/index.rst b/modules/contrib/doc/facerec/index.rst index b871448c55..4605d7c900 100644 --- a/modules/contrib/doc/facerec/index.rst +++ b/modules/contrib/doc/facerec/index.rst @@ -5,10 +5,6 @@ OpenCV 2.4 now comes with the very new :ocv:class:`FaceRecognizer` class for fac These documents are the help I have wished for, when I was working myself into face recognition. I hope you also think the new :ocv:class:`FaceRecognizer` is a useful addition to OpenCV. -Please issue any feature requests and/or bugs on the official OpenCV bug tracker at: - - * http://code.opencv.org/projects/opencv/issues - Contents ======== diff --git a/samples/cpp/Qt_sample/qt_opengl.cpp b/samples/cpp/Qt_sample/qt_opengl.cpp index eebb17b50a..47b5478e43 100644 --- a/samples/cpp/Qt_sample/qt_opengl.cpp +++ b/samples/cpp/Qt_sample/qt_opengl.cpp @@ -30,7 +30,7 @@ static void help() "Using OpenCV version " << CV_VERSION << "\n\n" " 1) This demo is mainly based on work from Javier Barandiaran Martirena\n" - " See this page http://code.opencv.org/projects/opencv/wiki/Posit.\n" + " See this page https://github.com/opencv/opencv/wiki/Posit .\n" " 2) This is a demo to illustrate how to use **OpenGL Callback**.\n" " 3) You need Qt binding to compile this sample with OpenGL support enabled.\n" " 4) The features' detection is very basic and could highly be improved\n" From d68e3502278d6fc5a1de0ce8f7951d9961b20913 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 16 Sep 2016 14:58:07 +0300 Subject: [PATCH 042/409] OpenCV version++ --- modules/core/include/opencv2/core/version.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/version.hpp b/modules/core/include/opencv2/core/version.hpp index 39e66583d2..e4f15abea0 100644 --- a/modules/core/include/opencv2/core/version.hpp +++ b/modules/core/include/opencv2/core/version.hpp @@ -50,7 +50,7 @@ #define CV_VERSION_EPOCH 2 #define CV_VERSION_MAJOR 4 #define CV_VERSION_MINOR 13 -#define CV_VERSION_REVISION 0 +#define CV_VERSION_REVISION 1 #define CVAUX_STR_EXP(__A) #__A #define CVAUX_STR(__A) CVAUX_STR_EXP(__A) From 3648c6117225bc1e385bfd10b1e63b1e943c9f0d Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 19 Sep 2016 19:46:42 +0300 Subject: [PATCH 043/409] highgui: clone cap_avfoundation_mac.mm from master --- modules/highgui/src/cap_avfoundation_mac.mm | 1322 +++++++++++++++++++ 1 file changed, 1322 insertions(+) create mode 100644 modules/highgui/src/cap_avfoundation_mac.mm diff --git a/modules/highgui/src/cap_avfoundation_mac.mm b/modules/highgui/src/cap_avfoundation_mac.mm new file mode 100644 index 0000000000..8c30506400 --- /dev/null +++ b/modules/highgui/src/cap_avfoundation_mac.mm @@ -0,0 +1,1322 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the contributor be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*//////////////////////////////////////////////////////////////////////////////////////// + + +#include "precomp.hpp" +#include "opencv2/imgproc.hpp" +#include +#import + +/********************** Declaration of class headers ************************/ + +/***************************************************************************** + * + * CaptureDelegate Declaration. + * + * CaptureDelegate is notified on a separate thread by the OS whenever there + * is a new frame. When "updateImage" is called from the main thread, it + * copies this new frame into an IplImage, but only if this frame has not + * been copied before. When "getOutput" is called from the main thread, + * it gives the last copied IplImage. + * + *****************************************************************************/ + + +@interface CaptureDelegate : NSObject +{ + NSCondition *mHasNewFrame; + CVPixelBufferRef mGrabbedPixels; + CVImageBufferRef mCurrentImageBuffer; + IplImage *mDeviceImage; + uint8_t *mOutImagedata; + IplImage *mOutImage; + size_t currSize; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput +didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection; + +- (BOOL)grabImageUntilDate: (NSDate *)limit; +- (int)updateImage; +- (IplImage*)getOutput; + +@end + +/***************************************************************************** + * + * CvCaptureCAM Declaration. + * + * CvCaptureCAM is the instantiation of a capture source for cameras. + * + *****************************************************************************/ + +class CvCaptureCAM : public CvCapture { +public: + CvCaptureCAM(int cameraNum = -1) ; + ~CvCaptureCAM(); + virtual bool grabFrame(); + virtual IplImage* retrieveFrame(int); + virtual double getProperty(int property_id) const; + virtual bool setProperty(int property_id, double value); + virtual int didStart(); + + +private: + AVCaptureSession *mCaptureSession; + AVCaptureDeviceInput *mCaptureDeviceInput; + AVCaptureVideoDataOutput *mCaptureVideoDataOutput; + AVCaptureDevice *mCaptureDevice; + CaptureDelegate *mCapture; + + int startCaptureDevice(int cameraNum); + void stopCaptureDevice(); + + void setWidthHeight(); + bool grabFrame(double timeOut); + + int camNum; + int width; + int height; + int settingWidth; + int settingHeight; + OSType mInputPixelFormat; + + int started; +}; + + +/***************************************************************************** + * + * CvCaptureFile Declaration. + * + * CvCaptureFile is the instantiation of a capture source for video files. + * + *****************************************************************************/ + +class CvCaptureFile : public CvCapture { +public: + CvCaptureFile(const char* filename) ; + ~CvCaptureFile(); + virtual bool grabFrame(); + virtual IplImage* retrieveFrame(int); + virtual double getProperty(int property_id) const; + virtual bool setProperty(int property_id, double value); + virtual int didStart(); + + +private: + AVAsset *mAsset; + AVAssetTrack *mAssetTrack; + AVAssetReader *mAssetReader; + AVAssetReaderTrackOutput *mTrackOutput; + + CMSampleBufferRef mCurrentSampleBuffer; + CVImageBufferRef mGrabbedPixels; + IplImage *mDeviceImage; + uint8_t *mOutImagedata; + IplImage *mOutImage; + size_t currSize; + int mMode; + int mFormat; + + bool setupReadingAt(CMTime position); + IplImage* retrieveFramePixelBuffer(); + + CMTime mFrameTimestamp; + size_t mFrameNum; + OSType mInputPixelFormat; + + int started; +}; + + +/***************************************************************************** + * + * CvVideoWriter_AVFoundation Declaration. + * + * CvVideoWriter_AVFoundation is the instantiation of a video output class. + * + *****************************************************************************/ + +class CvVideoWriter_AVFoundation : public CvVideoWriter { + public: + CvVideoWriter_AVFoundation(const char* filename, int fourcc, + double fps, CvSize frame_size, + int is_color=1); + ~CvVideoWriter_AVFoundation(); + bool writeFrame(const IplImage* image); + private: + IplImage* argbimage; + + AVAssetWriter *mMovieWriter; + AVAssetWriterInput* mMovieWriterInput; + AVAssetWriterInputPixelBufferAdaptor* mMovieWriterAdaptor; + + NSString* path; + NSString* codec; + NSString* fileType; + double mMovieFPS; + CvSize movieSize; + int movieColor; + unsigned long mFrameNum; +}; + +/****************** Implementation of interface functions ********************/ + + +CvCapture* cvCreateFileCapture_AVFoundation(const char* filename) { + CvCaptureFile *retval = new CvCaptureFile(filename); + + if(retval->didStart()) + return retval; + delete retval; + return NULL; +} + +CvCapture* cvCreateCameraCapture_AVFoundation(int index ) { + CvCapture* retval = new CvCaptureCAM(index); + if (!((CvCaptureCAM *)retval)->didStart()) + cvReleaseCapture(&retval); + return retval; +} + +CvVideoWriter* cvCreateVideoWriter_AVFoundation(const char* filename, int fourcc, + double fps, CvSize frame_size, + int is_color) { + return new CvVideoWriter_AVFoundation(filename, fourcc, fps, frame_size,is_color); +} + +/********************** Implementation of Classes ****************************/ + +/***************************************************************************** + * + * CvCaptureCAM Implementation. + * + * CvCaptureCAM is the instantiation of a capture source for cameras. + * + *****************************************************************************/ + +CvCaptureCAM::CvCaptureCAM(int cameraNum) { + mCaptureSession = nil; + mCaptureDeviceInput = nil; + mCaptureVideoDataOutput = nil; + mCaptureDevice = nil; + mCapture = nil; + + width = 0; + height = 0; + settingWidth = 0; + settingHeight = 0; + + camNum = cameraNum; + + if ( ! startCaptureDevice(camNum) ) { + fprintf(stderr, "OpenCV: camera failed to properly initialize!\n"); + started = 0; + } else { + started = 1; + } +} + +CvCaptureCAM::~CvCaptureCAM() { + stopCaptureDevice(); +} + +int CvCaptureCAM::didStart() { + return started; +} + + +bool CvCaptureCAM::grabFrame() { + return grabFrame(1); +} + +bool CvCaptureCAM::grabFrame(double timeOut) { + NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; + + bool isGrabbed = false; + NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: timeOut]; + if ( [mCapture grabImageUntilDate: limit] ) { + [mCapture updateImage]; + isGrabbed = true; + } + + [localpool drain]; + return isGrabbed; +} + +IplImage* CvCaptureCAM::retrieveFrame(int) { + return [mCapture getOutput]; +} + +void CvCaptureCAM::stopCaptureDevice() { + NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; + + [mCaptureSession stopRunning]; + + [mCaptureSession release]; + [mCaptureDeviceInput release]; + [mCaptureDevice release]; + + [mCaptureVideoDataOutput release]; + [mCapture release]; + + [localpool drain]; +} + +int CvCaptureCAM::startCaptureDevice(int cameraNum) { + NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; + + // get capture device + NSArray *devices = [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo]; + + if ( devices.count == 0 ) { + fprintf(stderr, "OpenCV: AVFoundation didn't find any attached Video Input Devices!\n"); + [localpool drain]; + return 0; + } + + if ( cameraNum < 0 || devices.count <= NSUInteger(cameraNum) ) { + fprintf(stderr, "OpenCV: out device of bound (0-%ld): %d\n", devices.count-1, cameraNum); + [localpool drain]; + return 0; + } + + mCaptureDevice = devices[cameraNum]; + + if ( ! mCaptureDevice ) { + fprintf(stderr, "OpenCV: device %d not able to use.\n", cameraNum); + [localpool drain]; + return 0; + } + + // get input device + NSError *error = nil; + mCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice: mCaptureDevice + error: &error]; + if ( error ) { + fprintf(stderr, "OpenCV: error in [AVCaptureDeviceInput initWithDevice:error:]\n"); + NSLog(@"OpenCV: %@", error.localizedDescription); + [localpool drain]; + return 0; + } + + // create output + mCapture = [[CaptureDelegate alloc] init]; + mCaptureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init]; + dispatch_queue_t queue = dispatch_queue_create("cameraQueue", DISPATCH_QUEUE_SERIAL); + [mCaptureVideoDataOutput setSampleBufferDelegate: mCapture queue: queue]; + dispatch_release(queue); + + OSType pixelFormat = kCVPixelFormatType_32BGRA; + //OSType pixelFormat = kCVPixelFormatType_422YpCbCr8; + NSDictionary *pixelBufferOptions; + if (width > 0 && height > 0) { + pixelBufferOptions = + @{ + (id)kCVPixelBufferWidthKey: @(1.0*width), + (id)kCVPixelBufferHeightKey: @(1.0*height), + (id)kCVPixelBufferPixelFormatTypeKey: @(pixelFormat) + }; + } else { + pixelBufferOptions = + @{ + (id)kCVPixelBufferPixelFormatTypeKey: @(pixelFormat) + }; + } + mCaptureVideoDataOutput.videoSettings = pixelBufferOptions; + mCaptureVideoDataOutput.alwaysDiscardsLateVideoFrames = YES; + + // create session + mCaptureSession = [[AVCaptureSession alloc] init]; + mCaptureSession.sessionPreset = AVCaptureSessionPresetMedium; + [mCaptureSession addInput: mCaptureDeviceInput]; + [mCaptureSession addOutput: mCaptureVideoDataOutput]; + + [mCaptureSession startRunning]; + + // flush old position image + grabFrame(1); + + [localpool drain]; + return 1; +} + +void CvCaptureCAM::setWidthHeight() { + NSMutableDictionary *pixelBufferOptions = [mCaptureVideoDataOutput.videoSettings mutableCopy]; + + while ( true ) { + // auto matching + pixelBufferOptions[(id)kCVPixelBufferWidthKey] = @(1.0*width); + pixelBufferOptions[(id)kCVPixelBufferHeightKey] = @(1.0*height); + mCaptureVideoDataOutput.videoSettings = pixelBufferOptions; + + // compare matched size and my options + CMFormatDescriptionRef format = mCaptureDevice.activeFormat.formatDescription; + CMVideoDimensions deviceSize = CMVideoFormatDescriptionGetDimensions(format); + if ( deviceSize.width == width && deviceSize.height == height ) { + break; + } + + // fit my options to matched size + width = deviceSize.width; + height = deviceSize.height; + } + + // flush old size image + grabFrame(1); + + [pixelBufferOptions release]; +} + + +double CvCaptureCAM::getProperty(int property_id) const{ + NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; + + CMFormatDescriptionRef format = mCaptureDevice.activeFormat.formatDescription; + CMVideoDimensions s1 = CMVideoFormatDescriptionGetDimensions(format); + double retval = 0; + + switch (property_id) { + case CV_CAP_PROP_FRAME_WIDTH: + retval = s1.width; + break; + case CV_CAP_PROP_FRAME_HEIGHT: + retval = s1.height; + break; + case CV_CAP_PROP_FPS: + { + CMTime frameDuration = mCaptureDevice.activeVideoMaxFrameDuration; + retval = frameDuration.timescale / double(frameDuration.value); + } + break; + case CV_CAP_PROP_FORMAT: + retval = CV_8UC3; + break; + default: + break; + } + + [localpool drain]; + return retval; +} + +bool CvCaptureCAM::setProperty(int property_id, double value) { + NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; + + bool isSucceeded = false; + + switch (property_id) { + case CV_CAP_PROP_FRAME_WIDTH: + width = value; + settingWidth = 1; + if (settingWidth && settingHeight) { + setWidthHeight(); + settingWidth = 0; + settingHeight = 0; + } + isSucceeded = true; + break; + case CV_CAP_PROP_FRAME_HEIGHT: + height = value; + settingHeight = 1; + if (settingWidth && settingHeight) { + setWidthHeight(); + settingWidth = 0; + settingHeight = 0; + } + isSucceeded = true; + break; + case CV_CAP_PROP_FPS: + if ( [mCaptureDevice lockForConfiguration: NULL] ) { + NSArray * ranges = mCaptureDevice.activeFormat.videoSupportedFrameRateRanges; + AVFrameRateRange *matchedRange = ranges[0]; + double minDiff = fabs(matchedRange.maxFrameRate - value); + for ( AVFrameRateRange *range in ranges ) { + double diff = fabs(range.maxFrameRate - value); + if ( diff < minDiff ) { + minDiff = diff; + matchedRange = range; + } + } + mCaptureDevice.activeVideoMinFrameDuration = matchedRange.minFrameDuration; + mCaptureDevice.activeVideoMaxFrameDuration = matchedRange.minFrameDuration; + isSucceeded = true; + [mCaptureDevice unlockForConfiguration]; + } + break; + default: + break; + } + + [localpool drain]; + return isSucceeded; +} + + +/***************************************************************************** + * + * CaptureDelegate Implementation. + * + * CaptureDelegate is notified on a separate thread by the OS whenever there + * is a new frame. When "updateImage" is called from the main thread, it + * copies this new frame into an IplImage, but only if this frame has not + * been copied before. When "getOutput" is called from the main thread, + * it gives the last copied IplImage. + * + *****************************************************************************/ + + +@implementation CaptureDelegate + +- (id)init { + [super init]; + mHasNewFrame = [[NSCondition alloc] init]; + mCurrentImageBuffer = NULL; + mGrabbedPixels = NULL; + mDeviceImage = NULL; + mOutImagedata = NULL; + mOutImage = NULL; + currSize = 0; + return self; +} + +-(void)dealloc { + free(mOutImagedata); + cvReleaseImage(&mOutImage); + cvReleaseImage(&mDeviceImage); + CVBufferRelease(mCurrentImageBuffer); + CVBufferRelease(mGrabbedPixels); + [mHasNewFrame release]; + [super dealloc]; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput +didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection { + (void)captureOutput; + (void)sampleBuffer; + (void)connection; + + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CVBufferRetain(imageBuffer); + + [mHasNewFrame lock]; + + CVBufferRelease(mCurrentImageBuffer); + mCurrentImageBuffer = imageBuffer; + [mHasNewFrame signal]; + + [mHasNewFrame unlock]; + +} + +-(IplImage*) getOutput { + return mOutImage; +} + +-(BOOL) grabImageUntilDate: (NSDate *)limit { + BOOL isGrabbed = NO; + [mHasNewFrame lock]; + + if ( mGrabbedPixels ) { + CVBufferRelease(mGrabbedPixels); + } + if ( [mHasNewFrame waitUntilDate: limit] ) { + isGrabbed = YES; + mGrabbedPixels = CVBufferRetain(mCurrentImageBuffer); + } + + [mHasNewFrame unlock]; + return isGrabbed; +} + +-(int) updateImage { + if ( ! mGrabbedPixels ) { + return 0; + } + + CVPixelBufferLockBaseAddress(mGrabbedPixels, 0); + void *baseaddress = CVPixelBufferGetBaseAddress(mGrabbedPixels); + + size_t width = CVPixelBufferGetWidth(mGrabbedPixels); + size_t height = CVPixelBufferGetHeight(mGrabbedPixels); + size_t rowBytes = CVPixelBufferGetBytesPerRow(mGrabbedPixels); + OSType pixelFormat = CVPixelBufferGetPixelFormatType(mGrabbedPixels); + + if ( rowBytes == 0 ) { + fprintf(stderr, "OpenCV: error: rowBytes == 0\n"); + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + return 0; + } + + if ( currSize != width*3*height ) { + currSize = width*3*height; + free(mOutImagedata); + mOutImagedata = reinterpret_cast(malloc(currSize)); + } + + if (mOutImage == NULL) { + mOutImage = cvCreateImageHeader(cvSize((int)width,(int)height), IPL_DEPTH_8U, 3); + } + mOutImage->width = int(width); + mOutImage->height = int(height); + mOutImage->nChannels = 3; + mOutImage->depth = IPL_DEPTH_8U; + mOutImage->widthStep = int(width*3); + mOutImage->imageData = reinterpret_cast(mOutImagedata); + mOutImage->imageSize = int(currSize); + + if ( pixelFormat == kCVPixelFormatType_32BGRA ) { + if (mDeviceImage == NULL) { + mDeviceImage = cvCreateImageHeader(cvSize(int(width),int(height)), IPL_DEPTH_8U, 4); + } + mDeviceImage->width = int(width); + mDeviceImage->height = int(height); + mDeviceImage->nChannels = 4; + mDeviceImage->depth = IPL_DEPTH_8U; + mDeviceImage->widthStep = int(rowBytes); + mDeviceImage->imageData = reinterpret_cast(baseaddress); + mDeviceImage->imageSize = int(rowBytes*height); + + cvCvtColor(mDeviceImage, mOutImage, CV_BGRA2BGR); + } else if ( pixelFormat == kCVPixelFormatType_422YpCbCr8 ) { + if ( currSize != width*3*height ) { + currSize = width*3*height; + free(mOutImagedata); + mOutImagedata = reinterpret_cast(malloc(currSize)); + } + + if (mDeviceImage == NULL) { + mDeviceImage = cvCreateImageHeader(cvSize(int(width),int(height)), IPL_DEPTH_8U, 2); + } + mDeviceImage->width = int(width); + mDeviceImage->height = int(height); + mDeviceImage->nChannels = 2; + mDeviceImage->depth = IPL_DEPTH_8U; + mDeviceImage->widthStep = int(rowBytes); + mDeviceImage->imageData = reinterpret_cast(baseaddress); + mDeviceImage->imageSize = int(rowBytes*height); + + cvCvtColor(mDeviceImage, mOutImage, CV_YUV2BGR_UYVY); + } else { + fprintf(stderr, "OpenCV: unknown pixel format 0x%08X\n", pixelFormat); + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + return 0; + } + + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + + return 1; +} + +@end + + +/***************************************************************************** + * + * CvCaptureFile Implementation. + * + * CvCaptureFile is the instantiation of a capture source for video files. + * + *****************************************************************************/ + +CvCaptureFile::CvCaptureFile(const char* filename) { + NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; + + mAsset = nil; + mAssetTrack = nil; + mAssetReader = nil; + mTrackOutput = nil; + mDeviceImage = NULL; + mOutImage = NULL; + mOutImagedata = NULL; + currSize = 0; + mMode = CV_CAP_MODE_BGR; + mFormat = CV_8UC3; + mCurrentSampleBuffer = NULL; + mGrabbedPixels = NULL; + mFrameTimestamp = kCMTimeZero; + mFrameNum = 0; + + started = 0; + + mAsset = [[AVAsset assetWithURL:[NSURL fileURLWithPath: @(filename)]] retain]; + + if ( mAsset == nil ) { + fprintf(stderr, "OpenCV: Couldn't read movie file \"%s\"\n", filename); + [localpool drain]; + started = 0; + return; + } + + mAssetTrack = [[mAsset tracksWithMediaType: AVMediaTypeVideo][0] retain]; + + if ( ! setupReadingAt(kCMTimeZero) ) { + fprintf(stderr, "OpenCV: Couldn't read movie file \"%s\"\n", filename); + [localpool drain]; + started = 0; + return; + } + + started = 1; + [localpool drain]; +} + +CvCaptureFile::~CvCaptureFile() { + NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; + + free(mOutImagedata); + cvReleaseImage(&mOutImage); + cvReleaseImage(&mDeviceImage); + [mAssetReader release]; + [mTrackOutput release]; + [mAssetTrack release]; + [mAsset release]; + CVBufferRelease(mGrabbedPixels); + if ( mCurrentSampleBuffer ) { + CFRelease(mCurrentSampleBuffer); + } + + [localpool drain]; +} + +bool CvCaptureFile::setupReadingAt(CMTime position) { + if (mAssetReader) { + if (mAssetReader.status == AVAssetReaderStatusReading) { + [mAssetReader cancelReading]; + } + [mAssetReader release]; + mAssetReader = nil; + } + if (mTrackOutput) { + [mTrackOutput release]; + mTrackOutput = nil; + } + + // Capture in a pixel format that can be converted efficiently to the output mode. + OSType pixelFormat; + if (mMode == CV_CAP_MODE_BGR || mMode == CV_CAP_MODE_RGB) { + // For CV_CAP_MODE_BGR, read frames as BGRA (AV Foundation's YUV->RGB conversion is slightly faster than OpenCV's CV_YUV2BGR_YV12) + // kCVPixelFormatType_32ABGR is reportedly faster on OS X, but OpenCV doesn't have a CV_ABGR2BGR conversion. + // kCVPixelFormatType_24RGB is significanly slower than kCVPixelFormatType_32BGRA. + pixelFormat = kCVPixelFormatType_32BGRA; + mFormat = CV_8UC3; + } else if (mMode == CV_CAP_MODE_GRAY) { + // For CV_CAP_MODE_GRAY, read frames as 420v (faster than 420f or 422 -- at least for H.264 files) + pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; + mFormat = CV_8UC1; + } else if (mMode == CV_CAP_MODE_YUYV) { + // For CV_CAP_MODE_YUYV, read frames directly as 422. + pixelFormat = kCVPixelFormatType_422YpCbCr8; + mFormat = CV_8UC2; + } else { + fprintf(stderr, "VIDEOIO ERROR: AVF Mac: Unsupported mode: %d\n", mMode); + return false; + } + + NSDictionary *settings = + @{ + (id)kCVPixelBufferPixelFormatTypeKey: @(pixelFormat) + }; + mTrackOutput = [[AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack: mAssetTrack + outputSettings: settings] retain]; + + if ( !mTrackOutput ) { + fprintf(stderr, "OpenCV: error in [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:outputSettings:]\n"); + return false; + } + + NSError *error = nil; + mAssetReader = [[AVAssetReader assetReaderWithAsset: mAsset + error: &error] retain]; + if ( error ) { + fprintf(stderr, "OpenCV: error in [AVAssetReader assetReaderWithAsset:error:]\n"); + NSLog(@"OpenCV: %@", error.localizedDescription); + return false; + } + + mAssetReader.timeRange = CMTimeRangeMake(position, kCMTimePositiveInfinity); + mFrameTimestamp = position; + mFrameNum = round((mFrameTimestamp.value * mAssetTrack.nominalFrameRate) / double(mFrameTimestamp.timescale)); + [mAssetReader addOutput: mTrackOutput]; + [mAssetReader startReading]; + + return true; +} + +int CvCaptureFile::didStart() { + return started; +} + +bool CvCaptureFile::grabFrame() { + NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; + + CVBufferRelease(mGrabbedPixels); + if ( mCurrentSampleBuffer ) { + CFRelease(mCurrentSampleBuffer); + } + mCurrentSampleBuffer = [mTrackOutput copyNextSampleBuffer]; + mGrabbedPixels = CMSampleBufferGetImageBuffer(mCurrentSampleBuffer); + CVBufferRetain(mGrabbedPixels); + mFrameTimestamp = CMSampleBufferGetOutputPresentationTimeStamp(mCurrentSampleBuffer); + mFrameNum++; + + bool isReading = (mAssetReader.status == AVAssetReaderStatusReading); + [localpool drain]; + return isReading; +} + + +IplImage* CvCaptureFile::retrieveFramePixelBuffer() { + if ( ! mGrabbedPixels ) { + return 0; + } + + NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; + + CVPixelBufferLockBaseAddress(mGrabbedPixels, 0); + void *baseaddress; + size_t width, height, rowBytes; + + OSType pixelFormat = CVPixelBufferGetPixelFormatType(mGrabbedPixels); + + if (CVPixelBufferIsPlanar(mGrabbedPixels)) { + baseaddress = CVPixelBufferGetBaseAddressOfPlane(mGrabbedPixels, 0); + width = CVPixelBufferGetWidthOfPlane(mGrabbedPixels, 0); + height = CVPixelBufferGetHeightOfPlane(mGrabbedPixels, 0); + rowBytes = CVPixelBufferGetBytesPerRowOfPlane(mGrabbedPixels, 0); + } else { + baseaddress = CVPixelBufferGetBaseAddress(mGrabbedPixels); + width = CVPixelBufferGetWidth(mGrabbedPixels); + height = CVPixelBufferGetHeight(mGrabbedPixels); + rowBytes = CVPixelBufferGetBytesPerRow(mGrabbedPixels); + } + + if ( rowBytes == 0 ) { + fprintf(stderr, "OpenCV: error: rowBytes == 0\n"); + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + return 0; + } + + // Output image paramaters. + int outChannels; + if (mMode == CV_CAP_MODE_BGR || mMode == CV_CAP_MODE_RGB) { + outChannels = 3; + } else if (mMode == CV_CAP_MODE_GRAY) { + outChannels = 1; + } else if (mMode == CV_CAP_MODE_YUYV) { + outChannels = 2; + } else { + fprintf(stderr, "VIDEOIO ERROR: AVF Mac: Unsupported mode: %d\n", mMode); + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + return 0; + } + + if ( currSize != width*outChannels*height ) { + currSize = width*outChannels*height; + free(mOutImagedata); + mOutImagedata = reinterpret_cast(malloc(currSize)); + } + + // Build the header for the output image. + if (mOutImage == NULL) { + mOutImage = cvCreateImageHeader(cvSize((int)width,(int)height), IPL_DEPTH_8U, outChannels); + } + mOutImage->width = int(width); + mOutImage->height = int(height); + mOutImage->nChannels = outChannels; + mOutImage->depth = IPL_DEPTH_8U; + mOutImage->widthStep = int(width*outChannels); + mOutImage->imageData = reinterpret_cast(mOutImagedata); + mOutImage->imageSize = int(currSize); + + // Device image paramaters and conversion code. + // (Not all of these conversions are used in production, but they were all tested to find the fastest options.) + int deviceChannels; + int cvtCode; + + if ( pixelFormat == kCVPixelFormatType_32BGRA ) { + deviceChannels = 4; + + if (mMode == CV_CAP_MODE_BGR) { + cvtCode = CV_BGRA2BGR; + } else if (mMode == CV_CAP_MODE_RGB) { + cvtCode = CV_BGRA2RGB; + } else if (mMode == CV_CAP_MODE_GRAY) { + cvtCode = CV_BGRA2GRAY; + } else { + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + fprintf(stderr, "OpenCV: unsupported pixel conversion mode\n"); + return 0; + } + } else if ( pixelFormat == kCVPixelFormatType_24RGB ) { + deviceChannels = 3; + + if (mMode == CV_CAP_MODE_BGR) { + cvtCode = CV_RGB2BGR; + } else if (mMode == CV_CAP_MODE_RGB) { + cvtCode = 0; + } else if (mMode == CV_CAP_MODE_GRAY) { + cvtCode = CV_RGB2GRAY; + } else { + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + fprintf(stderr, "OpenCV: unsupported pixel conversion mode\n"); + return 0; + } + } else if ( pixelFormat == kCVPixelFormatType_422YpCbCr8 ) { // 422 (2vuy, UYVY) + deviceChannels = 2; + + if (mMode == CV_CAP_MODE_BGR) { + cvtCode = CV_YUV2BGR_UYVY; + } else if (mMode == CV_CAP_MODE_RGB) { + cvtCode = CV_YUV2RGB_UYVY; + } else if (mMode == CV_CAP_MODE_GRAY) { + cvtCode = CV_YUV2GRAY_UYVY; + } else if (mMode == CV_CAP_MODE_YUYV) { + cvtCode = -1; // Copy + } else { + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + fprintf(stderr, "OpenCV: unsupported pixel conversion mode\n"); + return 0; + } + } else if ( pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange || // 420v + pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ) { // 420f + // cvCvtColor(CV_YUV2GRAY_420) is expecting a single buffer with both the Y plane and the CrCb planes. + // So, lie about the height of the buffer. cvCvtColor(CV_YUV2GRAY_420) will only read the first 2/3 of it. + height = height * 3 / 2; + deviceChannels = 1; + + if (mMode == CV_CAP_MODE_BGR) { + cvtCode = CV_YUV2BGR_YV12; + } else if (mMode == CV_CAP_MODE_RGB) { + cvtCode = CV_YUV2RGB_YV12; + } else if (mMode == CV_CAP_MODE_GRAY) { + cvtCode = CV_YUV2GRAY_420; + } else { + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + fprintf(stderr, "OpenCV: unsupported pixel conversion mode\n"); + return 0; + } + } else { + fprintf(stderr, "OpenCV: unsupported pixel format 0x%08X\n", pixelFormat); + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + CVBufferRelease(mGrabbedPixels); + mGrabbedPixels = NULL; + return 0; + } + + // Build the header for the device image. + if (mDeviceImage == NULL) { + mDeviceImage = cvCreateImageHeader(cvSize(int(width),int(height)), IPL_DEPTH_8U, deviceChannels); + } + mDeviceImage->width = int(width); + mDeviceImage->height = int(height); + mDeviceImage->nChannels = deviceChannels; + mDeviceImage->depth = IPL_DEPTH_8U; + mDeviceImage->widthStep = int(rowBytes); + mDeviceImage->imageData = reinterpret_cast(baseaddress); + mDeviceImage->imageSize = int(rowBytes*height); + + // Convert the device image into the output image. + if (cvtCode == -1) { + // Copy. + cv::cvarrToMat(mDeviceImage).copyTo(cv::cvarrToMat(mOutImage)); + } else { + cvCvtColor(mDeviceImage, mOutImage, cvtCode); + } + + + CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); + + [localpool drain]; + + return mOutImage; +} + + +IplImage* CvCaptureFile::retrieveFrame(int) { + return retrieveFramePixelBuffer(); +} + +double CvCaptureFile::getProperty(int property_id) const{ + if (mAsset == nil) return 0; + + CMTime t; + + switch (property_id) { + case CV_CAP_PROP_POS_MSEC: + return mFrameTimestamp.value * 1000.0 / mFrameTimestamp.timescale; + case CV_CAP_PROP_POS_FRAMES: + return mFrameNum; + case CV_CAP_PROP_POS_AVI_RATIO: + t = [mAsset duration]; + return (mFrameTimestamp.value * t.timescale) / double(mFrameTimestamp.timescale * t.value); + case CV_CAP_PROP_FRAME_WIDTH: + return mAssetTrack.naturalSize.width; + case CV_CAP_PROP_FRAME_HEIGHT: + return mAssetTrack.naturalSize.height; + case CV_CAP_PROP_FPS: + return mAssetTrack.nominalFrameRate; + case CV_CAP_PROP_FRAME_COUNT: + t = [mAsset duration]; + return round((t.value * mAssetTrack.nominalFrameRate) / double(t.timescale)); + case CV_CAP_PROP_FORMAT: + return mFormat; + case CV_CAP_PROP_MODE: + return mMode; + default: + break; + } + + return 0; +} + +bool CvCaptureFile::setProperty(int property_id, double value) { + if (mAsset == nil) return false; + + NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init]; + + bool retval = false; + CMTime t; + + switch (property_id) { + case CV_CAP_PROP_POS_MSEC: + t = mAsset.duration; + t.value = value * t.timescale / 1000; + setupReadingAt(t); + retval = true; + break; + case CV_CAP_PROP_POS_FRAMES: + setupReadingAt(CMTimeMake(value, mAssetTrack.nominalFrameRate)); + retval = true; + break; + case CV_CAP_PROP_POS_AVI_RATIO: + t = mAsset.duration; + t.value = round(t.value * value); + setupReadingAt(t); + retval = true; + break; + case CV_CAP_PROP_MODE: + int mode; + mode = cvRound(value); + if (mMode == mode) { + retval = true; + } else { + switch (mode) { + case CV_CAP_MODE_BGR: + case CV_CAP_MODE_RGB: + case CV_CAP_MODE_GRAY: + case CV_CAP_MODE_YUYV: + mMode = mode; + setupReadingAt(mFrameTimestamp); + retval = true; + break; + default: + fprintf(stderr, "VIDEOIO ERROR: AVF Mac: Unsupported mode: %d\n", mode); + retval=false; + break; + } + } + break; + default: + break; + } + + [localpool drain]; + return retval; +} + + +/***************************************************************************** + * + * CvVideoWriter_AVFoundation Implementation. + * + * CvVideoWriter_AVFoundation is the instantiation of a video output class. + * + *****************************************************************************/ + + +CvVideoWriter_AVFoundation::CvVideoWriter_AVFoundation(const char* filename, int fourcc, + double fps, CvSize frame_size, + int is_color) { + + NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init]; + + + mFrameNum = 0; + mMovieFPS = fps; + movieSize = frame_size; + movieColor = is_color; + argbimage = cvCreateImage(movieSize, IPL_DEPTH_8U, 4); + path = [[[NSString stringWithCString:filename encoding:NSASCIIStringEncoding] stringByExpandingTildeInPath] retain]; + + + /* + AVFileTypeQuickTimeMovie + UTI for the QuickTime movie file format. + The value of this UTI is com.apple.quicktime-movie. Files are identified with the .mov and .qt extensions. + + AVFileTypeMPEG4 + UTI for the MPEG-4 file format. + The value of this UTI is public.mpeg-4. Files are identified with the .mp4 extension. + + AVFileTypeAppleM4V + UTI for the iTunes video file format. + The value of this UTI is com.apple.mpeg-4-video. Files are identified with the .m4v extension. + + AVFileType3GPP + UTI for the 3GPP file format. + The value of this UTI is public.3gpp. Files are identified with the .3gp, .3gpp, and .sdv extensions. + */ + + NSString *fileExt =[[[path pathExtension] lowercaseString] copy]; + if ([fileExt isEqualToString:@"mov"] || [fileExt isEqualToString:@"qt"]){ + fileType = [AVFileTypeQuickTimeMovie copy]; + }else if ([fileExt isEqualToString:@"mp4"]){ + fileType = [AVFileTypeMPEG4 copy]; + }else if ([fileExt isEqualToString:@"m4v"]){ + fileType = [AVFileTypeAppleM4V copy]; +#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + }else if ([fileExt isEqualToString:@"3gp"] || [fileExt isEqualToString:@"3gpp"] || [fileExt isEqualToString:@"sdv"] ){ + fileType = [AVFileType3GPP copy]; +#endif + } else{ + fileType = [AVFileTypeMPEG4 copy]; //default mp4 + } + [fileExt release]; + + char cc[5]; + cc[0] = fourcc & 255; + cc[1] = (fourcc >> 8) & 255; + cc[2] = (fourcc >> 16) & 255; + cc[3] = (fourcc >> 24) & 255; + cc[4] = 0; + int cc2 = CV_FOURCC(cc[0], cc[1], cc[2], cc[3]); + if (cc2!=fourcc) { + fprintf(stderr, "OpenCV: Didn't properly encode FourCC. Expected 0x%08X but got 0x%08X.\n", fourcc, cc2); + //exception; + } + + // Two codec supported AVVideoCodecH264 AVVideoCodecJPEG + // On iPhone 3G H264 is not supported. + if (fourcc == CV_FOURCC('J','P','E','G') || fourcc == CV_FOURCC('j','p','e','g') || + fourcc == CV_FOURCC('M','J','P','G') || fourcc == CV_FOURCC('m','j','p','g') ){ + codec = [AVVideoCodecJPEG copy]; // Use JPEG codec if specified, otherwise H264 + }else if(fourcc == CV_FOURCC('H','2','6','4') || fourcc == CV_FOURCC('a','v','c','1')){ + codec = [AVVideoCodecH264 copy]; + }else{ + codec = [AVVideoCodecH264 copy]; // default canonical H264. + + } + + //NSLog(@"Path: %@", path); + + NSError *error = nil; + + + // Make sure the file does not already exist. Necessary to overwirte?? + /* + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:path]){ + [fileManager removeItemAtPath:path error:&error]; + } + */ + + // Wire the writer: + // Supported file types: + // AVFileTypeQuickTimeMovie AVFileTypeMPEG4 AVFileTypeAppleM4V AVFileType3GPP + + mMovieWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:path] + fileType:fileType + error:&error]; + //NSParameterAssert(mMovieWriter); + + NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys: + codec, AVVideoCodecKey, + [NSNumber numberWithInt:movieSize.width], AVVideoWidthKey, + [NSNumber numberWithInt:movieSize.height], AVVideoHeightKey, + nil]; + + mMovieWriterInput = [[AVAssetWriterInput + assetWriterInputWithMediaType:AVMediaTypeVideo + outputSettings:videoSettings] retain]; + + //NSParameterAssert(mMovieWriterInput); + //NSParameterAssert([mMovieWriter canAddInput:mMovieWriterInput]); + + [mMovieWriter addInput:mMovieWriterInput]; + + mMovieWriterAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] initWithAssetWriterInput:mMovieWriterInput sourcePixelBufferAttributes:nil]; + + + //Start a session: + [mMovieWriter startWriting]; + [mMovieWriter startSessionAtSourceTime:kCMTimeZero]; + + + if(mMovieWriter.status == AVAssetWriterStatusFailed){ + NSLog(@"AVF: AVAssetWriter status: %@", [mMovieWriter.error localizedDescription]); + // TODO: error handling, cleanup. Throw execption? + // return; + } + + [localpool drain]; +} + + +CvVideoWriter_AVFoundation::~CvVideoWriter_AVFoundation() { + NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init]; + + [mMovieWriterInput markAsFinished]; + [mMovieWriter finishWriting]; + [mMovieWriter release]; + [mMovieWriterInput release]; + [mMovieWriterAdaptor release]; + [path release]; + [codec release]; + [fileType release]; + cvReleaseImage(&argbimage); + + [localpool drain]; + +} + +static void releaseCallback( void *releaseRefCon, const void * ) { + CFRelease((CFDataRef)releaseRefCon); +} + +bool CvVideoWriter_AVFoundation::writeFrame(const IplImage* iplimage) { + NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init]; + + // writer status check + if (mMovieWriter.status != AVAssetWriterStatusWriting ) { + NSLog(@"mMovieWriter.status: %d. Error: %@", (int)mMovieWriter.status, [mMovieWriter.error localizedDescription]); + [localpool drain]; + return false; + } + + // Make writeFrame() a blocking call. + while (![mMovieWriterInput isReadyForMoreMediaData]) { + fprintf(stderr, "OpenCV: AVF: waiting to write video data.\n"); + // Sleep 1 msec. + usleep(1000); + } + + BOOL success = FALSE; + + if (iplimage->height!=movieSize.height || iplimage->width!=movieSize.width){ + fprintf(stderr, "OpenCV: Frame size does not match video size.\n"); + [localpool drain]; + return false; + } + + if (movieColor) { + //assert(iplimage->nChannels == 3); + cvCvtColor(iplimage, argbimage, CV_BGR2BGRA); + }else{ + //assert(iplimage->nChannels == 1); + cvCvtColor(iplimage, argbimage, CV_GRAY2BGRA); + } + //IplImage -> CGImage conversion + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + NSData *nsData = [NSData dataWithBytes:argbimage->imageData length:argbimage->imageSize]; + CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)nsData); + CGImageRef cgImage = CGImageCreate(argbimage->width, argbimage->height, + argbimage->depth, argbimage->depth * argbimage->nChannels, argbimage->widthStep, + colorSpace, kCGImageAlphaLast|kCGBitmapByteOrderDefault, + provider, NULL, false, kCGRenderingIntentDefault); + + //CGImage -> CVPixelBufferRef coversion + CVPixelBufferRef pixelBuffer = NULL; + CFDataRef cfData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage)); + int status = CVPixelBufferCreateWithBytes(NULL, + movieSize.width, + movieSize.height, + kCVPixelFormatType_32BGRA, + (void*)CFDataGetBytePtr(cfData), + CGImageGetBytesPerRow(cgImage), + &releaseCallback, + (void *)cfData, + NULL, + &pixelBuffer); + if(status == kCVReturnSuccess){ + success = [mMovieWriterAdaptor appendPixelBuffer:pixelBuffer + withPresentationTime:CMTimeMake(mFrameNum, mMovieFPS)]; + } + + //cleanup + CVPixelBufferRelease(pixelBuffer); + CGImageRelease(cgImage); + CGDataProviderRelease(provider); + CGColorSpaceRelease(colorSpace); + + [localpool drain]; + + if (success) { + mFrameNum ++; + //NSLog(@"Frame #%d", mFrameNum); + return true; + }else{ + NSLog(@"Frame appendPixelBuffer failed."); + return false; + } + +} From c314178e7aa06fd4ed84b6521d592ea12e1d6851 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 19 Sep 2016 19:51:10 +0300 Subject: [PATCH 044/409] highgui/osx: backport AVFoundation support --- CMakeLists.txt | 20 +++++++------ cmake/OpenCVFindLibsVideo.cmake | 23 +++++++-------- modules/highgui/CMakeLists.txt | 9 ++++-- modules/highgui/src/cap_avfoundation_mac.mm | 32 +++++++++++++++++++++ 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c52ab5c458..007b80d4da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ endif() # Optional 3rd party components # =================================================== OCV_OPTION(WITH_1394 "Include IEEE1394 support" ON IF (NOT ANDROID AND NOT IOS) ) -OCV_OPTION(WITH_AVFOUNDATION "Use AVFoundation for Video I/O" ON IF IOS) +OCV_OPTION(WITH_AVFOUNDATION "Use AVFoundation for Video I/O (iOS/Mac)" ON IF APPLE) OCV_OPTION(WITH_CARBON "Use Carbon for UI instead of Cocoa" OFF IF APPLE ) OCV_OPTION(WITH_CUDA "Include NVidia Cuda Runtime support" ON IF (CMAKE_VERSION VERSION_GREATER "2.8" AND NOT IOS) ) OCV_OPTION(WITH_VTK "Include VTK library support (and build opencv_viz module eiher)" OFF IF (NOT ANDROID AND NOT IOS AND NOT CMAKE_CROSSCOMPILING) ) @@ -162,7 +162,8 @@ OCV_OPTION(WITH_PVAPI "Include Prosilica GigE support" ON OCV_OPTION(WITH_GIGEAPI "Include Smartek GigE support" ON IF (NOT ANDROID AND NOT IOS) ) OCV_OPTION(WITH_QT "Build with Qt Backend support" OFF IF (NOT ANDROID AND NOT IOS) ) OCV_OPTION(WITH_WIN32UI "Build with Win32 UI Backend support" ON IF WIN32 ) -OCV_OPTION(WITH_QUICKTIME "Use QuickTime for Video I/O insted of QTKit" OFF IF APPLE ) +OCV_OPTION(WITH_QUICKTIME "Use QuickTime for Video I/O" OFF IF APPLE ) +OCV_OPTION(WITH_QTKIT "Use QTKit Video I/O backend" OFF IF APPLE ) OCV_OPTION(WITH_TBB "Include Intel TBB support" OFF IF (NOT IOS) ) OCV_OPTION(WITH_OPENMP "Include OpenMP support" OFF) OCV_OPTION(WITH_CSTRIPES "Include C= support" OFF IF WIN32 ) @@ -879,10 +880,6 @@ if(ANDROID) endif() endif() -if(DEFINED WITH_AVFOUNDATION) - status(" AVFoundation:" WITH_AVFOUNDATION THEN YES ELSE NO) -endif(DEFINED WITH_AVFOUNDATION) - if(DEFINED WITH_FFMPEG) if(WIN32) status(" FFMPEG:" WITH_FFMPEG THEN "YES (prebuilt binaries)" ELSE NO) @@ -923,10 +920,15 @@ if(DEFINED WITH_GIGEAPI) status(" GigEVisionSDK:" HAVE_GIGE_API THEN YES ELSE NO) endif(DEFINED WITH_GIGEAPI) -if(DEFINED WITH_QUICKTIME) +if(DEFINED APPLE) + status(" AVFoundation:" HAVE_AVFOUNDATION THEN YES ELSE NO) + if(WITH_QUICKTIME OR HAVE_QUICKTIME) status(" QuickTime:" HAVE_QUICKTIME THEN YES ELSE NO) - status(" QTKit:" HAVE_QTKIT THEN YES ELSE NO) -endif(DEFINED WITH_QUICKTIME) + endif() + if(WITH_QTKIT OR HAVE_QTKIT) + status(" QTKit:" HAVE_QTKIT THEN "YES (deprecated)" ELSE NO) + endif() +endif(DEFINED APPLE) if(DEFINED WITH_UNICAP) status(" UniCap:" HAVE_UNICAP THEN "YES (ver ${ALIASOF_libunicap_VERSION})" ELSE NO) diff --git a/cmake/OpenCVFindLibsVideo.cmake b/cmake/OpenCVFindLibsVideo.cmake index 514aa5cd3d..5430759f39 100644 --- a/cmake/OpenCVFindLibsVideo.cmake +++ b/cmake/OpenCVFindLibsVideo.cmake @@ -273,19 +273,18 @@ if(WIN32) endif() endif(WIN32) -# --- Apple AV Foundation --- -if(WITH_AVFOUNDATION) - set(HAVE_AVFOUNDATION YES) -endif() - -# --- QuickTime --- -if (NOT IOS) - if(WITH_QUICKTIME) - set(HAVE_QUICKTIME YES) - elseif(APPLE) - set(HAVE_QTKIT YES) +if(APPLE) + if(WITH_AVFOUNDATION) + set(HAVE_AVFOUNDATION YES) endif() -endif() + if(NOT IOS) + if(WITH_QUICKTIME) + set(HAVE_QUICKTIME YES) + elseif(WITH_QTKIT) + set(HAVE_QTKIT YES) + endif() + endif() +endif(APPLE) # --- Intel Perceptual Computing SDK --- if(WITH_INTELPERC) diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index 44cf98c613..789232141e 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -215,8 +215,13 @@ if(HAVE_IMAGEIO AND IOS) endif() if(HAVE_AVFOUNDATION) - list(APPEND highgui_srcs src/cap_avfoundation.mm) - list(APPEND HIGHGUI_LIBRARIES "-framework AVFoundation" "-framework QuartzCore") + if(IOS) + list(APPEND highgui_srcs ${CMAKE_CURRENT_LIST_DIR}/src/cap_avfoundation.mm) + list(APPEND HIGHGUI_LIBRARIES "-framework AVFoundation" "-framework QuartzCore") + else() + list(APPEND highgui_srcs ${CMAKE_CURRENT_LIST_DIR}/src/cap_avfoundation_mac.mm) + list(APPEND HIGHGUI_LIBRARIES "-framework Cocoa" "-framework Accelerate" "-framework AVFoundation" "-framework CoreGraphics" "-framework CoreImage" "-framework CoreMedia" "-framework CoreVideo" "-framework QuartzCore") + endif() endif() if(HAVE_QUICKTIME) diff --git a/modules/highgui/src/cap_avfoundation_mac.mm b/modules/highgui/src/cap_avfoundation_mac.mm index 8c30506400..a7b889522f 100644 --- a/modules/highgui/src/cap_avfoundation_mac.mm +++ b/modules/highgui/src/cap_avfoundation_mac.mm @@ -155,7 +155,9 @@ private: uint8_t *mOutImagedata; IplImage *mOutImage; size_t currSize; +/* int mMode; +*/ int mFormat; bool setupReadingAt(CMTime position); @@ -677,7 +679,9 @@ CvCaptureFile::CvCaptureFile(const char* filename) { mOutImage = NULL; mOutImagedata = NULL; currSize = 0; +/* mMode = CV_CAP_MODE_BGR; +*/ mFormat = CV_8UC3; mCurrentSampleBuffer = NULL; mGrabbedPixels = NULL; @@ -741,12 +745,15 @@ bool CvCaptureFile::setupReadingAt(CMTime position) { // Capture in a pixel format that can be converted efficiently to the output mode. OSType pixelFormat; +/* if (mMode == CV_CAP_MODE_BGR || mMode == CV_CAP_MODE_RGB) { +*/ // For CV_CAP_MODE_BGR, read frames as BGRA (AV Foundation's YUV->RGB conversion is slightly faster than OpenCV's CV_YUV2BGR_YV12) // kCVPixelFormatType_32ABGR is reportedly faster on OS X, but OpenCV doesn't have a CV_ABGR2BGR conversion. // kCVPixelFormatType_24RGB is significanly slower than kCVPixelFormatType_32BGRA. pixelFormat = kCVPixelFormatType_32BGRA; mFormat = CV_8UC3; +/* } else if (mMode == CV_CAP_MODE_GRAY) { // For CV_CAP_MODE_GRAY, read frames as 420v (faster than 420f or 422 -- at least for H.264 files) pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; @@ -759,6 +766,7 @@ bool CvCaptureFile::setupReadingAt(CMTime position) { fprintf(stderr, "VIDEOIO ERROR: AVF Mac: Unsupported mode: %d\n", mMode); return false; } +*/ NSDictionary *settings = @{ @@ -848,8 +856,11 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { // Output image paramaters. int outChannels; +/* if (mMode == CV_CAP_MODE_BGR || mMode == CV_CAP_MODE_RGB) { +*/ outChannels = 3; +/* } else if (mMode == CV_CAP_MODE_GRAY) { outChannels = 1; } else if (mMode == CV_CAP_MODE_YUYV) { @@ -861,6 +872,7 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { mGrabbedPixels = NULL; return 0; } +*/ if ( currSize != width*outChannels*height ) { currSize = width*outChannels*height; @@ -888,8 +900,11 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { if ( pixelFormat == kCVPixelFormatType_32BGRA ) { deviceChannels = 4; +/* if (mMode == CV_CAP_MODE_BGR) { +*/ cvtCode = CV_BGRA2BGR; +/* } else if (mMode == CV_CAP_MODE_RGB) { cvtCode = CV_BGRA2RGB; } else if (mMode == CV_CAP_MODE_GRAY) { @@ -901,11 +916,15 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { fprintf(stderr, "OpenCV: unsupported pixel conversion mode\n"); return 0; } +*/ } else if ( pixelFormat == kCVPixelFormatType_24RGB ) { deviceChannels = 3; +/* if (mMode == CV_CAP_MODE_BGR) { +*/ cvtCode = CV_RGB2BGR; +/* } else if (mMode == CV_CAP_MODE_RGB) { cvtCode = 0; } else if (mMode == CV_CAP_MODE_GRAY) { @@ -917,11 +936,15 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { fprintf(stderr, "OpenCV: unsupported pixel conversion mode\n"); return 0; } +*/ } else if ( pixelFormat == kCVPixelFormatType_422YpCbCr8 ) { // 422 (2vuy, UYVY) deviceChannels = 2; +/* if (mMode == CV_CAP_MODE_BGR) { +*/ cvtCode = CV_YUV2BGR_UYVY; +/* } else if (mMode == CV_CAP_MODE_RGB) { cvtCode = CV_YUV2RGB_UYVY; } else if (mMode == CV_CAP_MODE_GRAY) { @@ -935,6 +958,7 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { fprintf(stderr, "OpenCV: unsupported pixel conversion mode\n"); return 0; } +*/ } else if ( pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange || // 420v pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ) { // 420f // cvCvtColor(CV_YUV2GRAY_420) is expecting a single buffer with both the Y plane and the CrCb planes. @@ -942,8 +966,11 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { height = height * 3 / 2; deviceChannels = 1; +/* if (mMode == CV_CAP_MODE_BGR) { +*/ cvtCode = CV_YUV2BGR_YV12; +/* } else if (mMode == CV_CAP_MODE_RGB) { cvtCode = CV_YUV2RGB_YV12; } else if (mMode == CV_CAP_MODE_GRAY) { @@ -955,6 +982,7 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { fprintf(stderr, "OpenCV: unsupported pixel conversion mode\n"); return 0; } +*/ } else { fprintf(stderr, "OpenCV: unsupported pixel format 0x%08X\n", pixelFormat); CVPixelBufferUnlockBaseAddress(mGrabbedPixels, 0); @@ -1020,8 +1048,10 @@ double CvCaptureFile::getProperty(int property_id) const{ return round((t.value * mAssetTrack.nominalFrameRate) / double(t.timescale)); case CV_CAP_PROP_FORMAT: return mFormat; +/* case CV_CAP_PROP_MODE: return mMode; +*/ default: break; } @@ -1054,6 +1084,7 @@ bool CvCaptureFile::setProperty(int property_id, double value) { setupReadingAt(t); retval = true; break; +/* case CV_CAP_PROP_MODE: int mode; mode = cvRound(value); @@ -1076,6 +1107,7 @@ bool CvCaptureFile::setProperty(int property_id, double value) { } } break; +*/ default: break; } From f1009e2234712546907ffa4b15f6bb6fc629de74 Mon Sep 17 00:00:00 2001 From: catree Date: Fri, 30 Sep 2016 23:56:35 +0200 Subject: [PATCH 045/409] Fix dead link for hit or miss 2.4 documentation. --- modules/imgproc/doc/filtering.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/doc/filtering.rst b/modules/imgproc/doc/filtering.rst index 0a78c365ff..b796b67415 100755 --- a/modules/imgproc/doc/filtering.rst +++ b/modules/imgproc/doc/filtering.rst @@ -1271,7 +1271,7 @@ Morphological gradient: \texttt{dst} = \mathrm{blackhat} ( \texttt{src} , \texttt{element} )= \mathrm{close} ( \texttt{src} , \texttt{element} )- \texttt{src} -"Hit and Miss": Only supported for CV_8UC1 binary images. Tutorial can be found in this page: http://opencv-code.com/tutorials/hit-or-miss-transform-in-opencv/ +"Hit and Miss": Only supported for CV_8UC1 binary images. Tutorial can be found in this page: https://web.archive.org/web/20160316070407/http://opencv-code.com/tutorials/hit-or-miss-transform-in-opencv/ Any of the operations can be done in-place. In case of multi-channel images, each channel is processed independently. From 26a32dcbf60ffd05fb7262bb4d211bef9f18f4b1 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sun, 2 Oct 2016 03:04:37 +0300 Subject: [PATCH 046/409] videoio: remove 'const' from getProperty 'const' is available in OpenCV 3.x only --- modules/highgui/src/cap_avfoundation_mac.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/highgui/src/cap_avfoundation_mac.mm b/modules/highgui/src/cap_avfoundation_mac.mm index a7b889522f..5b24263cfe 100644 --- a/modules/highgui/src/cap_avfoundation_mac.mm +++ b/modules/highgui/src/cap_avfoundation_mac.mm @@ -95,7 +95,7 @@ public: ~CvCaptureCAM(); virtual bool grabFrame(); virtual IplImage* retrieveFrame(int); - virtual double getProperty(int property_id) const; + virtual double getProperty(int property_id); virtual bool setProperty(int property_id, double value); virtual int didStart(); @@ -138,7 +138,7 @@ public: ~CvCaptureFile(); virtual bool grabFrame(); virtual IplImage* retrieveFrame(int); - virtual double getProperty(int property_id) const; + virtual double getProperty(int property_id); virtual bool setProperty(int property_id, double value); virtual int didStart(); @@ -411,7 +411,7 @@ void CvCaptureCAM::setWidthHeight() { } -double CvCaptureCAM::getProperty(int property_id) const{ +double CvCaptureCAM::getProperty(int property_id) { NSAutoreleasePool *localpool = [[NSAutoreleasePool alloc] init]; CMFormatDescriptionRef format = mCaptureDevice.activeFormat.formatDescription; @@ -1024,7 +1024,7 @@ IplImage* CvCaptureFile::retrieveFrame(int) { return retrieveFramePixelBuffer(); } -double CvCaptureFile::getProperty(int property_id) const{ +double CvCaptureFile::getProperty(int property_id) { if (mAsset == nil) return 0; CMTime t; From c181f2f4937186306dc6f1a72fc3cebd8685f37a Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 4 Oct 2016 13:30:43 +0300 Subject: [PATCH 047/409] cmake: fix for GCC 6.x --- cmake/OpenCVPCHSupport.cmake | 7 +------ cmake/OpenCVUtils.cmake | 3 +++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cmake/OpenCVPCHSupport.cmake b/cmake/OpenCVPCHSupport.cmake index e6585cae02..7d7aacf00f 100644 --- a/cmake/OpenCVPCHSupport.cmake +++ b/cmake/OpenCVPCHSupport.cmake @@ -14,12 +14,7 @@ IF(CMAKE_COMPILER_IS_GNUCXX) - EXEC_PROGRAM( - ${CMAKE_CXX_COMPILER} - ARGS ${CMAKE_CXX_COMPILER_ARG1} -dumpversion - OUTPUT_VARIABLE gcc_compiler_version) - #MESSAGE("GCC Version: ${gcc_compiler_version}") - IF(gcc_compiler_version VERSION_GREATER "4.2.-1" AND gcc_compiler_version VERSION_LESS "6.0.0") + IF(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.2.0") SET(PCHSupport_FOUND TRUE) ENDIF() diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 0692b763d5..0241c43c1c 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -41,6 +41,9 @@ function(ocv_include_directories) get_filename_component(__abs_dir "${dir}" ABSOLUTE) if("${__abs_dir}" MATCHES "^${OpenCV_SOURCE_DIR}" OR "${__abs_dir}" MATCHES "^${OpenCV_BINARY_DIR}") list(APPEND __add_before "${dir}") + elseif(CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "6.0" AND + dir MATCHES "/usr/include$") + # workaround for GCC 6.x bug else() include_directories(AFTER SYSTEM "${dir}") endif() From 80f03f58e71bafbae79644f086a53f3d291b4e55 Mon Sep 17 00:00:00 2001 From: StevenPuttemans Date: Thu, 6 Oct 2016 13:42:12 +0200 Subject: [PATCH 048/409] backport of 7361 --- .../introduction/windows_install/windows_install.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/tutorials/introduction/windows_install/windows_install.rst b/doc/tutorials/introduction/windows_install/windows_install.rst index 3fda01c086..a02a07d969 100644 --- a/doc/tutorials/introduction/windows_install/windows_install.rst +++ b/doc/tutorials/introduction/windows_install/windows_install.rst @@ -70,8 +70,12 @@ Building the OpenCV library from scratch requires a couple of tools installed be .. _CUDA_Toolkit: http://developer.nvidia.com/cuda-downloads .. |OpenEXR| replace:: OpenEXR .. _OpenEXR: http://www.openexr.com/downloads.html -.. |OpenNI_Framework| replace:: OpenNI Framework -.. _OpenNI_Framework: http://www.openni.org/ +.. |OpenNI_prebuilt| replace:: OpenNI prebuilt binaries +.. _OpenNI_prebuilt: http://structure.io/openni +.. |OpenNI_github| replace:: OpenNI Github +.. _OpenNI_github: https://github.com/OpenNI/OpenNI +.. |OpenNI2_github| replace:: OpenNI2 Github +.. _OpenNI2_github: https://github.com/OpenNI/OpenNI2 .. |Miktex| replace:: Miktex .. _Miktex: http://miktex.org/2.9/setup .. |Sphinx| replace:: Sphinx @@ -105,7 +109,7 @@ OpenCV may come in multiple flavors. There is a "core" section that will work on + |OpenEXR|_ source files are required for the library to work with this high dynamic range (HDR) image file format. - + The |OpenNI_Framework|_ contains a set of open source APIs that provide support for natural interaction with devices via methods such as voice command recognition, hand gestures and body motion tracking. + + The OpenNI Framework contains a set of open source APIs that provide support for natural interaction with devices via methods such as voice command recognition, hand gestures and body motion tracking. |OpenNI_prebuilt|_ , |OpenNI_github|_ code and |OpenNI2_github|_ code is available. + |Miktex|_ is the best `TEX `_ implementation on the Windows OS. It is required to build the *OpenCV documentation*. @@ -172,7 +176,7 @@ Building the library #) Same as above with |OpenEXR|_. - #) For the |OpenNI_Framework|_ you need to install both the `development build `_ and the `PrimeSensor Module `_. + #) For the |OpenNI_prebuilt|_ you need to install both the `development build `_ and the `PrimeSensor Module `_. #) For the CUDA you need again two modules: the latest |CUDA_Toolkit|_ and the *CUDA Tools SDK*. Download and install both of them with a *complete* option by using the 32 or 64 bit setups according to your OS. From 4e7f28811c1503e431a91d790d4998e63e80753d Mon Sep 17 00:00:00 2001 From: Juha Reunanen Date: Wed, 12 Oct 2016 10:37:07 +0300 Subject: [PATCH 049/409] Merge pull request #7452 from reunanen:issue-7409-2.4 Fix findContours crash for very large images (v2.4) * Cast step to size_t in order to avoid integer overflow when processing very large images * Change assert to CV_Assert --- modules/imgproc/src/contours.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/imgproc/src/contours.cpp b/modules/imgproc/src/contours.cpp index 304214257c..8d1ec9b32a 100644 --- a/modules/imgproc/src/contours.cpp +++ b/modules/imgproc/src/contours.cpp @@ -284,10 +284,13 @@ cvStartFindContours( void* _img, CvMemStorage* storage, scanner->cinfo_storage ); } + CV_Assert(step >= 0); + CV_Assert(size.height >= 1); + /* make zero borders */ int esz = CV_ELEM_SIZE(mat->type); memset( img, 0, size.width*esz ); - memset( img + step * (size.height - 1), 0, size.width*esz ); + memset( img + static_cast(step) * (size.height - 1), 0, size.width*esz ); img += step; for( int y = 1; y < size.height - 1; y++, img += step ) @@ -989,6 +992,8 @@ cvFindNextContour( CvContourScanner scanner ) { if( !scanner ) CV_Error( CV_StsNullPtr, "" ); + CV_Assert(scanner->img_step >= 0); + icvEndProcessContour( scanner ); /* initialize local state */ @@ -1063,7 +1068,7 @@ cvFindNextContour( CvContourScanner scanner ) is_hole = 1; } - if( mode == 0 && (is_hole || img0[lnbd.y * step + lnbd.x] > 0) ) + if( mode == 0 && (is_hole || img0[lnbd.y * static_cast(step) + lnbd.x] > 0) ) goto resume_scan; origin.y = y; @@ -1077,8 +1082,8 @@ cvFindNextContour( CvContourScanner scanner ) else { int lval = (img0_i ? - img0_i[lnbd.y * step_i + lnbd.x] : - (int)img0[lnbd.y * step + lnbd.x]) & 0x7f; + img0_i[lnbd.y * static_cast(step_i) + lnbd.x] : + (int)img0[lnbd.y * static_cast(step) + lnbd.x]) & 0x7f; _CvContourInfo *cur = scanner->cinfo_table[lval]; /* find the first bounding contour */ @@ -1090,11 +1095,11 @@ cvFindNextContour( CvContourScanner scanner ) if( par_info ) { if( (img0_i && - icvTraceContour_32s( img0_i + par_info->origin.y * step_i + + icvTraceContour_32s( img0_i + par_info->origin.y * static_cast(step_i) + par_info->origin.x, step_i, img_i + lnbd.x, par_info->is_hole ) > 0) || (!img0_i && - icvTraceContour( img0 + par_info->origin.y * step + + icvTraceContour( img0 + par_info->origin.y * static_cast(step) + par_info->origin.x, step, img + lnbd.x, par_info->is_hole ) > 0) ) break; From 316fece020fe7be89a1d5edf9877ff213cab25b4 Mon Sep 17 00:00:00 2001 From: Feng Zhoutian Date: Sun, 16 Oct 2016 02:22:28 +0800 Subject: [PATCH 050/409] fix the problem: the headers of cudev module are not installed if BUILD_opencv_world --- modules/cudev/CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/cudev/CMakeLists.txt b/modules/cudev/CMakeLists.txt index f9f1d0bb98..e27186c57d 100644 --- a/modules/cudev/CMakeLists.txt +++ b/modules/cudev/CMakeLists.txt @@ -10,10 +10,13 @@ ocv_add_module(cudev) ocv_module_include_directories(opencv_core opencv_hal) -file(GLOB_RECURSE lib_hdrs "include/opencv2/*.hpp") -file(GLOB lib_srcs "src/*.cpp") +file(GLOB_RECURSE lib_hdrs "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.hpp") +file(GLOB lib_srcs "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp") -ocv_set_module_sources(HEADERS ${lib_hdrs} SOURCES ${lib_srcs}) +source_group("Include" FILES ${lib_hdrs}) +source_group("Src" FILES ${lib_srcs}) + +ocv_glob_module_sources(HEADERS ${lib_hdrs} SOURCES ${lib_srcs}) ocv_create_module() From c419552002486ef45ba403afc6eeb145fc081275 Mon Sep 17 00:00:00 2001 From: catree Date: Mon, 17 Oct 2016 00:01:08 +0200 Subject: [PATCH 051/409] Add images for tutorials on morphological operations to avoid possible confusion. --- .../erosion_dilatation/erosion_dilatation.rst | 15 +++++++++++++++ ...Morphology_1_Tutorial_Theory_Dilatation_2.png | Bin 0 -> 1558 bytes .../Morphology_1_Tutorial_Theory_Erosion_2.png | Bin 0 -> 1533 bytes .../Morphology_2_Tutorial_Theory_Closing_2.png | Bin 0 -> 1383 bytes .../Morphology_2_Tutorial_Theory_Opening_2.png | Bin 0 -> 1341 bytes .../opening_closing_hats.rst | 13 ++++++++++++- 6 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 doc/tutorials/imgproc/erosion_dilatation/images/Morphology_1_Tutorial_Theory_Dilatation_2.png create mode 100644 doc/tutorials/imgproc/erosion_dilatation/images/Morphology_1_Tutorial_Theory_Erosion_2.png create mode 100644 doc/tutorials/imgproc/opening_closing_hats/images/Morphology_2_Tutorial_Theory_Closing_2.png create mode 100644 doc/tutorials/imgproc/opening_closing_hats/images/Morphology_2_Tutorial_Theory_Opening_2.png diff --git a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.rst b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.rst index 5ba21f622e..4d7eedbf73 100644 --- a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.rst +++ b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.rst @@ -53,6 +53,14 @@ Dilation The background (bright) dilates around the black regions of the letter. +To better grasp the idea and avoid possible confusion, in this another example we have inverted the original image such as the object in white is now the letter. We have performed two dilatations with a rectangular structuring element of size :math:`3x3`. + + .. image:: images/Morphology_1_Tutorial_Theory_Dilatation_2.png + :alt: Left image: original image inverted, right image: resulting dilatation + :align: center + +The dilatation makes the object in white bigger. + Erosion ^^^^^^^^ @@ -66,6 +74,13 @@ Erosion :alt: Erosion result - Theory example :align: center +In the same manner, the corresponding image resulting of the erosion operation on the inverted original image (two erosions with a rectangular structuring element of size :math:`3x3`. + + .. image:: images/Morphology_1_Tutorial_Theory_Erosion_2.png + :alt: Left image: original image inverted, right image: resulting erosion + :align: center + +The erosion makes the object in white smaller. Code ====== diff --git a/doc/tutorials/imgproc/erosion_dilatation/images/Morphology_1_Tutorial_Theory_Dilatation_2.png b/doc/tutorials/imgproc/erosion_dilatation/images/Morphology_1_Tutorial_Theory_Dilatation_2.png new file mode 100644 index 0000000000000000000000000000000000000000..bdca7c62339475cb30ee30ed690759dd1e54835c GIT binary patch literal 1558 zcmV+x2I={UP)WFU8GbZ8()Nlj2>E@cM*00nwUL_t(|+U?!Ta@#r( zMNw0Q|NmuWlu1%DC6NI7vG&;4(ZxC3Xg*9GKR-XuAEf2^tR3HR970aa!x8`>7ytwV z00aYoU;yN0SOOq7XLk~J-iad^ppZ;n{WrTLLogS@^^69h%VoD@2k11|Dj z+$pL*NsIi`3&8-GE|(pWA{Y}18uf8?zX|4s#U~|zV5UlfWB`HzKrjFZ1^@_VJ|4IO z2nGPb0H`o&6O2PH#%O{8g7eO_5_QTp!2rqS^2yJcV1k9|`U^7kH(Fm!Fef{J zH`O8f^b#3@X%aL*Fa!dW>LkGcFwz1JD>H{=W-1Oz-1A>`nh3&Vt-cC6?S#uj$_9J6I_I@AKCOWf5!ILU?NNi zB!a5)dsr-=5%Qk(*;-F9JEs?7L8PwE>SI~KblWMQlMz3Y=U{>d>Mjy8zZ+?EJ%}^h z@*@{ypQKDMLBk{$J;?MqJjuJ^;3XGC>gsB_l$>C)?-b->ddc+fpY_!#QjkE21$TF1 zw%FeJe52ceJ@Y^|wIIUuvf|%-x3eag&4H2&DvG6?V0KI|#Dd)ia`f+mH^C4K5>6DW zPrbhFZ5zQvxL`&u1uLmGr^Dx$Yf7vhj2*+{iL$0w?n9;f<97N-QOWpVnGkF z?C9=Wxg6wjs z)ZvjK%)bln8xc`SE*&<*5jD(z7eC^csexF?WuaiM21*d9LHhPTOpVzUob{4SRofT8x{&?m0X00N~bg3Hp8z3^XWnV?V`GKgk2la zj5qst?CK-i4gl=xtZ90|1oO!vUy+M2vAQcWUk=}Oqn+|3@2W33GabRCUFZ~P#WfLf zK{UCTrh`l{4ti9ZT6|^j!U-nIxipa>%R%40t6>B~E|}SyZ#v}EsMrKUF32n{eZ3%< zvjulc8dc;$E&(T)Z$lv$m|O^EN;s{6frW)k9n#kYg1KX%kK+&)QXTmE`aa{%1@{-xh$5L;57FuF2EkZ-RF{ly z2M`Pp@pLK5w~rDGP*^lr&%+V`AQ%7y0{{dAfM5U+3;_JcA3*B%y~mug)Bpeg07*qo IM6N<$g5j*1WB>pF literal 0 HcmV?d00001 diff --git a/doc/tutorials/imgproc/erosion_dilatation/images/Morphology_1_Tutorial_Theory_Erosion_2.png b/doc/tutorials/imgproc/erosion_dilatation/images/Morphology_1_Tutorial_Theory_Erosion_2.png new file mode 100644 index 0000000000000000000000000000000000000000..5e666eef3614ce093e5dd178bd4d07284fcf3d90 GIT binary patch literal 1533 zcmVWFU8GbZ8()Nlj2>E@cM*00m)5L_t(|+U?!jZrd;r zMbSv$|NpZ46rfGxM3#A(8Ls_U1gXI}-aM2P`}p|yczhr&k4NqC9miqFi8(C+0D=KP zFaSU>00;&E!2kfk0GLO!1lRfc%!y|-0D|cvlV?9eGJqpece&j8SwMpUkbX0a(yTQY zjRXa=8YRaB(_tv21Q5(nNstUQZGr)|kxK*#5e%TY5Db8P5KJdLa0d_!U?U?K02O3J zFpfBoXM!)FyJ3D+~M3-FysPxC72My@+=I6Kyk5yYB9K`^uAVwi}~v`6Q#O4Dwc7K2={I=|rhTp&+HoA@0%$Nbb_Um9pm8N_FaSU>g?9?npxQ@kp1TbV z0KsT40Kovzw4c)qE%s~GuC!_m2E#PyySur`N$xavVw^+G;%?vxsQ5^+z}~=T!;nL?s7i+KWF5z z8QPrRO)i+7iIrk0BbX6#>HRy&1rgp4D{{ES5$UIe9oc?}1yP#zsw}o$1~&8TODu@c zv?JuO87{e?PKKl9uT@>>C`T}0M$?X(!B%B3q=Fqc%jh|56&IaYP&LQd^4BU4gIGW& z8wcZRyJOB;mf;WyGPjjov)3{V$1o95D$jD}d#@%4X4N8-3r6Pl zPQG@|WLq($y92T_V1-z0T7d*J>f;R}ewiAG1rr71Dm${vrFRB*&vY9#sGdV`G{R`P z^b$daFS1W1m?(doWtdnk7jza(lrI<)5JV7*_VPbxbh$VlL2KEp!MysWzdKBKjzBIO zACVkikV!7HzBX9q-#8+@(q7(KXh$QEiv}~KwPgMLQN%<}k~EkZ*F?w#y<0_vmbA?g z$(tS^mnDLkwd)urR<{>&NhTO_!3gAqV1xx4((Zy8`8AaWhOBb2=}l@dcO^kCaQq^e zA+nhD&>qk`-}1OXFyw-0&7xOBCYQm2xw@#o4fOGNkPA{thFtE`ULD$%VU7A+Ucj-a z_z`K=A{!=FwwSL?TOKDgm=(bQ-p$gdD58A4s|K@h!3`6UG9`}-1T$*k4HHo$a~y{} zu0A4Nz359Wh$NY3nU%p{g6Sa{Lq(<|6DAAh%FbZG0KwQ90CeTF1ONyI0KotN!2lo_ j00aX71OtFz02JU4O40(pVDRtD00000NkvXXu0mjfQaX&Y literal 0 HcmV?d00001 diff --git a/doc/tutorials/imgproc/opening_closing_hats/images/Morphology_2_Tutorial_Theory_Closing_2.png b/doc/tutorials/imgproc/opening_closing_hats/images/Morphology_2_Tutorial_Theory_Closing_2.png new file mode 100644 index 0000000000000000000000000000000000000000..57b790583f42933202d593d58ebb49521bd529fe GIT binary patch literal 1383 zcmYjRX;4#F6nNC|z~7weDCJ@?*o@7caHbIv&tyDi+- z#@z-009!!>Z#w{3xuSdxXpNru&@<)eWt|ii&I8_Cmb$aC3jHBwMeNB2020Nrt$;d} z#fBggMDq#bHbiH)4Gno6s0>(^zGJs6^gu>NYNiZ*0zjxVHBpwDLeEK;eNGP-M8~GE zQV9Tndnn)q?~woYYHTdCf$hXkJo(SN8!FAxQ{QbH_Ijd%4vSxffw{uFj#eFCS(s*5akC6Lv$wPMvUt~D@lJEU;ze`FtFZ5Dj+qy+%&wfnX?sI8U7cMnLM_SS;SQE?YG z5;fim7Kqd^SN}CTCS>7-@2>_-RVgt|5Np6l{j}9&a_>kSg$#T!Ho80(znkcYIVs8u zHYn=EmBmwnLv$C&ffdBYVPt=LTQExq7XNl=ycI4eR|pPvvi}Pn7Nd^h#!Og!K-M#A zH8dFiQAZa(f~xp+262hjLEe$19uo%fa-GN*ftU8nS;i19ak9zSi)EsrJWN$$X_0@A z+&s&qm6No0R=N}c8vFhX6fOO>sKqopYxmm@{-WT$ z_SWJ8a|`L%B`|V<7Zj0){`a&I8dvDsZ}Ya zjnJ!at8$9iJ@y`5ks;iIX!g(dvXG(_6Xy>c7QiLnutgKqdvA)e6ru5gMp4+0e6>~yQZOgfUvQ&H%zOD!tV_^?JpByKcveD43NBXE zoE0;c-!(uiL{{N1mv7~VOezMhm$oh5oy*LS%}hDv^dGDe#h!UY-a1Waxq>&RL&Q(sR+3iFXCMAdchxQ}Nn3V3<#i+R_--}) z&^#K(hFQ;?C3I=9ftb$cHg=EAcJy^?Uo^v?%3tPrYs#Y@D{ekvAZ^Ed23a~NVt8N~ zqDttxOr`ZQLb;tTCqzRfV~EgS%M0`=w=r?fiy7Rx(j97;h^HV2D|I2O&9y?d+qt-? zHQ!!<`*6uRT=U55DJS>ZAnq`U8Ir}rAZwV)&5)<#wB(@n*`YAH%d~_xK%O||SAxBG zGnA>Uzqh!$x@7HDj7GU{gU#fy5yYS+G|HlO1I_E5h1I(bDHY#eg`Xd37qH`~b!Z#- z1vC^(?(owzPZ$~Co3Zg+X<;wo)l@_|i4Vh@I7;!pfX%s%=b9Yi=Z}k^%!+uz{L{)+ zAS1@tyzo1&RbK~c#Hl$Z&fnC&3z%DccE{U6p7qC4X4-WXD5%c;oPxy&cQ+;(5pL4n zSqd44c8YMV8_E9s^WTU}*#+W4f>gT~T6o3SN}Bvj4%(e*fP=FG%JdhH{xq=Vic#b# zZ^xQzb4v|JXqUab5Hb^O6rCyiia1bBNWiP%dZz0ji|^kzJvHh3=roBH$fH7v=X9kH z_4${H4tv&_7u;qa*dNt9Rjh1)1$a+a+0%82!&Kt`hYk*VObnoe8@IdhZm52Li*~(b N-U;~Ic-KS3(7%}ZOS}L8 literal 0 HcmV?d00001 diff --git a/doc/tutorials/imgproc/opening_closing_hats/images/Morphology_2_Tutorial_Theory_Opening_2.png b/doc/tutorials/imgproc/opening_closing_hats/images/Morphology_2_Tutorial_Theory_Opening_2.png new file mode 100644 index 0000000000000000000000000000000000000000..973e13a2d24c11fbbceda8d89676243bc17d3b3c GIT binary patch literal 1341 zcmYL}2~d+q6oCJPB!nXea)qippvsVM=45+N8s48@}bW5G}<{nP18XJ_Ag``#Yk?CyJKqxP{0 zmd=&{01(1BtOEdmq9Aq+9*0P5$i?%B!o`QPS-`rfUHR!oHR7>2#)&-+09Ll9jsof~ znbw$RhwTkDpSHBHcOps(%qkI=+mYy)?2rtBASpc?83I5^RuVruDUo{ONOmff9kw?r z5lk`%08C>TD=0c|;?>m1xe2BCY$q8JVqj_S7FiaC59y6%RBsIrgCkRz5P+f8le8ijK%3z!xUTdD^wUKz zlYp9xc2buz4^XPs85vT~ve%E;KlEc=+6lBdW3+htO1C=(vgSB;=z+D9pez8eh>_2-{Uk9P#E&vbHi4zlsdRoOYGAVwyddkw zxFCBGl7EqlP1G^<{r%k0=z8NG!^vo2p#V(09NE4iV}ILg>)5Qm!M)y7Mgzs=Fy5+X zBmHCI%cDGNLTyUI9ix9U!*@SdBH%E+zu(!wzSC<9q~UV>x46}JLW)$OWNw&jzXwB` zoo*SFs`Vn{oV{`kZ|I7F{F`j-IyjLe<@jdm))E2zF{$sx_98xG-q7BJ618neU|OdSqxS?~GhPZf zEkGY%5xFh>{#EDsrK_Iw_k_pTYmaAV2X!Mei?izynKl<&l}9rrZzgU1U&~8MRmj_$ zg))O2e8}7sS~)>~D;Lwz$^G#pTAW4wMci z{=tOdr0)(drLcw+ai-XIgK9orWb9K~Sk&g!spYWnc{vpnhr4?;%vHWs7xe_`Q>E%K zmx$4Em+W_pgTGF&b`8xB!`ey-b9#9<^j%s14bMSW2ZIwux;3!5cML&#l*7ot(jJf~ zwvO6QpS@XDaN1|%*GiIIRAZiN3j38In;lb7ix^%c=zm_$@&wZ>DFHrUZfJVd)sC7& zPvfEcuq)A0RWZ)$D^+o2goV0BnKG|XB!^XZ(H2hs{@zg!P@W3a3n))64D_Fs6ZBIY zu~p0GZ&#GOt^=yoVRubQc?P}e8%aoMznS5A^}AT#A%mUcZ?~5Oler6_=WAuP;_z0( z%`d{U=Doa&t23&S1Ez@%&bZyY!eN+&WAczxQ{;#=-0M|AWJj`ehQFBL!;cGL4E|-0 xUw^-@LY1-v`%<=)o(b;Ym=YhU5dW)9i{@S4J_^mbu3S?U3k%)HY7K^p{sBv$RJH&B literal 0 HcmV?d00001 diff --git a/doc/tutorials/imgproc/opening_closing_hats/opening_closing_hats.rst b/doc/tutorials/imgproc/opening_closing_hats/opening_closing_hats.rst index abe7b496fe..a09ea41b6d 100644 --- a/doc/tutorials/imgproc/opening_closing_hats/opening_closing_hats.rst +++ b/doc/tutorials/imgproc/opening_closing_hats/opening_closing_hats.rst @@ -31,7 +31,7 @@ In the previous tutorial we covered two basic Morphology operations: * Erosion * Dilation. -Based on these two we can effectuate more sophisticated transformations to our images. Here we discuss briefly 05 operations offered by OpenCV: +Based on these two we can effectuate more sophisticated transformations to our images. Here we discuss briefly 5 operations offered by OpenCV: Opening --------- @@ -50,6 +50,12 @@ Opening :alt: Opening :align: center +For the sake of clarity, we have performed the opening operation (:math:`7x7` rectangular structuring element) on the same original image but inverted such as the object in white is now the letter. + + .. image:: images/Morphology_2_Tutorial_Theory_Opening_2.png + :alt: Left image: original image inverted, right image: resulting opening + :align: center + Closing --------- @@ -65,6 +71,11 @@ Closing :alt: Closing example :align: center +On the inverted image, we have performed the closing operation (:math:`7x7` rectangular structuring element): + + .. image:: images/Morphology_2_Tutorial_Theory_Closing_2.png + :alt: Left image: original image inverted, right image: resulting closing + :align: center Morphological Gradient ------------------------ From 1c409abe9c04b0b03cbafedee07e33190fdb30ab Mon Sep 17 00:00:00 2001 From: catree Date: Mon, 17 Oct 2016 00:18:41 +0200 Subject: [PATCH 052/409] Add example image about distortion effect. --- ...camera_calibration_and_3d_reconstruction.rst | 9 ++++++++- .../calib3d/doc/pics/distortion_examples.png | Bin 0 -> 36227 bytes 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 modules/calib3d/doc/pics/distortion_examples.png diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index 29b458e6ca..9d3f1f2940 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -83,7 +83,14 @@ is extended as: :math:`k_6` are radial distortion coefficients. :math:`p_1` and :math:`p_2` are tangential distortion coefficients. -Higher-order coefficients are not considered in OpenCV. In the functions below the coefficients are passed or returned as +Higher-order coefficients are not considered in OpenCV. + +The next figure shows two common types of radial distortion: barrel distortion (typically :math:`k_1 > 0` and pincushion distortion (typically :math:`k_1 < 0`). + +.. image:: pics/distortion_examples.png + :align: center + +In the functions below the coefficients are passed or returned as .. math:: diff --git a/modules/calib3d/doc/pics/distortion_examples.png b/modules/calib3d/doc/pics/distortion_examples.png new file mode 100644 index 0000000000000000000000000000000000000000..4650d44c7096432c52f2b687c2b7018ea1cc00e1 GIT binary patch literal 36227 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 literal 0 HcmV?d00001 From 058f93d9e802dff57be0b731926a17291160654b Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Tue, 11 Oct 2016 12:13:41 +0300 Subject: [PATCH 053/409] OpenVX based implementation for integral image HAL API. --- 3rdparty/openvx/include/openvx_hal.hpp | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/include/openvx_hal.hpp index 100c14938c..ba95b802d7 100644 --- a/3rdparty/openvx/include/openvx_hal.hpp +++ b/3rdparty/openvx/include/openvx_hal.hpp @@ -100,6 +100,24 @@ struct VX_Traits }; }; +template <> +struct VX_Traits +{ + enum { + ImgType = VX_DF_IMAGE_U32, + DataType = VX_TYPE_UINT32 + }; +}; + +template <> +struct VX_Traits +{ + enum { + ImgType = VX_DF_IMAGE_S32, + DataType = VX_TYPE_INT32 + }; +}; + template <> struct VX_Traits { @@ -1018,6 +1036,32 @@ inline int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, return CV_HAL_ERROR_OK; } +inline int ovx_hal_integral(int depth, int sdepth, int , const uchar * a, size_t astep, uchar * b, size_t bstep, uchar * c, size_t , uchar * d, size_t , int w, int h, int cn) +{ + if (depth != CV_8U || sdepth != CV_32S || c != NULL || d != NULL || cn != 1) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + try + { + vxContext * ctx = vxContext::getContext(); + vxImage ia(*ctx, a, astep, w, h); + vxImage ib(*ctx, (unsigned int *)(b+bstep+sizeof(unsigned int)), bstep, w, h); + vxErr::check(vxuIntegralImage(ctx->ctx, ia.img, ib.img)); + memset(b, 0, (w+1)*sizeof(unsigned int)); + b += bstep; + for (int i = 0; i < h; i++, b += bstep) + { + *((unsigned int*)b) = 0; + } + } + catch (vxErr & e) + { + e.print(); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + //================================================================================================== // functions redefinition // ... @@ -1096,5 +1140,7 @@ inline int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, #define cv_hal_cvtBGRtoThreePlaneYUV ovx_hal_cvtBGRtoThreePlaneYUV #undef cv_hal_cvtOnePlaneYUVtoBGR #define cv_hal_cvtOnePlaneYUVtoBGR ovx_hal_cvtOnePlaneYUVtoBGR +#undef cv_hal_integral +#define cv_hal_integral ovx_hal_integral #endif From 6f00e53159f9f7295dbc26ee9c36974e42e7f268 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 20 Oct 2016 14:35:34 +0300 Subject: [PATCH 054/409] ts: update run.py Add information about python bindings --- modules/ts/misc/run_suite.py | 41 ++++++++++++++++++++++++++++++------ modules/ts/misc/run_utils.py | 24 +++++++++++++++++---- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/modules/ts/misc/run_suite.py b/modules/ts/misc/run_suite.py index 280c21caa6..bd80874475 100644 --- a/modules/ts/misc/run_suite.py +++ b/modules/ts/misc/run_suite.py @@ -85,8 +85,8 @@ class TestSuite(object): return set(res) def isTest(self, fullpath): - if fullpath == "java": - return True + if fullpath in ['java', 'python2', 'python3']: + return self.options.mode == 'test' if not os.path.isfile(fullpath): return False if self.cache.getOS() == "nt" and not fullpath.endswith(".exe"): @@ -102,6 +102,14 @@ class TestSuite(object): return res + cmd return cmd + def tryCommand(self, cmd): + try: + if 0 == execute(cmd, cwd = workingDir): + return True + except: + pass + return False + def runTest(self, path, logfile, workingDir, args = []): args = args[:] exe = os.path.abspath(path) @@ -109,6 +117,22 @@ class TestSuite(object): cmd = [self.cache.ant_executable, "-Dopencv.build.type=%s" % self.cache.build_type, "buildAndTest"] ret = execute(cmd, cwd = self.cache.java_test_binary_dir + "/.build") return None, ret + elif path in ['python2', 'python3']: + executable = os.getenv('OPENCV_PYTHON_BINARY', None) + if executable is None: + executable = path + if not self.tryCommand([executable, '--version']): + executable = 'python' + cmd = [executable, self.cache.opencv_home + '/modules/python/test/test.py', '--repo', self.cache.opencv_home, '-v'] + args + module_suffix = '' if not 'Visual Studio' in self.cache.cmake_generator else '/' + self.cache.build_type + env = {} + env['PYTHONPATH'] = self.cache.opencv_build + '/lib' + module_suffix + os.pathsep + os.getenv('PYTHONPATH', '') + if self.cache.getOS() == 'nt': + env['PATH'] = self.cache.opencv_build + '/bin' + module_suffix + os.pathsep + os.getenv('PATH', '') + else: + env['LD_LIBRARY_PATH'] = self.cache.opencv_build + '/bin' + os.pathsep + os.getenv('LD_LIBRARY_PATH', '') + ret = execute(cmd, cwd = workingDir, env = env) + return None, ret else: if isColorEnabled(args): args.append("--gtest_color=yes") @@ -140,12 +164,15 @@ class TestSuite(object): more_args = [] exe = self.getTest(test) - userlog = [a for a in args if a.startswith("--gtest_output=")] - if len(userlog) == 0: - logname = self.getLogName(exe, date) - more_args.append("--gtest_output=xml:" + logname) + if exe in ["java", "python2", "python3"]: + logname = None else: - logname = userlog[0][userlog[0].find(":")+1:] + userlog = [a for a in args if a.startswith("--gtest_output=")] + if len(userlog) == 0: + logname = self.getLogName(exe, date) + more_args.append("--gtest_output=xml:" + logname) + else: + logname = userlog[0][userlog[0].find(":")+1:] log.debug("Running the test: %s (%s) ==> %s in %s", exe, args + more_args, logname, workingDir) if self.options.dry_run: diff --git a/modules/ts/misc/run_utils.py b/modules/ts/misc/run_utils.py index 5841631a7c..8740aa7855 100644 --- a/modules/ts/misc/run_utils.py +++ b/modules/ts/misc/run_utils.py @@ -22,13 +22,17 @@ class Err(Exception): def __init__(self, msg, *args): self.msg = msg % args -def execute(cmd, silent = False, cwd = "."): +def execute(cmd, silent = False, cwd = ".", env = None): try: log.debug("Run: %s", cmd) + if env: + for k in env: + log.debug(" Environ: %s=%s", k, env[k]) + env = os.environ.update(env) if silent: - return check_output(cmd, stderr = STDOUT, cwd = cwd).decode("latin-1") + return check_output(cmd, stderr = STDOUT, cwd = cwd, env = env).decode("latin-1") else: - return check_call(cmd, cwd = cwd) + return check_call(cmd, cwd = cwd, env = env) except CalledProcessError as e: if silent: log.debug("Process returned: %d", e.returncode) @@ -171,6 +175,8 @@ parse_patterns = ( {'name': "cuda_library", 'default': None, 'pattern': re.compile(r"^CUDA_CUDA_LIBRARY:FILEPATH=(.+)$")}, {'name': "cuda_version", 'default': None, 'pattern': re.compile(r"^CUDA_VERSION:STRING=(.+)$")}, {'name': "core_dependencies", 'default': None, 'pattern': re.compile(r"^opencv_core_LIB_DEPENDS:STATIC=(.+)$")}, + {'name': "python2", 'default': None, 'pattern': re.compile(r"^BUILD_opencv_python2:BOOL=(.*)$")}, + {'name': "python3", 'default': None, 'pattern': re.compile(r"^BUILD_opencv_python3:BOOL=(.*)$")}, ) class CMakeCache: @@ -247,11 +253,15 @@ class CMakeCache: files = glob.glob(os.path.join(d, mask)) if not self.getOS() == "android" and self.withJava(): files.append("java") + if self.withPython2(): + files.append("python2") + if self.withPython3(): + files.append("python3") return [f for f in files if isGood(f)] return [] def isMainModule(self, name): - return name in self.main_modules + return name in self.main_modules + ['python2', 'python3'] def withCuda(self): return self.cuda_version and self.with_cuda == "ON" and self.cuda_library and not self.cuda_library.endswith("-NOTFOUND") @@ -259,6 +269,12 @@ class CMakeCache: def withJava(self): return self.ant_executable and self.java_test_binary_dir + def withPython2(self): + return self.python2 == 'ON' + + def withPython3(self): + return self.python3 == 'ON' + def getGitVersion(self): if self.cmake_home_vcver: if self.cmake_home_vcver == self.opencv_home_vcver: From 8577f718105cb28148e564df4341cb34b4c7ebd1 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 20 Oct 2016 16:26:19 +0300 Subject: [PATCH 055/409] ts: 2.4 changes --- modules/ts/misc/run_suite.py | 6 +++--- modules/ts/misc/run_utils.py | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/ts/misc/run_suite.py b/modules/ts/misc/run_suite.py index bd80874475..30805facdc 100644 --- a/modules/ts/misc/run_suite.py +++ b/modules/ts/misc/run_suite.py @@ -85,7 +85,7 @@ class TestSuite(object): return set(res) def isTest(self, fullpath): - if fullpath in ['java', 'python2', 'python3']: + if fullpath in ['java', 'python', 'python2', 'python3']: return self.options.mode == 'test' if not os.path.isfile(fullpath): return False @@ -117,7 +117,7 @@ class TestSuite(object): cmd = [self.cache.ant_executable, "-Dopencv.build.type=%s" % self.cache.build_type, "buildAndTest"] ret = execute(cmd, cwd = self.cache.java_test_binary_dir + "/.build") return None, ret - elif path in ['python2', 'python3']: + elif path in ['python', 'python2', 'python3']: executable = os.getenv('OPENCV_PYTHON_BINARY', None) if executable is None: executable = path @@ -164,7 +164,7 @@ class TestSuite(object): more_args = [] exe = self.getTest(test) - if exe in ["java", "python2", "python3"]: + if exe in ["java", "python", "python2", "python3"]: logname = None else: userlog = [a for a in args if a.startswith("--gtest_output=")] diff --git a/modules/ts/misc/run_utils.py b/modules/ts/misc/run_utils.py index 8740aa7855..db1e7f5b0d 100644 --- a/modules/ts/misc/run_utils.py +++ b/modules/ts/misc/run_utils.py @@ -175,6 +175,7 @@ parse_patterns = ( {'name': "cuda_library", 'default': None, 'pattern': re.compile(r"^CUDA_CUDA_LIBRARY:FILEPATH=(.+)$")}, {'name': "cuda_version", 'default': None, 'pattern': re.compile(r"^CUDA_VERSION:STRING=(.+)$")}, {'name': "core_dependencies", 'default': None, 'pattern': re.compile(r"^opencv_core_LIB_DEPENDS:STATIC=(.+)$")}, + {'name': "python", 'default': None, 'pattern': re.compile(r"^BUILD_opencv_python:BOOL=(.*)$")}, {'name': "python2", 'default': None, 'pattern': re.compile(r"^BUILD_opencv_python2:BOOL=(.*)$")}, {'name': "python3", 'default': None, 'pattern': re.compile(r"^BUILD_opencv_python3:BOOL=(.*)$")}, ) @@ -253,6 +254,8 @@ class CMakeCache: files = glob.glob(os.path.join(d, mask)) if not self.getOS() == "android" and self.withJava(): files.append("java") + if self.withPython(): + files.append("python") if self.withPython2(): files.append("python2") if self.withPython3(): @@ -261,7 +264,7 @@ class CMakeCache: return [] def isMainModule(self, name): - return name in self.main_modules + ['python2', 'python3'] + return name in self.main_modules + ['python', 'python2', 'python3'] def withCuda(self): return self.cuda_version and self.with_cuda == "ON" and self.cuda_library and not self.cuda_library.endswith("-NOTFOUND") @@ -269,6 +272,9 @@ class CMakeCache: def withJava(self): return self.ant_executable and self.java_test_binary_dir + def withPython(self): + return self.python == 'ON' + def withPython2(self): return self.python2 == 'ON' From c6772a8f5ded16f63e29c03d077a1d9ad22aa30d Mon Sep 17 00:00:00 2001 From: Tetragramm Date: Fri, 21 Oct 2016 19:54:42 -0500 Subject: [PATCH 056/409] Fix CLAHE distribution. Fix CLAHE for 16-bit images. --- modules/imgproc/src/clahe.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/imgproc/src/clahe.cpp b/modules/imgproc/src/clahe.cpp index 342b393577..fa571f0318 100644 --- a/modules/imgproc/src/clahe.cpp +++ b/modules/imgproc/src/clahe.cpp @@ -212,8 +212,12 @@ namespace for (int i = 0; i < histSize; ++i) tileHist[i] += redistBatch; - for (int i = 0; i < residual; ++i) - tileHist[i]++; + if (residual != 0) + { + int residualStep = MAX(histSize / residual, 1); + for (int i = 0; i < histSize; i += residualStep) + tileHist[i]++; + } } // calc Lut @@ -359,7 +363,7 @@ namespace bool useOpenCL = cv::ocl::useOpenCL() && _src.isUMat() && _src.dims()<=2 && _src.type() == CV_8UC1; #endif - int histSize = _src.type() == CV_8UC1 ? 256 : 4096; + int histSize = _src.type() == CV_8UC1 ? 256 : 65536; cv::Size tileSize; cv::_InputArray _srcForLut; @@ -416,7 +420,7 @@ namespace if (_src.type() == CV_8UC1) calcLutBody = cv::makePtr >(srcForLut, lut_, tileSize, tilesX_, clipLimit, lutScale); else if (_src.type() == CV_16UC1) - calcLutBody = cv::makePtr >(srcForLut, lut_, tileSize, tilesX_, clipLimit, lutScale); + calcLutBody = cv::makePtr >(srcForLut, lut_, tileSize, tilesX_, clipLimit, lutScale); else CV_Error( CV_StsBadArg, "Unsupported type" ); @@ -426,7 +430,7 @@ namespace if (_src.type() == CV_8UC1) interpolationBody = cv::makePtr >(src, dst, lut_, tileSize, tilesX_, tilesY_); else if (_src.type() == CV_16UC1) - interpolationBody = cv::makePtr >(src, dst, lut_, tileSize, tilesX_, tilesY_); + interpolationBody = cv::makePtr >(src, dst, lut_, tileSize, tilesX_, tilesY_); cv::parallel_for_(cv::Range(0, src.rows), *interpolationBody); } From c585d3890e50d8b8fceabc5d486704501fa79c83 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 27 Oct 2016 13:22:13 +0300 Subject: [PATCH 057/409] core: update CV_XADD Allow to use custom implementations of CV_XADD (passed via -D of CXXFLAGS) --- modules/core/include/opencv2/core/operations.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/operations.hpp b/modules/core/include/opencv2/core/operations.hpp index 0177d8db7a..1b7484aded 100644 --- a/modules/core/include/opencv2/core/operations.hpp +++ b/modules/core/include/opencv2/core/operations.hpp @@ -52,8 +52,8 @@ #ifdef __cplusplus /////// exchange-add operation for atomic operations on reference counters /////// -#if defined __INTEL_COMPILER && !(defined WIN32 || defined _WIN32) // atomic increment on the linux version of the Intel(tm) compiler - #define CV_XADD(addr,delta) _InterlockedExchangeAdd(const_cast(reinterpret_cast(addr)), delta) +#ifdef CV_XADD + // allow to use user-defined macro #elif defined __GNUC__ #if defined __clang__ && __clang_major__ >= 3 && !defined __ANDROID__ && !defined __EMSCRIPTEN__ && !defined(__CUDACC__) From 676736bdb2cf94080a51891de06f1b0396c35167 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Tue, 25 Oct 2016 18:04:11 +0900 Subject: [PATCH 058/409] avoid using fmadd in RNG --- modules/core/src/rand.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/core/src/rand.cpp b/modules/core/src/rand.cpp index 5247af8771..094cde9768 100644 --- a/modules/core/src/rand.cpp +++ b/modules/core/src/rand.cpp @@ -239,6 +239,17 @@ static void randf_32f( float* arr, int len, uint64* state, const Vec2f* p, bool __m128 p1 = _mm_unpackhi_ps(q01l, q01h); _mm_storeu_ps(arr + i, _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(f), p0), p1)); +#elif defined __ARM_NEON && defined __aarch64__ + // handwritten NEON is required not for performance but for numerical stability! + // 64bit gcc tends to use fmadd instead of separate multiply and add + // use volatile to ensure to separate the multiply and add + float32x4x2_t q = vld2q_f32((const float*)(p + i)); + + float32x4_t p0 = q.val[0]; + float32x4_t p1 = q.val[1]; + + volatile float32x4_t v0 = vmulq_f32(vld1q_f32(f), p0); + vst1q_f32(arr+i, vaddq_f32(v0, p1)); #else arr[i+0] = f[0]*p[i+0][0] + p[i+0][1]; arr[i+1] = f[1]*p[i+1][0] + p[i+1][1]; @@ -255,6 +266,11 @@ static void randf_32f( float* arr, int len, uint64* state, const Vec2f* p, bool _mm_mul_ss(_mm_set_ss((float)(int)temp), _mm_set_ss(p[i][0])), _mm_set_ss(p[i][1])) ); +#elif defined __ARM_NEON && defined __aarch64__ + float32x2_t t = vadd_f32(vmul_f32( + vdup_n_f32((float)(int)temp), vdup_n_f32(p[i][0])), + vdup_n_f32(p[i][1])); + arr[i] = vget_lane_f32(t, 0); #else arr[i] = (int)temp*p[i][0] + p[i][1]; #endif From 17df65e666c5e49e2a5ca5a38106e90411c92c90 Mon Sep 17 00:00:00 2001 From: Tetragramm Date: Thu, 3 Nov 2016 20:41:16 -0500 Subject: [PATCH 059/409] Fix the OpenCL portion to match the c++ code. Fix an undiscovered bug in the c++ code. --- modules/imgproc/src/clahe.cpp | 2 +- modules/imgproc/src/opencl/clahe.cl | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/imgproc/src/clahe.cpp b/modules/imgproc/src/clahe.cpp index fa571f0318..1bec0387d4 100644 --- a/modules/imgproc/src/clahe.cpp +++ b/modules/imgproc/src/clahe.cpp @@ -215,7 +215,7 @@ namespace if (residual != 0) { int residualStep = MAX(histSize / residual, 1); - for (int i = 0; i < histSize; i += residualStep) + for (int i = 0; i < histSize && residual > 0; i += residualStep, residual--) tileHist[i]++; } } diff --git a/modules/imgproc/src/opencl/clahe.cl b/modules/imgproc/src/opencl/clahe.cl index 9f88b20bfd..187933ce0c 100644 --- a/modules/imgproc/src/opencl/clahe.cl +++ b/modules/imgproc/src/opencl/clahe.cl @@ -201,7 +201,10 @@ __kernel void calcLut(__global __const uchar * src, const int srcStep, tHistVal += redistBatch; int residual = totalClipped - redistBatch * 256; - if (tid < residual) + int rStep = 256 / residual; + if (rStep < 1) + rStep = 1; + if (tid%rStep == 0 && (tid/rStep) Date: Thu, 10 Nov 2016 15:39:18 +0300 Subject: [PATCH 060/409] test: tvl1 test check update --- modules/video/test/test_tvl1optflow.cpp | 36 ++++++++++++++----------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/modules/video/test/test_tvl1optflow.cpp b/modules/video/test/test_tvl1optflow.cpp index 3976f25599..b0b962c43b 100644 --- a/modules/video/test/test_tvl1optflow.cpp +++ b/modules/video/test/test_tvl1optflow.cpp @@ -116,34 +116,39 @@ namespace return !cvIsNaN(u.x) && !cvIsNaN(u.y) && (fabs(u.x) < 1e9) && (fabs(u.y) < 1e9); } - double calcRMSE(const Mat_& flow1, const Mat_& flow2) + void check(const Mat_& gold, const Mat_& flow, double threshold = 0.1, double expectedAccuracy = 0.95) { - double sum = 0.0; - int counter = 0; + threshold = threshold*threshold; - for (int i = 0; i < flow1.rows; ++i) + size_t gold_counter = 0; + size_t valid_counter = 0; + + for (int i = 0; i < gold.rows; ++i) { - for (int j = 0; j < flow1.cols; ++j) + for (int j = 0; j < gold.cols; ++j) { - const Point2f u1 = flow1(i, j); - const Point2f u2 = flow2(i, j); + const Point2f u1 = gold(i, j); + const Point2f u2 = flow(i, j); - if (isFlowCorrect(u1) && isFlowCorrect(u2)) + if (isFlowCorrect(u1)) { - const Point2f diff = u1 - u2; - sum += diff.ddot(diff); - ++counter; + gold_counter++; + if (isFlowCorrect(u2)) + { + const Point2f diff = u1 - u2; + double err = diff.ddot(diff); + if (err <= threshold) + valid_counter++; + } } } } - return sqrt(sum / (1e-9 + counter)); + EXPECT_GE(valid_counter, expectedAccuracy * gold_counter); } } TEST(Video_calcOpticalFlowDual_TVL1, Regression) { - const double MAX_RMSE = 0.03; - const string frame1_path = TS::ptr()->get_data_path() + "optflow/RubberWhale1.png"; const string frame2_path = TS::ptr()->get_data_path() + "optflow/RubberWhale2.png"; const string gold_flow_path = TS::ptr()->get_data_path() + "optflow/tvl1_flow.flo"; @@ -167,7 +172,6 @@ TEST(Video_calcOpticalFlowDual_TVL1, Regression) ASSERT_EQ(gold.rows, flow.rows); ASSERT_EQ(gold.cols, flow.cols); - double err = calcRMSE(gold, flow); - EXPECT_LE(err, MAX_RMSE); + check(gold, flow); #endif } From 45bf60d18ccbf8061a7e540c1247db5a8c298fc9 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 10 Nov 2016 15:41:44 +0300 Subject: [PATCH 061/409] perf: remove tvl1 check for magic numbers, reduce number of samples --- modules/video/perf/perf_tvl1optflow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/video/perf/perf_tvl1optflow.cpp b/modules/video/perf/perf_tvl1optflow.cpp index 0a0f74a82e..4895ff455e 100644 --- a/modules/video/perf/perf_tvl1optflow.cpp +++ b/modules/video/perf/perf_tvl1optflow.cpp @@ -24,7 +24,7 @@ PERF_TEST_P(ImagePair, OpticalFlowDual_TVL1, testing::Values(impair("cv/optflow/ Ptr tvl1 = createOptFlow_DualTVL1(); - TEST_CYCLE_N(10) tvl1->calc(frame1, frame2, flow); + TEST_CYCLE() tvl1->calc(frame1, frame2, flow); - SANITY_CHECK(flow, 0.8); + SANITY_CHECK_NOTHING(); } From ca1ce5fa5cb5a7bcd23a039bee816ed47e1b64d6 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Wed, 9 Nov 2016 22:32:57 +0300 Subject: [PATCH 062/409] Fix for large image handling in OpenVX based implementation of HAL API --- 3rdparty/openvx/include/openvx_hal.hpp | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/include/openvx_hal.hpp index c5941e7b83..8c1bc3c3ab 100644 --- a/3rdparty/openvx/include/openvx_hal.hpp +++ b/3rdparty/openvx/include/openvx_hal.hpp @@ -349,6 +349,8 @@ inline void setConstantBorder(vx_border_t &border, vx_uint8 val) template \ inline int ovx_hal_##hal_func(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h) \ { \ + if(w >= 4194304 || h >= 4194304) \ + return CV_HAL_ERROR_NOT_IMPLEMENTED; \ try \ { \ vxContext * ctx = vxContext::getContext(); \ @@ -377,6 +379,8 @@ OVX_BINARY_OP(xor, {vxErr::check(vxuXor(ctx->ctx, ia.img, ib.img, ic.img));}) template inline int ovx_hal_mul(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h, double scale) { + if (w >= 4194304 || h >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; #ifdef _MSC_VER const float MAGIC_SCALE = 0x0.01010102; #else @@ -414,6 +418,8 @@ inline int ovx_hal_mul(const T *a, size_t astep, const T *b, size_t bstep, T *c, inline int ovx_hal_not(const uchar *a, size_t astep, uchar *c, size_t cstep, int w, int h) { + if (w >= 4194304 || h >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; try { vxContext * ctx = vxContext::getContext(); @@ -431,6 +437,8 @@ inline int ovx_hal_not(const uchar *a, size_t astep, uchar *c, size_t cstep, int inline int ovx_hal_merge8u(const uchar **src_data, uchar *dst_data, int len, int cn) { + if (len >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; if (cn != 3 && cn != 4) return CV_HAL_ERROR_NOT_IMPLEMENTED; try @@ -454,6 +462,8 @@ inline int ovx_hal_merge8u(const uchar **src_data, uchar *dst_data, int len, int inline int ovx_hal_resize(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, double inv_scale_x, double inv_scale_y, int interpolation) { + if (aw >= 4194304 || ah >= 4194304 || bw >= 4194304 || bh >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; try { vxContext * ctx = vxContext::getContext(); @@ -489,6 +499,8 @@ inline int ovx_hal_resize(int atype, const uchar *a, size_t astep, int aw, int a inline int ovx_hal_warpAffine(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[6], int interpolation, int borderType, const double borderValue[4]) { + if (aw >= 4194304 || ah >= 4194304 || bw >= 4194304 || bh >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; try { vxContext * ctx = vxContext::getContext(); @@ -546,6 +558,8 @@ inline int ovx_hal_warpAffine(int atype, const uchar *a, size_t astep, int aw, i inline int ovx_hal_warpPerspectve(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[9], int interpolation, int borderType, const double borderValue[4]) { + if (aw >= 4194304 || ah >= 4194304 || bw >= 4194304 || bh >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; try { vxContext * ctx = vxContext::getContext(); @@ -689,6 +703,8 @@ inline int ovx_hal_filterFree(cvhalFilter2D *filter_context) inline int ovx_hal_filter(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int , int , int , int ) { + if (w >= 4194304 || h >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; try { FilterCtx* cnv = (FilterCtx*)filter_context; @@ -909,6 +925,8 @@ inline int ovx_hal_morphFree(cvhalFilter2D *filter_context) inline int ovx_hal_morph(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int , int , int , int , int , int , int , int ) { + if (w >= 4194304 || h >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; try { MorphCtx* mat = (MorphCtx*)filter_context; @@ -939,6 +957,8 @@ inline int ovx_hal_morph(cvhalFilter2D *filter_context, uchar *a, size_t astep, inline int ovx_hal_cvtBGRtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int depth, int acn, int bcn, bool swapBlue) { + if (w >= 4194304 || h >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; if (depth != CV_8U || swapBlue || acn == bcn || (acn != 3 && acn != 4) || (bcn != 3 && bcn != 4)) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -962,6 +982,8 @@ inline int ovx_hal_cvtBGRtoBGR(const uchar * a, size_t astep, uchar * b, size_t inline int ovx_hal_cvtTwoPlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx) { + if (w >= 4194304 || h >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; if (!swapBlue || (bcn != 3 && bcn != 4)) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -989,6 +1011,8 @@ inline int ovx_hal_cvtTwoPlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, inline int ovx_hal_cvtThreePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx) { + if (w >= 4194304 || h >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; if (!swapBlue || (bcn != 3 && bcn != 4) || uIdx || (size_t)w / 2 != astep - (size_t)w / 2) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -1016,6 +1040,8 @@ inline int ovx_hal_cvtThreePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * inline int ovx_hal_cvtBGRtoThreePlaneYUV(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int acn, bool swapBlue, int uIdx) { + if (w >= 4194304 || h >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; if (!swapBlue || (acn != 3 && acn != 4) || uIdx || (size_t)w / 2 != bstep - (size_t)w / 2) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -1039,6 +1065,8 @@ inline int ovx_hal_cvtBGRtoThreePlaneYUV(const uchar * a, size_t astep, uchar * inline int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx, int ycn) { + if (w >= 4194304 || h >= 4194304) + return CV_HAL_ERROR_NOT_IMPLEMENTED; if (!swapBlue || (bcn != 3 && bcn != 4) || uIdx) return CV_HAL_ERROR_NOT_IMPLEMENTED; From cd1b324e5e58ce2e3edb3bde79a7e4bd36053100 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Mon, 7 Nov 2016 17:04:00 +0300 Subject: [PATCH 063/409] Fix for OpenVX based implementation of wrapAffine HAL API --- 3rdparty/openvx/include/openvx_hal.hpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/include/openvx_hal.hpp index 8c1bc3c3ab..78e04082a6 100644 --- a/3rdparty/openvx/include/openvx_hal.hpp +++ b/3rdparty/openvx/include/openvx_hal.hpp @@ -517,8 +517,7 @@ inline int ovx_hal_warpAffine(int atype, const uchar *a, size_t astep, int aw, i setConstantBorder(border, (vx_uint8)borderValue[0]); break; case CV_HAL_BORDER_REPLICATE: - border.mode = VX_BORDER_REPLICATE; - break; + // Neither 1.0 nor 1.1 OpenVX support BORDER_REPLICATE for warpings default: return CV_HAL_ERROR_NOT_IMPLEMENTED; } @@ -526,8 +525,9 @@ inline int ovx_hal_warpAffine(int atype, const uchar *a, size_t astep, int aw, i int mode; if (interpolation == CV_HAL_INTER_LINEAR) mode = VX_INTERPOLATION_BILINEAR; - else if (interpolation == CV_HAL_INTER_AREA) - mode = VX_INTERPOLATION_AREA; + //AREA interpolation is unsupported + //else if (interpolation == CV_HAL_INTER_AREA) + // mode = VX_INTERPOLATION_AREA; else if (interpolation == CV_HAL_INTER_NEAREST) mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; else @@ -576,8 +576,7 @@ inline int ovx_hal_warpPerspectve(int atype, const uchar *a, size_t astep, int a setConstantBorder(border, (vx_uint8)borderValue[0]); break; case CV_HAL_BORDER_REPLICATE: - border.mode = VX_BORDER_REPLICATE; - break; + // Neither 1.0 nor 1.1 OpenVX support BORDER_REPLICATE for warpings default: return CV_HAL_ERROR_NOT_IMPLEMENTED; } @@ -585,8 +584,9 @@ inline int ovx_hal_warpPerspectve(int atype, const uchar *a, size_t astep, int a int mode; if (interpolation == CV_HAL_INTER_LINEAR) mode = VX_INTERPOLATION_BILINEAR; - else if (interpolation == CV_HAL_INTER_AREA) - mode = VX_INTERPOLATION_AREA; + //AREA interpolation is unsupported + //else if (interpolation == CV_HAL_INTER_AREA) + // mode = VX_INTERPOLATION_AREA; else if (interpolation == CV_HAL_INTER_NEAREST) mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; else @@ -1132,8 +1132,10 @@ inline int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, #undef cv_hal_warpAffine #define cv_hal_warpAffine ovx_hal_warpAffine -#undef cv_hal_warpPerspective -#define cv_hal_warpPerspective ovx_hal_warpPerspectve +//OpenVX perspective warp use round to zero policy at least in sample implementation +//while OpenCV require round to nearest +//#undef cv_hal_warpPerspective +//#define cv_hal_warpPerspective ovx_hal_warpPerspectve #undef cv_hal_filterInit #define cv_hal_filterInit ovx_hal_filterInit From 850d12ae69657219f02df097ddde008941774fea Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Wed, 9 Nov 2016 23:34:10 +0300 Subject: [PATCH 064/409] Completely disabled OpenVX based implementation of warp HAL API --- 3rdparty/openvx/include/openvx_hal.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/include/openvx_hal.hpp index 78e04082a6..8884c7a4fd 100644 --- a/3rdparty/openvx/include/openvx_hal.hpp +++ b/3rdparty/openvx/include/openvx_hal.hpp @@ -1130,10 +1130,10 @@ inline int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, #undef cv_hal_resize #define cv_hal_resize ovx_hal_resize -#undef cv_hal_warpAffine -#define cv_hal_warpAffine ovx_hal_warpAffine -//OpenVX perspective warp use round to zero policy at least in sample implementation +//OpenVX warps use round to zero policy at least in sample implementation //while OpenCV require round to nearest +//#undef cv_hal_warpAffine +//#define cv_hal_warpAffine ovx_hal_warpAffine //#undef cv_hal_warpPerspective //#define cv_hal_warpPerspective ovx_hal_warpPerspectve From 88ea9f5dd72d261385ffe41b5decd8bedd458b34 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Fri, 30 Sep 2016 14:55:06 +0300 Subject: [PATCH 065/409] Fix for OpenVX based implementation of resize HAL API --- 3rdparty/openvx/include/openvx_hal.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/include/openvx_hal.hpp index 8884c7a4fd..62c99f5820 100644 --- a/3rdparty/openvx/include/openvx_hal.hpp +++ b/3rdparty/openvx/include/openvx_hal.hpp @@ -479,11 +479,15 @@ inline int ovx_hal_resize(int atype, const uchar *a, size_t astep, int aw, int a int mode; if (interpolation == CV_HAL_INTER_LINEAR) + { mode = VX_INTERPOLATION_BILINEAR; + if (inv_scale_x > 1 || inv_scale_y > 1) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } else if (interpolation == CV_HAL_INTER_AREA) - mode = VX_INTERPOLATION_AREA; + return CV_HAL_ERROR_NOT_IMPLEMENTED; //mode = VX_INTERPOLATION_AREA; else if (interpolation == CV_HAL_INTER_NEAREST) - mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; + return CV_HAL_ERROR_NOT_IMPLEMENTED; //mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; else return CV_HAL_ERROR_NOT_IMPLEMENTED; From 9eac0f057556b7000fe6458a570c4ac6d312fe74 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Wed, 9 Nov 2016 23:33:40 +0300 Subject: [PATCH 066/409] Completely disabled OpenVX based implementation of resize HAL API --- 3rdparty/openvx/include/openvx_hal.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/include/openvx_hal.hpp index 62c99f5820..f0aa3d5d3b 100644 --- a/3rdparty/openvx/include/openvx_hal.hpp +++ b/3rdparty/openvx/include/openvx_hal.hpp @@ -1131,8 +1131,8 @@ inline int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, #undef cv_hal_merge8u #define cv_hal_merge8u ovx_hal_merge8u -#undef cv_hal_resize -#define cv_hal_resize ovx_hal_resize +//#undef cv_hal_resize +//#define cv_hal_resize ovx_hal_resize //OpenVX warps use round to zero policy at least in sample implementation //while OpenCV require round to nearest From 64a2d1904b80faf71a322f934be885bee8ecac4a Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 11 Nov 2016 16:27:22 +0300 Subject: [PATCH 067/409] ffmpeg: backport 7213 Commit: 6d54abbd0fc64b23ea0b97f3c906cba219b52dc8 --- modules/highgui/src/cap_ffmpeg_impl.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/highgui/src/cap_ffmpeg_impl.hpp b/modules/highgui/src/cap_ffmpeg_impl.hpp index da05bde324..a934faaa91 100644 --- a/modules/highgui/src/cap_ffmpeg_impl.hpp +++ b/modules/highgui/src/cap_ffmpeg_impl.hpp @@ -1482,6 +1482,10 @@ static AVStream *icv_add_video_stream_FFMPEG(AVFormatContext *oc, } #endif +#if LIBAVCODEC_BUILD >= CALC_FFMPEG_VERSION(52, 42, 0) + st->avg_frame_rate = (AVRational){frame_rate, frame_rate_base}; +#endif + return st; } From 614ce7743e567b78818c70f2568ec04c3d6a44f0 Mon Sep 17 00:00:00 2001 From: k-shinotsuka Date: Sat, 12 Nov 2016 19:02:43 +0900 Subject: [PATCH 068/409] add SSE code for HSV2RGB_f. --- modules/imgproc/src/color.cpp | 154 +++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 3 deletions(-) diff --git a/modules/imgproc/src/color.cpp b/modules/imgproc/src/color.cpp index 5b36386dcb..2a75422428 100644 --- a/modules/imgproc/src/color.cpp +++ b/modules/imgproc/src/color.cpp @@ -4443,16 +4443,161 @@ struct HSV2RGB_f typedef float channel_type; HSV2RGB_f(int _dstcn, int _blueIdx, float _hrange) - : dstcn(_dstcn), blueIdx(_blueIdx), hscale(6.f/_hrange) {} + : dstcn(_dstcn), blueIdx(_blueIdx), hscale(6.f/_hrange) { + #if CV_SSE2 + haveSIMD = checkHardwareSupport(CV_CPU_SSE2); + #endif + } + + #if CV_SSE2 + void process(__m128& v_h0, __m128& v_h1, __m128& v_s0, + __m128& v_s1, __m128& v_v0, __m128& v_v1) const + { + v_h0 = _mm_mul_ps(v_h0, _mm_set1_ps(hscale)); + v_h1 = _mm_mul_ps(v_h1, _mm_set1_ps(hscale)); + + __m128 v_pre_sector0 = _mm_cvtepi32_ps(_mm_cvttps_epi32(v_h0)); + __m128 v_pre_sector1 = _mm_cvtepi32_ps(_mm_cvttps_epi32(v_h1)); + + v_h0 = _mm_sub_ps(v_h0, v_pre_sector0); + v_h1 = _mm_sub_ps(v_h1, v_pre_sector1); + + __m128 v_tab00 = v_v0; + __m128 v_tab01 = v_v1; + __m128 v_tab10 = _mm_mul_ps(v_v0, _mm_sub_ps(_mm_set1_ps(1.0f), v_s0)); + __m128 v_tab11 = _mm_mul_ps(v_v1, _mm_sub_ps(_mm_set1_ps(1.0f), v_s1)); + __m128 v_tab20 = _mm_mul_ps(v_v0, _mm_sub_ps(_mm_set1_ps(1.0f), _mm_mul_ps(v_s0, v_h0))); + __m128 v_tab21 = _mm_mul_ps(v_v1, _mm_sub_ps(_mm_set1_ps(1.0f), _mm_mul_ps(v_s1, v_h1))); + __m128 v_tab30 = _mm_mul_ps(v_v0, _mm_sub_ps(_mm_set1_ps(1.0f), _mm_mul_ps(v_s0, _mm_sub_ps(_mm_set1_ps(1.0f), v_h0)))); + __m128 v_tab31 = _mm_mul_ps(v_v1, _mm_sub_ps(_mm_set1_ps(1.0f), _mm_mul_ps(v_s1, _mm_sub_ps(_mm_set1_ps(1.0f), v_h1)))); + + __m128 v_sector0 = _mm_div_ps(v_pre_sector0, _mm_set1_ps(6.0f)); + __m128 v_sector1 = _mm_div_ps(v_pre_sector1, _mm_set1_ps(6.0f)); + v_sector0 = _mm_cvtepi32_ps(_mm_cvttps_epi32(v_sector0)); + v_sector1 = _mm_cvtepi32_ps(_mm_cvttps_epi32(v_sector1)); + v_sector0 = _mm_mul_ps(v_sector0, _mm_set1_ps(6.0f)); + v_sector1 = _mm_mul_ps(v_sector1, _mm_set1_ps(6.0f)); + v_sector0 = _mm_sub_ps(v_pre_sector0, v_sector0); + v_sector1 = _mm_sub_ps(v_pre_sector1, v_sector1); + + v_h0 = _mm_and_ps(v_tab10, _mm_cmplt_ps(v_sector0, _mm_set1_ps(2.0f))); + v_h1 = _mm_and_ps(v_tab11, _mm_cmplt_ps(v_sector1, _mm_set1_ps(2.0f))); + v_h0 = _mm_or_ps(v_h0, _mm_and_ps(v_tab30, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(2.0f)))); + v_h1 = _mm_or_ps(v_h1, _mm_and_ps(v_tab31, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(2.0f)))); + v_h0 = _mm_or_ps(v_h0, _mm_and_ps(v_tab00, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(3.0f)))); + v_h1 = _mm_or_ps(v_h1, _mm_and_ps(v_tab01, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(3.0f)))); + v_h0 = _mm_or_ps(v_h0, _mm_and_ps(v_tab00, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(4.0f)))); + v_h1 = _mm_or_ps(v_h1, _mm_and_ps(v_tab01, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(4.0f)))); + v_h0 = _mm_or_ps(v_h0, _mm_and_ps(v_tab20, _mm_cmpgt_ps(v_sector0, _mm_set1_ps(4.0f)))); + v_h1 = _mm_or_ps(v_h1, _mm_and_ps(v_tab21, _mm_cmpgt_ps(v_sector1, _mm_set1_ps(4.0f)))); + v_s0 = _mm_and_ps(v_tab30, _mm_cmplt_ps(v_sector0, _mm_set1_ps(1.0f))); + v_s1 = _mm_and_ps(v_tab31, _mm_cmplt_ps(v_sector1, _mm_set1_ps(1.0f))); + v_s0 = _mm_or_ps(v_s0, _mm_and_ps(v_tab00, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(1.0f)))); + v_s1 = _mm_or_ps(v_s1, _mm_and_ps(v_tab01, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(1.0f)))); + v_s0 = _mm_or_ps(v_s0, _mm_and_ps(v_tab00, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(2.0f)))); + v_s1 = _mm_or_ps(v_s1, _mm_and_ps(v_tab01, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(2.0f)))); + v_s0 = _mm_or_ps(v_s0, _mm_and_ps(v_tab20, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(3.0f)))); + v_s1 = _mm_or_ps(v_s1, _mm_and_ps(v_tab21, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(3.0f)))); + v_s0 = _mm_or_ps(v_s0, _mm_and_ps(v_tab10, _mm_cmpgt_ps(v_sector0, _mm_set1_ps(3.0f)))); + v_s1 = _mm_or_ps(v_s1, _mm_and_ps(v_tab11, _mm_cmpgt_ps(v_sector1, _mm_set1_ps(3.0f)))); + v_v0 = _mm_and_ps(v_tab00, _mm_cmplt_ps(v_sector0, _mm_set1_ps(1.0f))); + v_v1 = _mm_and_ps(v_tab01, _mm_cmplt_ps(v_sector1, _mm_set1_ps(1.0f))); + v_v0 = _mm_or_ps(v_v0, _mm_and_ps(v_tab20, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(1.0f)))); + v_v1 = _mm_or_ps(v_v1, _mm_and_ps(v_tab21, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(1.0f)))); + v_v0 = _mm_or_ps(v_v0, _mm_and_ps(v_tab10, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(2.0f)))); + v_v1 = _mm_or_ps(v_v1, _mm_and_ps(v_tab11, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(2.0f)))); + v_v0 = _mm_or_ps(v_v0, _mm_and_ps(v_tab10, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(3.0f)))); + v_v1 = _mm_or_ps(v_v1, _mm_and_ps(v_tab11, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(3.0f)))); + v_v0 = _mm_or_ps(v_v0, _mm_and_ps(v_tab30, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(4.0f)))); + v_v1 = _mm_or_ps(v_v1, _mm_and_ps(v_tab31, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(4.0f)))); + v_v0 = _mm_or_ps(v_v0, _mm_and_ps(v_tab00, _mm_cmpgt_ps(v_sector0, _mm_set1_ps(4.0f)))); + v_v1 = _mm_or_ps(v_v1, _mm_and_ps(v_tab01, _mm_cmpgt_ps(v_sector1, _mm_set1_ps(4.0f)))); + } + #endif void operator()(const float* src, float* dst, int n) const { - int i, bidx = blueIdx, dcn = dstcn; + int i = 0, bidx = blueIdx, dcn = dstcn; float _hscale = hscale; float alpha = ColorChannel::max(); n *= 3; - for( i = 0; i < n; i += 3, dst += dcn ) + #if CV_SSE2 + if (haveSIMD) + { + for( ; i <= n - 24; i += 24, dst += dcn * 8 ) + { + __m128 v_h0 = _mm_loadu_ps(src + i + 0); + __m128 v_h1 = _mm_loadu_ps(src + i + 4); + __m128 v_s0 = _mm_loadu_ps(src + i + 8); + __m128 v_s1 = _mm_loadu_ps(src + i + 12); + __m128 v_v0 = _mm_loadu_ps(src + i + 16); + __m128 v_v1 = _mm_loadu_ps(src + i + 20); + + _mm_deinterleave_ps(v_h0, v_h1, v_s0, v_s1, v_v0, v_v1); + + process(v_h0, v_h1, v_s0, v_s1, v_v0, v_v1); + + if (dcn == 3) + { + if (bidx) + { + _mm_interleave_ps(v_v0, v_v1, v_s0, v_s1, v_h0, v_h1); + + _mm_storeu_ps(dst + 0, v_v0); + _mm_storeu_ps(dst + 4, v_v1); + _mm_storeu_ps(dst + 8, v_s0); + _mm_storeu_ps(dst + 12, v_s1); + _mm_storeu_ps(dst + 16, v_h0); + _mm_storeu_ps(dst + 20, v_h1); + } + else + { + _mm_interleave_ps(v_h0, v_h1, v_s0, v_s1, v_v0, v_v1); + + _mm_storeu_ps(dst + 0, v_h0); + _mm_storeu_ps(dst + 4, v_h1); + _mm_storeu_ps(dst + 8, v_s0); + _mm_storeu_ps(dst + 12, v_s1); + _mm_storeu_ps(dst + 16, v_v0); + _mm_storeu_ps(dst + 20, v_v1); + } + } + else + { + __m128 v_a0 = _mm_set1_ps(alpha); + __m128 v_a1 = _mm_set1_ps(alpha); + if (bidx) + { + _mm_interleave_ps(v_v0, v_v1, v_s0, v_s1, v_h0, v_h1, v_a0, v_a1); + + _mm_storeu_ps(dst + 0, v_v0); + _mm_storeu_ps(dst + 4, v_v1); + _mm_storeu_ps(dst + 8, v_s0); + _mm_storeu_ps(dst + 12, v_s1); + _mm_storeu_ps(dst + 16, v_h0); + _mm_storeu_ps(dst + 20, v_h1); + _mm_storeu_ps(dst + 24, v_a0); + _mm_storeu_ps(dst + 28, v_a1); + } + else + { + _mm_interleave_ps(v_h0, v_h1, v_s0, v_s1, v_v0, v_v1, v_a0, v_a1); + + _mm_storeu_ps(dst + 0, v_h0); + _mm_storeu_ps(dst + 4, v_h1); + _mm_storeu_ps(dst + 8, v_s0); + _mm_storeu_ps(dst + 12, v_s1); + _mm_storeu_ps(dst + 16, v_v0); + _mm_storeu_ps(dst + 20, v_v1); + _mm_storeu_ps(dst + 24, v_a0); + _mm_storeu_ps(dst + 28, v_a1); + } + } + } + } + #endif + for( ; i < n; i += 3, dst += dcn ) { float h = src[i], s = src[i+1], v = src[i+2]; float b, g, r; @@ -4498,6 +4643,9 @@ struct HSV2RGB_f int dstcn, blueIdx; float hscale; + #if CV_SSE2 + bool haveSIMD; + #endif }; From a909527a3978bb52eb9f47ca18233036ce4eae82 Mon Sep 17 00:00:00 2001 From: k-shinotsuka Date: Sun, 13 Nov 2016 15:24:00 +0900 Subject: [PATCH 069/409] add SSE code for HLS2RGB_f. --- modules/imgproc/src/color.cpp | 162 +++++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 3 deletions(-) diff --git a/modules/imgproc/src/color.cpp b/modules/imgproc/src/color.cpp index 9f3e6ac992..bd6934d18e 100644 --- a/modules/imgproc/src/color.cpp +++ b/modules/imgproc/src/color.cpp @@ -4967,16 +4967,169 @@ struct HLS2RGB_f typedef float channel_type; HLS2RGB_f(int _dstcn, int _blueIdx, float _hrange) - : dstcn(_dstcn), blueIdx(_blueIdx), hscale(6.f/_hrange) {} + : dstcn(_dstcn), blueIdx(_blueIdx), hscale(6.f/_hrange) { + #if CV_SSE2 + haveSIMD = checkHardwareSupport(CV_CPU_SSE2); + #endif + } + + #if CV_SSE2 + void process(__m128& v_h0, __m128& v_h1, __m128& v_l0, + __m128& v_l1, __m128& v_s0, __m128& v_s1) const + { + __m128 v_lel0 = _mm_cmple_ps(v_l0, _mm_set1_ps(0.5f)); + __m128 v_lel1 = _mm_cmple_ps(v_l1, _mm_set1_ps(0.5f)); + __m128 v_p20 = _mm_andnot_ps(v_lel0, _mm_sub_ps(_mm_add_ps(v_l0, v_s0), _mm_mul_ps(v_l0, v_s0))); + __m128 v_p21 = _mm_andnot_ps(v_lel1, _mm_sub_ps(_mm_add_ps(v_l1, v_s1), _mm_mul_ps(v_l1, v_s1))); + v_p20 = _mm_or_ps(v_p20, _mm_and_ps(v_lel0, _mm_mul_ps(v_l0, _mm_add_ps(_mm_set1_ps(1.0f), v_s0)))); + v_p21 = _mm_or_ps(v_p21, _mm_and_ps(v_lel1, _mm_mul_ps(v_l1, _mm_add_ps(_mm_set1_ps(1.0f), v_s1)))); + + __m128 v_p10 = _mm_sub_ps(_mm_mul_ps(_mm_set1_ps(2.0f), v_l0), v_p20); + __m128 v_p11 = _mm_sub_ps(_mm_mul_ps(_mm_set1_ps(2.0f), v_l1), v_p21); + + v_h0 = _mm_mul_ps(v_h0, _mm_set1_ps(hscale)); + v_h1 = _mm_mul_ps(v_h1, _mm_set1_ps(hscale)); + + __m128 v_pre_sector0 = _mm_cvtepi32_ps(_mm_cvttps_epi32(v_h0)); + __m128 v_pre_sector1 = _mm_cvtepi32_ps(_mm_cvttps_epi32(v_h1)); + + v_h0 = _mm_sub_ps(v_h0, v_pre_sector0); + v_h1 = _mm_sub_ps(v_h1, v_pre_sector1); + + __m128 v_p2_p10 = _mm_sub_ps(v_p20, v_p10); + __m128 v_p2_p11 = _mm_sub_ps(v_p21, v_p11); + __m128 v_tab20 = _mm_add_ps(v_p10, _mm_mul_ps(v_p2_p10, _mm_sub_ps(_mm_set1_ps(1.0f), v_h0))); + __m128 v_tab21 = _mm_add_ps(v_p11, _mm_mul_ps(v_p2_p11, _mm_sub_ps(_mm_set1_ps(1.0f), v_h1))); + __m128 v_tab30 = _mm_add_ps(v_p10, _mm_mul_ps(v_p2_p10, v_h0)); + __m128 v_tab31 = _mm_add_ps(v_p11, _mm_mul_ps(v_p2_p11, v_h1)); + + __m128 v_sector0 = _mm_div_ps(v_pre_sector0, _mm_set1_ps(6.0f)); + __m128 v_sector1 = _mm_div_ps(v_pre_sector1, _mm_set1_ps(6.0f)); + v_sector0 = _mm_cvtepi32_ps(_mm_cvttps_epi32(v_sector0)); + v_sector1 = _mm_cvtepi32_ps(_mm_cvttps_epi32(v_sector1)); + v_sector0 = _mm_mul_ps(v_sector0, _mm_set1_ps(6.0f)); + v_sector1 = _mm_mul_ps(v_sector1, _mm_set1_ps(6.0f)); + v_sector0 = _mm_sub_ps(v_pre_sector0, v_sector0); + v_sector1 = _mm_sub_ps(v_pre_sector1, v_sector1); + + v_h0 = _mm_and_ps(v_p10, _mm_cmplt_ps(v_sector0, _mm_set1_ps(2.0f))); + v_h1 = _mm_and_ps(v_p11, _mm_cmplt_ps(v_sector1, _mm_set1_ps(2.0f))); + v_h0 = _mm_or_ps(v_h0, _mm_and_ps(v_tab30, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(2.0f)))); + v_h1 = _mm_or_ps(v_h1, _mm_and_ps(v_tab31, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(2.0f)))); + v_h0 = _mm_or_ps(v_h0, _mm_and_ps(v_p20, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(3.0f)))); + v_h1 = _mm_or_ps(v_h1, _mm_and_ps(v_p21, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(3.0f)))); + v_h0 = _mm_or_ps(v_h0, _mm_and_ps(v_p20, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(4.0f)))); + v_h1 = _mm_or_ps(v_h1, _mm_and_ps(v_p21, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(4.0f)))); + v_h0 = _mm_or_ps(v_h0, _mm_and_ps(v_tab20, _mm_cmpgt_ps(v_sector0, _mm_set1_ps(4.0f)))); + v_h1 = _mm_or_ps(v_h1, _mm_and_ps(v_tab21, _mm_cmpgt_ps(v_sector1, _mm_set1_ps(4.0f)))); + v_l0 = _mm_and_ps(v_tab30, _mm_cmplt_ps(v_sector0, _mm_set1_ps(1.0f))); + v_l1 = _mm_and_ps(v_tab31, _mm_cmplt_ps(v_sector1, _mm_set1_ps(1.0f))); + v_l0 = _mm_or_ps(v_l0, _mm_and_ps(v_p20, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(1.0f)))); + v_l1 = _mm_or_ps(v_l1, _mm_and_ps(v_p21, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(1.0f)))); + v_l0 = _mm_or_ps(v_l0, _mm_and_ps(v_p20, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(2.0f)))); + v_l1 = _mm_or_ps(v_l1, _mm_and_ps(v_p21, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(2.0f)))); + v_l0 = _mm_or_ps(v_l0, _mm_and_ps(v_tab20, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(3.0f)))); + v_l1 = _mm_or_ps(v_l1, _mm_and_ps(v_tab21, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(3.0f)))); + v_l0 = _mm_or_ps(v_l0, _mm_and_ps(v_p10, _mm_cmpgt_ps(v_sector0, _mm_set1_ps(3.0f)))); + v_l1 = _mm_or_ps(v_l1, _mm_and_ps(v_p11, _mm_cmpgt_ps(v_sector1, _mm_set1_ps(3.0f)))); + v_s0 = _mm_and_ps(v_p20, _mm_cmplt_ps(v_sector0, _mm_set1_ps(1.0f))); + v_s1 = _mm_and_ps(v_p21, _mm_cmplt_ps(v_sector1, _mm_set1_ps(1.0f))); + v_s0 = _mm_or_ps(v_s0, _mm_and_ps(v_tab20, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(1.0f)))); + v_s1 = _mm_or_ps(v_s1, _mm_and_ps(v_tab21, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(1.0f)))); + v_s0 = _mm_or_ps(v_s0, _mm_and_ps(v_p10, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(2.0f)))); + v_s1 = _mm_or_ps(v_s1, _mm_and_ps(v_p11, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(2.0f)))); + v_s0 = _mm_or_ps(v_s0, _mm_and_ps(v_p10, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(3.0f)))); + v_s1 = _mm_or_ps(v_s1, _mm_and_ps(v_p11, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(3.0f)))); + v_s0 = _mm_or_ps(v_s0, _mm_and_ps(v_tab30, _mm_cmpeq_ps(v_sector0, _mm_set1_ps(4.0f)))); + v_s1 = _mm_or_ps(v_s1, _mm_and_ps(v_tab31, _mm_cmpeq_ps(v_sector1, _mm_set1_ps(4.0f)))); + v_s0 = _mm_or_ps(v_s0, _mm_and_ps(v_p20, _mm_cmpgt_ps(v_sector0, _mm_set1_ps(4.0f)))); + v_s1 = _mm_or_ps(v_s1, _mm_and_ps(v_p21, _mm_cmpgt_ps(v_sector1, _mm_set1_ps(4.0f)))); + } + #endif void operator()(const float* src, float* dst, int n) const { - int i, bidx = blueIdx, dcn = dstcn; + int i = 0, bidx = blueIdx, dcn = dstcn; float _hscale = hscale; float alpha = ColorChannel::max(); n *= 3; - for( i = 0; i < n; i += 3, dst += dcn ) + #if CV_SSE2 + if (haveSIMD) + { + for( ; i <= n - 24; i += 24, dst += dcn * 8 ) + { + __m128 v_h0 = _mm_loadu_ps(src + i + 0); + __m128 v_h1 = _mm_loadu_ps(src + i + 4); + __m128 v_l0 = _mm_loadu_ps(src + i + 8); + __m128 v_l1 = _mm_loadu_ps(src + i + 12); + __m128 v_s0 = _mm_loadu_ps(src + i + 16); + __m128 v_s1 = _mm_loadu_ps(src + i + 20); + + _mm_deinterleave_ps(v_h0, v_h1, v_l0, v_l1, v_s0, v_s1); + + process(v_h0, v_h1, v_l0, v_l1, v_s0, v_s1); + + if (dcn == 3) + { + if (bidx) + { + _mm_interleave_ps(v_s0, v_s1, v_l0, v_l1, v_h0, v_h1); + + _mm_storeu_ps(dst + 0, v_s0); + _mm_storeu_ps(dst + 4, v_s1); + _mm_storeu_ps(dst + 8, v_l0); + _mm_storeu_ps(dst + 12, v_l1); + _mm_storeu_ps(dst + 16, v_h0); + _mm_storeu_ps(dst + 20, v_h1); + } + else + { + _mm_interleave_ps(v_h0, v_h1, v_l0, v_l1, v_s0, v_s1); + + _mm_storeu_ps(dst + 0, v_h0); + _mm_storeu_ps(dst + 4, v_h1); + _mm_storeu_ps(dst + 8, v_l0); + _mm_storeu_ps(dst + 12, v_l1); + _mm_storeu_ps(dst + 16, v_s0); + _mm_storeu_ps(dst + 20, v_s1); + } + } + else + { + __m128 v_a0 = _mm_set1_ps(alpha); + __m128 v_a1 = _mm_set1_ps(alpha); + if (bidx) + { + _mm_interleave_ps(v_s0, v_s1, v_l0, v_l1, v_h0, v_h1, v_a0, v_a1); + + _mm_storeu_ps(dst + 0, v_s0); + _mm_storeu_ps(dst + 4, v_s1); + _mm_storeu_ps(dst + 8, v_l0); + _mm_storeu_ps(dst + 12, v_l1); + _mm_storeu_ps(dst + 16, v_h0); + _mm_storeu_ps(dst + 20, v_h1); + _mm_storeu_ps(dst + 24, v_a0); + _mm_storeu_ps(dst + 28, v_a1); + } + else + { + _mm_interleave_ps(v_h0, v_h1, v_l0, v_l1, v_s0, v_s1, v_a0, v_a1); + + _mm_storeu_ps(dst + 0, v_h0); + _mm_storeu_ps(dst + 4, v_h1); + _mm_storeu_ps(dst + 8, v_l0); + _mm_storeu_ps(dst + 12, v_l1); + _mm_storeu_ps(dst + 16, v_s0); + _mm_storeu_ps(dst + 20, v_s1); + _mm_storeu_ps(dst + 24, v_a0); + _mm_storeu_ps(dst + 28, v_a1); + } + } + } + } + #endif + for( ; i < n; i += 3, dst += dcn ) { float h = src[i], l = src[i+1], s = src[i+2]; float b, g, r; @@ -5023,6 +5176,9 @@ struct HLS2RGB_f int dstcn, blueIdx; float hscale; + #if CV_SSE2 + bool haveSIMD; + #endif }; From 4d84e6efee0d4cc0e259647df57ae5414c342816 Mon Sep 17 00:00:00 2001 From: Declan Moran Date: Mon, 14 Nov 2016 17:21:53 +0100 Subject: [PATCH 070/409] fix compiler error when building with gnu-5 toolchain (eg as currently shipped with crystax.org android ndk, and presumably soon with android studio). "ts_perf.cpp ....error: 'errno' was not declared in this scope int err=errno;" --- modules/ts/src/ts_perf.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ts/src/ts_perf.cpp b/modules/ts/src/ts_perf.cpp index 3bcb5c11a9..360c8b5a13 100644 --- a/modules/ts/src/ts_perf.cpp +++ b/modules/ts/src/ts_perf.cpp @@ -41,6 +41,7 @@ static bool log_power_checkpoints; #include #include +#include static void setCurrentThreadAffinityMask(int mask) { pid_t pid=gettid(); From cf5c400ecbf7f2832519e8f34f1b8bde3bc037a0 Mon Sep 17 00:00:00 2001 From: k-shinotsuka Date: Sun, 13 Nov 2016 23:13:02 +0900 Subject: [PATCH 071/409] add SSE code for RGB2HLS_f. --- modules/imgproc/src/color.cpp | 117 ++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 5 deletions(-) diff --git a/modules/imgproc/src/color.cpp b/modules/imgproc/src/color.cpp index 9f3e6ac992..054449ddeb 100644 --- a/modules/imgproc/src/color.cpp +++ b/modules/imgproc/src/color.cpp @@ -4725,15 +4725,119 @@ struct RGB2HLS_f typedef float channel_type; RGB2HLS_f(int _srccn, int _blueIdx, float _hrange) - : srccn(_srccn), blueIdx(_blueIdx), hrange(_hrange) {} + : srccn(_srccn), blueIdx(_blueIdx), hscale(_hrange/360.f) { + #if CV_SSE2 + haveSIMD = checkHardwareSupport(CV_CPU_SSE2); + #endif + } + + #if CV_SSE2 + void process(__m128& v_b0, __m128& v_b1, __m128& v_g0, + __m128& v_g1, __m128& v_r0, __m128& v_r1) const + { + __m128 v_max0 = _mm_max_ps(_mm_max_ps(v_b0, v_g0), v_r0); + __m128 v_max1 = _mm_max_ps(_mm_max_ps(v_b1, v_g1), v_r1); + __m128 v_min0 = _mm_min_ps(_mm_min_ps(v_b0, v_g0), v_r0); + __m128 v_min1 = _mm_min_ps(_mm_min_ps(v_b1, v_g1), v_r1); + __m128 v_diff0 = _mm_sub_ps(v_max0, v_min0); + __m128 v_diff1 = _mm_sub_ps(v_max1, v_min1); + __m128 v_sum0 = _mm_add_ps(v_max0, v_min0); + __m128 v_sum1 = _mm_add_ps(v_max1, v_min1); + __m128 v_l0 = _mm_mul_ps(v_sum0, _mm_set1_ps(0.5f)); + __m128 v_l1 = _mm_mul_ps(v_sum1, _mm_set1_ps(0.5f)); + + __m128 v_gel0 = _mm_cmpge_ps(v_l0, _mm_set1_ps(0.5f)); + __m128 v_gel1 = _mm_cmpge_ps(v_l1, _mm_set1_ps(0.5f)); + __m128 v_s0 = _mm_and_ps(v_gel0, _mm_sub_ps(_mm_set1_ps(2.0f), v_sum0)); + __m128 v_s1 = _mm_and_ps(v_gel1, _mm_sub_ps(_mm_set1_ps(2.0f), v_sum1)); + v_s0 = _mm_or_ps(v_s0, _mm_andnot_ps(v_gel0, v_sum0)); + v_s1 = _mm_or_ps(v_s1, _mm_andnot_ps(v_gel1, v_sum1)); + v_s0 = _mm_div_ps(v_diff0, v_s0); + v_s1 = _mm_div_ps(v_diff1, v_s1); + + __m128 v_gteps0 = _mm_cmpgt_ps(v_diff0, _mm_set1_ps(FLT_EPSILON)); + __m128 v_gteps1 = _mm_cmpgt_ps(v_diff1, _mm_set1_ps(FLT_EPSILON)); + + v_diff0 = _mm_div_ps(_mm_set1_ps(60.f), v_diff0); + v_diff1 = _mm_div_ps(_mm_set1_ps(60.f), v_diff1); + + __m128 v_eqr0 = _mm_cmpeq_ps(v_max0, v_r0); + __m128 v_eqr1 = _mm_cmpeq_ps(v_max1, v_r1); + __m128 v_h0 = _mm_and_ps(v_eqr0, _mm_mul_ps(_mm_sub_ps(v_g0, v_b0), v_diff0)); + __m128 v_h1 = _mm_and_ps(v_eqr1, _mm_mul_ps(_mm_sub_ps(v_g1, v_b1), v_diff1)); + __m128 v_eqg0 = _mm_cmpeq_ps(v_max0, v_g0); + __m128 v_eqg1 = _mm_cmpeq_ps(v_max1, v_g1); + v_h0 = _mm_or_ps(v_h0, _mm_and_ps(_mm_andnot_ps(v_eqr0, v_eqg0), _mm_add_ps(_mm_mul_ps(_mm_sub_ps(v_b0, v_r0), v_diff0), _mm_set1_ps(120.f)))); + v_h1 = _mm_or_ps(v_h1, _mm_and_ps(_mm_andnot_ps(v_eqr1, v_eqg1), _mm_add_ps(_mm_mul_ps(_mm_sub_ps(v_b1, v_r1), v_diff1), _mm_set1_ps(120.f)))); + v_h0 = _mm_or_ps(v_h0, _mm_andnot_ps(_mm_or_ps(v_eqr0, v_eqg0), _mm_add_ps(_mm_mul_ps(_mm_sub_ps(v_r0, v_g0), v_diff0), _mm_set1_ps(240.f)))); + v_h1 = _mm_or_ps(v_h1, _mm_andnot_ps(_mm_or_ps(v_eqr1, v_eqg1), _mm_add_ps(_mm_mul_ps(_mm_sub_ps(v_r1, v_g1), v_diff1), _mm_set1_ps(240.f)))); + v_h0 = _mm_add_ps(v_h0, _mm_and_ps(_mm_cmplt_ps(v_h0, _mm_setzero_ps()), _mm_set1_ps(360.f))); + v_h1 = _mm_add_ps(v_h1, _mm_and_ps(_mm_cmplt_ps(v_h1, _mm_setzero_ps()), _mm_set1_ps(360.f))); + v_h0 = _mm_mul_ps(v_h0, _mm_set1_ps(hscale)); + v_h1 = _mm_mul_ps(v_h1, _mm_set1_ps(hscale)); + + v_b0 = _mm_and_ps(v_gteps0, v_h0); + v_b1 = _mm_and_ps(v_gteps1, v_h1); + v_g0 = v_l0; + v_g1 = v_l1; + v_r0 = _mm_and_ps(v_gteps0, v_s0); + v_r1 = _mm_and_ps(v_gteps1, v_s1); + } + #endif void operator()(const float* src, float* dst, int n) const { - int i, bidx = blueIdx, scn = srccn; - float hscale = hrange*(1.f/360.f); + int i = 0, bidx = blueIdx, scn = srccn; n *= 3; - for( i = 0; i < n; i += 3, src += scn ) + #if CV_SSE2 + if (haveSIMD) + { + for( ; i <= n - 24; i += 24, src += scn * 8 ) + { + __m128 v_b0 = _mm_loadu_ps(src + 0); + __m128 v_b1 = _mm_loadu_ps(src + 4); + __m128 v_g0 = _mm_loadu_ps(src + 8); + __m128 v_g1 = _mm_loadu_ps(src + 12); + __m128 v_r0 = _mm_loadu_ps(src + 16); + __m128 v_r1 = _mm_loadu_ps(src + 20); + + if (scn == 3) + { + _mm_deinterleave_ps(v_b0, v_b1, v_g0, v_g1, v_r0, v_r1); + } + else + { + __m128 v_a0 = _mm_loadu_ps(src + 24); + __m128 v_a1 = _mm_loadu_ps(src + 28); + _mm_deinterleave_ps(v_b0, v_b1, v_g0, v_g1, v_r0, v_r1, v_a0, v_a1); + } + + if (bidx) + { + __m128 v_tmp0 = v_b0; + __m128 v_tmp1 = v_b1; + v_b0 = v_r0; + v_b1 = v_r1; + v_r0 = v_tmp0; + v_r1 = v_tmp1; + } + + process(v_b0, v_b1, v_g0, v_g1, v_r0, v_r1); + + _mm_interleave_ps(v_b0, v_b1, v_g0, v_g1, v_r0, v_r1); + + _mm_storeu_ps(dst + i + 0, v_b0); + _mm_storeu_ps(dst + i + 4, v_b1); + _mm_storeu_ps(dst + i + 8, v_g0); + _mm_storeu_ps(dst + i + 12, v_g1); + _mm_storeu_ps(dst + i + 16, v_r0); + _mm_storeu_ps(dst + i + 20, v_r1); + } + } + #endif + + for( ; i < n; i += 3, src += scn ) { float b = src[bidx], g = src[1], r = src[bidx^2]; float h = 0.f, s = 0.f, l; @@ -4770,7 +4874,10 @@ struct RGB2HLS_f } int srccn, blueIdx; - float hrange; + float hscale; + #if CV_SSE2 + bool haveSIMD; + #endif }; From 7c266ffffedc47cbc19aed62dfdd6851c31b8361 Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Tue, 8 Nov 2016 14:04:17 +0300 Subject: [PATCH 072/409] Add support of vector of vectors serialization to FileStorage --- .../core/include/opencv2/core/persistence.hpp | 12 ++++- modules/core/test/test_io.cpp | 46 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/persistence.hpp b/modules/core/include/opencv2/core/persistence.hpp index 66c963132b..3f18d54a39 100644 --- a/modules/core/include/opencv2/core/persistence.hpp +++ b/modules/core/include/opencv2/core/persistence.hpp @@ -958,7 +958,6 @@ void write( FileStorage& fs, const std::vector<_Tp>& vec ) w(vec); } - template static inline void write(FileStorage& fs, const String& name, const Point_<_Tp>& pt ) { @@ -1022,6 +1021,17 @@ void write( FileStorage& fs, const String& name, const std::vector<_Tp>& vec ) write(fs, vec); } +template static inline +void write( FileStorage& fs, const String& name, const std::vector< std::vector<_Tp> >& vec ) +{ + cv::internal::WriteStructContext ws(fs, name, FileNode::SEQ); + for(size_t i = 0; i < vec.size(); i++) + { + cv::internal::WriteStructContext ws_(fs, name, FileNode::SEQ+(DataType<_Tp>::fmt != 0 ? FileNode::FLOW : 0)); + write(fs, vec[i]); + } +} + //! @} FileStorage //! @relates cv::FileNode diff --git a/modules/core/test/test_io.cpp b/modules/core/test/test_io.cpp index 34915cc6ef..eb02a7b23d 100644 --- a/modules/core/test/test_io.cpp +++ b/modules/core/test/test_io.cpp @@ -950,3 +950,49 @@ TEST(Core_InputOutput, filestorage_utf8_bom) fs.release(); }); } + +TEST(Core_InputOutput, filestorage_vec_vec_io) +{ + std::vector > outputMats(3); + for(size_t i = 0; i < outputMats.size(); i++) + { + outputMats[i].resize(i+1); + for(size_t j = 0; j < outputMats[i].size(); j++) + { + outputMats[i][j] = Mat::eye((int)i + 1, (int)i + 1, CV_8U); + } + } + + String fileName = "vec_test."; + + std::vector formats; + formats.push_back("xml"); + formats.push_back("yml"); + formats.push_back("json"); + + for(size_t i = 0; i < formats.size(); i++) + { + FileStorage writer(fileName + formats[i], FileStorage::WRITE); + writer << "vecVecMat" << outputMats; + writer.release(); + + FileStorage reader(fileName + formats[i], FileStorage::READ); + std::vector > testMats; + reader["vecVecMat"] >> testMats; + + ASSERT_EQ(testMats.size(), testMats.size()); + + for(size_t j = 0; j < testMats.size(); j++) + { + ASSERT_EQ(testMats[j].size(), outputMats[j].size()); + + for(size_t k = 0; k < testMats[j].size(); k++) + { + ASSERT_TRUE(norm(outputMats[j][k] - testMats[j][k], NORM_INF) == 0); + } + } + + reader.release(); + remove((fileName + formats[i]).c_str()); + } +} From e6f2729c7a73d0e5365d047de00e3efc3bef420a Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Wed, 16 Nov 2016 13:36:37 +0300 Subject: [PATCH 073/409] Replaced magic constant in large image handling check --- 3rdparty/openvx/include/openvx_hal.hpp | 43 +++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/include/openvx_hal.hpp index f0aa3d5d3b..b027ff0e11 100644 --- a/3rdparty/openvx/include/openvx_hal.hpp +++ b/3rdparty/openvx/include/openvx_hal.hpp @@ -15,6 +15,10 @@ #include #include +#ifndef VX_VENDOR_ID +#define VX_VENDOR_ID VX_ID_DEFAULT +#endif + #if VX_VERSION == VX_VERSION_1_0 #define VX_MEMORY_TYPE_HOST VX_IMPORT_TYPE_HOST @@ -66,6 +70,17 @@ struct Tick }; #endif +inline bool dimTooBig(int size) +{ + if (VX_VENDOR_ID == VX_ID_KHRONOS || VX_VENDOR_ID == VX_ID_DEFAULT) + { + //OpenVX use uint32_t for image addressing + return ((unsigned)size > (UINT_MAX / VX_SCALE_UNITY)); + } + else + return false; +} + //================================================================================================== // One more OpenVX C++ binding :-) // ... @@ -349,7 +364,7 @@ inline void setConstantBorder(vx_border_t &border, vx_uint8 val) template \ inline int ovx_hal_##hal_func(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h) \ { \ - if(w >= 4194304 || h >= 4194304) \ + if(dimTooBig(w) || dimTooBig(h)) \ return CV_HAL_ERROR_NOT_IMPLEMENTED; \ try \ { \ @@ -379,7 +394,7 @@ OVX_BINARY_OP(xor, {vxErr::check(vxuXor(ctx->ctx, ia.img, ib.img, ic.img));}) template inline int ovx_hal_mul(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h, double scale) { - if (w >= 4194304 || h >= 4194304) + if(dimTooBig(w) || dimTooBig(h)) return CV_HAL_ERROR_NOT_IMPLEMENTED; #ifdef _MSC_VER const float MAGIC_SCALE = 0x0.01010102; @@ -418,7 +433,7 @@ inline int ovx_hal_mul(const T *a, size_t astep, const T *b, size_t bstep, T *c, inline int ovx_hal_not(const uchar *a, size_t astep, uchar *c, size_t cstep, int w, int h) { - if (w >= 4194304 || h >= 4194304) + if(dimTooBig(w) || dimTooBig(h)) return CV_HAL_ERROR_NOT_IMPLEMENTED; try { @@ -437,7 +452,7 @@ inline int ovx_hal_not(const uchar *a, size_t astep, uchar *c, size_t cstep, int inline int ovx_hal_merge8u(const uchar **src_data, uchar *dst_data, int len, int cn) { - if (len >= 4194304) + if(dimTooBig(len)) return CV_HAL_ERROR_NOT_IMPLEMENTED; if (cn != 3 && cn != 4) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -462,7 +477,7 @@ inline int ovx_hal_merge8u(const uchar **src_data, uchar *dst_data, int len, int inline int ovx_hal_resize(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, double inv_scale_x, double inv_scale_y, int interpolation) { - if (aw >= 4194304 || ah >= 4194304 || bw >= 4194304 || bh >= 4194304) + if(dimTooBig(aw) || dimTooBig(ah) || dimTooBig(bw) || dimTooBig(bh)) return CV_HAL_ERROR_NOT_IMPLEMENTED; try { @@ -503,7 +518,7 @@ inline int ovx_hal_resize(int atype, const uchar *a, size_t astep, int aw, int a inline int ovx_hal_warpAffine(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[6], int interpolation, int borderType, const double borderValue[4]) { - if (aw >= 4194304 || ah >= 4194304 || bw >= 4194304 || bh >= 4194304) + if(dimTooBig(aw) || dimTooBig(ah) || dimTooBig(bw) || dimTooBig(bh)) return CV_HAL_ERROR_NOT_IMPLEMENTED; try { @@ -562,7 +577,7 @@ inline int ovx_hal_warpAffine(int atype, const uchar *a, size_t astep, int aw, i inline int ovx_hal_warpPerspectve(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[9], int interpolation, int borderType, const double borderValue[4]) { - if (aw >= 4194304 || ah >= 4194304 || bw >= 4194304 || bh >= 4194304) + if(dimTooBig(aw) || dimTooBig(ah) || dimTooBig(bw) || dimTooBig(bh)) return CV_HAL_ERROR_NOT_IMPLEMENTED; try { @@ -707,7 +722,7 @@ inline int ovx_hal_filterFree(cvhalFilter2D *filter_context) inline int ovx_hal_filter(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int , int , int , int ) { - if (w >= 4194304 || h >= 4194304) + if(dimTooBig(w) || dimTooBig(h)) return CV_HAL_ERROR_NOT_IMPLEMENTED; try { @@ -929,7 +944,7 @@ inline int ovx_hal_morphFree(cvhalFilter2D *filter_context) inline int ovx_hal_morph(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int , int , int , int , int , int , int , int ) { - if (w >= 4194304 || h >= 4194304) + if(dimTooBig(w) || dimTooBig(h)) return CV_HAL_ERROR_NOT_IMPLEMENTED; try { @@ -961,7 +976,7 @@ inline int ovx_hal_morph(cvhalFilter2D *filter_context, uchar *a, size_t astep, inline int ovx_hal_cvtBGRtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int depth, int acn, int bcn, bool swapBlue) { - if (w >= 4194304 || h >= 4194304) + if(dimTooBig(w) || dimTooBig(h)) return CV_HAL_ERROR_NOT_IMPLEMENTED; if (depth != CV_8U || swapBlue || acn == bcn || (acn != 3 && acn != 4) || (bcn != 3 && bcn != 4)) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -986,7 +1001,7 @@ inline int ovx_hal_cvtBGRtoBGR(const uchar * a, size_t astep, uchar * b, size_t inline int ovx_hal_cvtTwoPlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx) { - if (w >= 4194304 || h >= 4194304) + if(dimTooBig(w) || dimTooBig(h)) return CV_HAL_ERROR_NOT_IMPLEMENTED; if (!swapBlue || (bcn != 3 && bcn != 4)) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -1015,7 +1030,7 @@ inline int ovx_hal_cvtTwoPlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, inline int ovx_hal_cvtThreePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx) { - if (w >= 4194304 || h >= 4194304) + if(dimTooBig(w) || dimTooBig(h)) return CV_HAL_ERROR_NOT_IMPLEMENTED; if (!swapBlue || (bcn != 3 && bcn != 4) || uIdx || (size_t)w / 2 != astep - (size_t)w / 2) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -1044,7 +1059,7 @@ inline int ovx_hal_cvtThreePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * inline int ovx_hal_cvtBGRtoThreePlaneYUV(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int acn, bool swapBlue, int uIdx) { - if (w >= 4194304 || h >= 4194304) + if(dimTooBig(w) || dimTooBig(h)) return CV_HAL_ERROR_NOT_IMPLEMENTED; if (!swapBlue || (acn != 3 && acn != 4) || uIdx || (size_t)w / 2 != bstep - (size_t)w / 2) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -1069,7 +1084,7 @@ inline int ovx_hal_cvtBGRtoThreePlaneYUV(const uchar * a, size_t astep, uchar * inline int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx, int ycn) { - if (w >= 4194304 || h >= 4194304) + if(dimTooBig(w) || dimTooBig(h)) return CV_HAL_ERROR_NOT_IMPLEMENTED; if (!swapBlue || (bcn != 3 && bcn != 4) || uIdx) return CV_HAL_ERROR_NOT_IMPLEMENTED; From 40aa8aa8621ebcedfc1b6e047fe0e25f54bd90ff Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 10 Nov 2016 15:55:47 +0300 Subject: [PATCH 074/409] ffmpeg: check return value --- modules/videoio/src/cap_ffmpeg_impl.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index e7ece78a01..daa235e588 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -2455,11 +2455,14 @@ bool OutputMediaStream_FFMPEG::open(const char* fileName, int width, int height, } // write the stream header, if any + int header_err = #if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(53, 2, 0) av_write_header(oc_); #else avformat_write_header(oc_, NULL); #endif + if (header_err != 0) + return false; return true; } From 30cdcfa55497fc97693d46d04447475194aba78d Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 17 Nov 2016 18:41:39 +0300 Subject: [PATCH 075/409] warnings --- 3rdparty/libtiff/CMakeLists.txt | 4 +++- 3rdparty/openexr/CMakeLists.txt | 5 +++-- modules/calib3d/src/homography_decomp.cpp | 2 +- modules/core/include/opencv2/core/hal/intrin_cpp.hpp | 4 +++- modules/highgui/src/window_gtk.cpp | 2 +- modules/superres/src/optical_flow.cpp | 4 ++++ 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/3rdparty/libtiff/CMakeLists.txt b/3rdparty/libtiff/CMakeLists.txt index 7a5b34c611..93bcf7ee6a 100644 --- a/3rdparty/libtiff/CMakeLists.txt +++ b/3rdparty/libtiff/CMakeLists.txt @@ -86,7 +86,9 @@ else() endif() ocv_warnings_disable(CMAKE_C_FLAGS -Wno-unused-but-set-variable -Wmissing-prototypes -Wmissing-declarations -Wundef -Wunused -Wsign-compare - -Wcast-align -Wshadow -Wno-maybe-uninitialized -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast) + -Wcast-align -Wshadow -Wno-maybe-uninitialized -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast + -Wmisleading-indentation +) ocv_warnings_disable(CMAKE_C_FLAGS -Wunused-parameter) # clang ocv_warnings_disable(CMAKE_CXX_FLAGS -Wmissing-declarations -Wunused-parameter) ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4018 /wd4100 /wd4127 /wd4311 /wd4701 /wd4706) # vs2005 diff --git a/3rdparty/openexr/CMakeLists.txt b/3rdparty/openexr/CMakeLists.txt index dc1d3935f6..65d43ae71a 100644 --- a/3rdparty/openexr/CMakeLists.txt +++ b/3rdparty/openexr/CMakeLists.txt @@ -39,8 +39,9 @@ endif() source_group("Include" FILES ${lib_hdrs} ) source_group("Src" FILES ${lib_srcs}) -ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow -Wunused -Wsign-compare -Wundef -Wmissing-declarations -Wuninitialized -Wswitch -Wparentheses -Warray-bounds -Wextra) -ocv_warnings_disable(CMAKE_CXX_FLAGS -Wdeprecated-declarations) +ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow -Wunused -Wsign-compare -Wundef -Wmissing-declarations -Wuninitialized -Wswitch -Wparentheses -Warray-bounds -Wextra + -Wdeprecated-declarations -Wmisleading-indentation +) ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4018 /wd4099 /wd4100 /wd4101 /wd4127 /wd4189 /wd4245 /wd4305 /wd4389 /wd4512 /wd4701 /wd4702 /wd4706 /wd4800) # vs2005 ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4334) # vs2005 Win64 ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4244) # vs2008 diff --git a/modules/calib3d/src/homography_decomp.cpp b/modules/calib3d/src/homography_decomp.cpp index 1a642612f0..9007d0a4b5 100644 --- a/modules/calib3d/src/homography_decomp.cpp +++ b/modules/calib3d/src/homography_decomp.cpp @@ -447,7 +447,7 @@ int decomposeHomographyMat(InputArray _H, Mat K = _K.getMat().reshape(1, 3); CV_Assert(K.cols == 3 && K.rows == 3); - auto_ptr hdecomp(new HomographyDecompInria); + cv::Ptr hdecomp(new HomographyDecompInria); vector motions; hdecomp->decomposeHomography(H, K, motions); diff --git a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp index 8e8b691559..2f8d027b7e 100644 --- a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp @@ -455,8 +455,10 @@ template inline v_reg<_Tp, n> operator ~ (const v_reg<_Tp, { v_reg<_Tp, n> c; for( int i = 0; i < n; i++ ) + { c.s[i] = V_TypeTraits<_Tp>::reinterpret_from_int(~V_TypeTraits<_Tp>::reinterpret_int(a.s[i])); - return c; + } + return c; } //! @brief Helper macro diff --git a/modules/highgui/src/window_gtk.cpp b/modules/highgui/src/window_gtk.cpp index 527335e012..8ead11d6ee 100644 --- a/modules/highgui/src/window_gtk.cpp +++ b/modules/highgui/src/window_gtk.cpp @@ -1983,7 +1983,7 @@ static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_da break; #endif //GTK_VERSION3_4 case GDK_SCROLL_LEFT: cv_event = CV_EVENT_MOUSEHWHEEL; - case GDK_SCROLL_UP: flags |= ((-(int)1 << 16)); + case GDK_SCROLL_UP: flags |= ~0xffff; break; case GDK_SCROLL_RIGHT: cv_event = CV_EVENT_MOUSEHWHEEL; case GDK_SCROLL_DOWN: flags |= (((int)1 << 16)); diff --git a/modules/superres/src/optical_flow.cpp b/modules/superres/src/optical_flow.cpp index b5d1407eee..89a67899b4 100644 --- a/modules/superres/src/optical_flow.cpp +++ b/modules/superres/src/optical_flow.cpp @@ -65,7 +65,9 @@ namespace virtual void impl(InputArray input0, InputArray input1, OutputArray dst) = 0; private: +#ifdef HAVE_OPENCL bool ocl_calc(InputArray frame0, InputArray frame1, OutputArray flow1, OutputArray flow2); +#endif int work_type_; @@ -85,6 +87,7 @@ namespace { } +#ifdef HAVE_OPENCL bool CpuOpticalFlow::ocl_calc(InputArray _frame0, InputArray _frame1, OutputArray _flow1, OutputArray _flow2) { UMat frame0 = arrGetUMat(_frame0, ubuf_[0]); @@ -116,6 +119,7 @@ namespace return true; } +#endif void CpuOpticalFlow::calc(InputArray _frame0, InputArray _frame1, OutputArray _flow1, OutputArray _flow2) { From 14d2046f807e6354617f6464922833b44022e70b Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 8 Nov 2016 18:00:03 +0300 Subject: [PATCH 076/409] cmake: ccache --- CMakeLists.txt | 2 ++ cmake/OpenCVCompilerOptions.cmake | 30 +++++++++++++++++++++++++++++ cmake/OpenCVDetectCXXCompiler.cmake | 4 ---- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6395b1fd18..8337ec9fb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,7 @@ OCV_OPTION(INSTALL_TESTS "Install accuracy and performance test binar # OpenCV build options # =================================================== +OCV_OPTION(ENABLE_CCACHE "Use ccache" (UNIX AND NOT IOS AND (CMAKE_GENERATOR MATCHES "Makefile" OR CMAKE_GENERATOR MATCHES "Ninja")) ) OCV_OPTION(ENABLE_PRECOMPILED_HEADERS "Use precompiled headers" ON IF (NOT IOS AND NOT CMAKE_CROSSCOMPILING) ) OCV_OPTION(ENABLE_SOLUTION_FOLDERS "Solution folder in Visual Studio or in other IDEs" (MSVC_IDE OR CMAKE_GENERATOR MATCHES Xcode) ) OCV_OPTION(ENABLE_PROFILING "Enable profiling in the GCC compiler (Add flags: -g -pg)" OFF IF CMAKE_COMPILER_IS_GNUCXX ) @@ -888,6 +889,7 @@ else() status(" Linker flags (Release):" ${CMAKE_SHARED_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS_RELEASE}) status(" Linker flags (Debug):" ${CMAKE_SHARED_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}) endif() +status(" ccache:" CMAKE_COMPILER_IS_CCACHE THEN YES ELSE NO) status(" Precompiled headers:" PCHSupport_FOUND AND ENABLE_PRECOMPILED_HEADERS THEN YES ELSE NO) # ========================== Dependencies ============================ diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index 460a7fd88f..c433d988fd 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -1,3 +1,33 @@ +if(ENABLE_CCACHE AND NOT CMAKE_COMPILER_IS_CCACHE) + # This works fine with Unix Makefiles and Ninja generators + find_host_program(CCACHE_PROGRAM ccache) + if(CCACHE_PROGRAM) + message(STATUS "Looking for ccache - found (${CCACHE_PROGRAM})") + get_property(__OLD_RULE_LAUNCH_COMPILE GLOBAL PROPERTY RULE_LAUNCH_COMPILE) + if(__OLD_RULE_LAUNCH_COMPILE) + message(STATUS "Can't replace CMake compiler launcher") + else() + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") + # NOTE: Actually this check doesn't work as expected. + # "RULE_LAUNCH_COMPILE" is ignored by CMake during try_compile() step. + # ocv_check_compiler_flag(CXX "" IS_CCACHE_WORKS) + set(IS_CCACHE_WORKS 1) + if(IS_CCACHE_WORKS) + set(CMAKE_COMPILER_IS_CCACHE 1) + else() + message(STATUS "Unable to compile program with enabled ccache, reverting...") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${__OLD_RULE_LAUNCH_COMPILE}") + endif() + else() + message(STATUS "Looking for ccache - not found") + endif() + endif() +endif() + +if((CMAKE_COMPILER_IS_CLANGCXX OR CMAKE_COMPILER_IS_CLANGCC OR CMAKE_COMPILER_IS_CCACHE) AND NOT CMAKE_GENERATOR MATCHES "Xcode") + set(ENABLE_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE) +endif() + if(MINGW OR (X86 AND UNIX AND NOT APPLE)) # mingw compiler is known to produce unstable SSE code with -O3 hence we are trying to use -O2 instead if(CMAKE_COMPILER_IS_GNUCXX) diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake index 5cf4c856bf..ce669bbc85 100644 --- a/cmake/OpenCVDetectCXXCompiler.cmake +++ b/cmake/OpenCVDetectCXXCompiler.cmake @@ -17,10 +17,6 @@ if("${CMAKE_CXX_COMPILER};${CMAKE_C_COMPILER}" MATCHES "ccache") set(CMAKE_COMPILER_IS_CCACHE 1) endif() -if((CMAKE_COMPILER_IS_CLANGCXX OR CMAKE_COMPILER_IS_CLANGCC OR CMAKE_COMPILER_IS_CCACHE) AND NOT CMAKE_GENERATOR MATCHES "Xcode") - set(ENABLE_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE) -endif() - # ---------------------------------------------------------------------------- # Detect Intel ICC compiler -- for -fPIC in 3rdparty ( UNIX ONLY ): # see include/opencv/cxtypes.h file for related ICC & CV_ICC defines. From dac37a0bc93e7b77f5055ef66ad531d833100c3e Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 17 Nov 2016 16:36:39 +0300 Subject: [PATCH 077/409] features2d: update rotation invariance test Don't process keypoints near border, because after rotation border points have contrast gradients with rotation border fill. --- .../test_rotation_and_scale_invariance.cpp | 78 ++++++++++++------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/modules/features2d/test/test_rotation_and_scale_invariance.cpp b/modules/features2d/test/test_rotation_and_scale_invariance.cpp index 93ded0b6af..072adc40df 100644 --- a/modules/features2d/test/test_rotation_and_scale_invariance.cpp +++ b/modules/features2d/test/test_rotation_and_scale_invariance.cpp @@ -47,7 +47,7 @@ using namespace cv; const string IMAGE_TSUKUBA = "/features2d/tsukuba.png"; const string IMAGE_BIKES = "/detectors_descriptors_evaluation/images_datasets/bikes/img1.png"; -#define SHOW_DEBUG_LOG 0 +#define SHOW_DEBUG_LOG 1 static Mat generateHomography(float angle) @@ -63,7 +63,7 @@ Mat generateHomography(float angle) } static -Mat rotateImage(const Mat& srcImage, float angle, Mat& dstImage, Mat& dstMask) +Mat rotateImage(const Mat& srcImage, const Mat& srcMask, float angle, Mat& dstImage, Mat& dstMask) { // angle - rotation around Oz in degrees float diag = std::sqrt(static_cast(srcImage.cols * srcImage.cols + srcImage.rows * srcImage.rows)); @@ -75,8 +75,6 @@ Mat rotateImage(const Mat& srcImage, float angle, Mat& dstImage, Mat& dstMask) RDShift.at(1,2) = diag/2; Size sz(cvRound(diag), cvRound(diag)); - Mat srcMask(srcImage.size(), CV_8UC1, Scalar(255)); - Mat H = RDShift * generateHomography(angle) * LUShift; warpPerspective(srcImage, dstImage, H, sz); warpPerspective(srcMask, dstMask, H, sz); @@ -212,16 +210,22 @@ protected: ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); return; } + std::cout << "Image: " << image0.size() << std::endl; + + const int borderSize = 16; + Mat mask0(image0.size(), CV_8UC1, Scalar(0)); + mask0(Rect(borderSize, borderSize, mask0.cols - 2*borderSize, mask0.rows - 2*borderSize)).setTo(Scalar(255)); vector keypoints0; - featureDetector->detect(image0, keypoints0); + featureDetector->detect(image0, keypoints0, mask0); + std::cout << "Intial keypoints: " << keypoints0.size() << std::endl; if(keypoints0.size() < 15) CV_Error(Error::StsAssert, "Detector gives too few points in a test image\n"); const int maxAngle = 360, angleStep = 15; for(int angle = 0; angle < maxAngle; angle += angleStep) { - Mat H = rotateImage(image0, static_cast(angle), image1, mask1); + Mat H = rotateImage(image0, mask0, static_cast(angle), image1, mask1); vector keypoints1; featureDetector->detect(image1, keypoints1, mask1); @@ -264,10 +268,9 @@ protected: float keyPointMatchesRatio = static_cast(keyPointMatchesCount) / keypoints0.size(); if(keyPointMatchesRatio < minKeyPointMatchesRatio) { - ts->printf(cvtest::TS::LOG, "Incorrect keyPointMatchesRatio: curr = %f, min = %f.\n", - keyPointMatchesRatio, minKeyPointMatchesRatio); + ts->printf(cvtest::TS::LOG, "Angle: %f: Incorrect keyPointMatchesRatio: curr = %f, min = %f (matched=%d total=%d - %d).\n", + (float)angle, keyPointMatchesRatio, minKeyPointMatchesRatio, (int)keyPointMatchesCount, (int)keypoints0.size(), (int)keypoints1.size()); ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); - return; } if(keyPointMatchesCount) @@ -275,15 +278,18 @@ protected: float angleInliersRatio = static_cast(angleInliersCount) / keyPointMatchesCount; if(angleInliersRatio < minAngleInliersRatio) { - ts->printf(cvtest::TS::LOG, "Incorrect angleInliersRatio: curr = %f, min = %f.\n", - angleInliersRatio, minAngleInliersRatio); + ts->printf(cvtest::TS::LOG, "Angle: %f: Incorrect angleInliersRatio: curr = %f, min = %f.\n", + (float)angle, angleInliersRatio, minAngleInliersRatio); ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); - return; } } #if SHOW_DEBUG_LOG - std::cout << "keyPointMatchesRatio - " << keyPointMatchesRatio - << " - angleInliersRatio " << static_cast(angleInliersCount) / keyPointMatchesCount << std::endl; + std::cout + << "angle = " << angle + << ", keypoints = " << keypoints1.size() + << ", keyPointMatchesRatio = " << keyPointMatchesRatio + << ", angleInliersRatio = " << (keyPointMatchesCount ? (static_cast(angleInliersCount) / keyPointMatchesCount) : 0) + << std::endl; #endif } ts->set_failed_test_info( cvtest::TS::OK ); @@ -324,10 +330,16 @@ protected: ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); return; } + std::cout << "Image: " << image0.size() << std::endl; + + const int borderSize = 16; + Mat mask0(image0.size(), CV_8UC1, Scalar(0)); + mask0(Rect(borderSize, borderSize, mask0.cols - 2*borderSize, mask0.rows - 2*borderSize)).setTo(Scalar(255)); vector keypoints0; Mat descriptors0; - featureDetector->detect(image0, keypoints0); + featureDetector->detect(image0, keypoints0, mask0); + std::cout << "Intial keypoints: " << keypoints0.size() << std::endl; if(keypoints0.size() < 15) CV_Error(Error::StsAssert, "Detector gives too few points in a test image\n"); descriptorExtractor->compute(image0, keypoints0, descriptors0); @@ -338,7 +350,7 @@ protected: const int maxAngle = 360, angleStep = 15; for(int angle = 0; angle < maxAngle; angle += angleStep) { - Mat H = rotateImage(image0, static_cast(angle), image1, mask1); + Mat H = rotateImage(image0, mask0, static_cast(angle), image1, mask1); vector keypoints1; rotateKeyPoints(keypoints0, H, static_cast(angle), keypoints1); @@ -369,7 +381,11 @@ protected: return; } #if SHOW_DEBUG_LOG - std::cout << "descInliersRatio " << static_cast(descInliersCount) / keypoints0.size() << std::endl; + std::cout + << "angle = " << angle + << ", keypoints = " << keypoints1.size() + << ", descInliersRatio = " << static_cast(descInliersCount) / keypoints0.size() + << std::endl; #endif } ts->set_failed_test_info( cvtest::TS::OK ); @@ -485,8 +501,11 @@ protected: } } #if SHOW_DEBUG_LOG - std::cout << "keyPointMatchesRatio - " << keyPointMatchesRatio - << " - scaleInliersRatio " << static_cast(scaleInliersCount) / keyPointMatchesCount << std::endl; + std::cout + << "scale = " << scale + << ", keyPointMatchesRatio = " << keyPointMatchesRatio + << ", scaleInliersRatio = " << (keyPointMatchesCount ? static_cast(scaleInliersCount) / keyPointMatchesCount : 0) + << std::endl; #endif } ts->set_failed_test_info( cvtest::TS::OK ); @@ -573,7 +592,10 @@ protected: return; } #if SHOW_DEBUG_LOG - std::cout << "descInliersRatio " << static_cast(descInliersCount) / keypoints0.size() << std::endl; + std::cout + << "scale = " << scale + << ", descInliersRatio = " << static_cast(descInliersCount) / keypoints0.size() + << std::endl; #endif } ts->set_failed_test_info( cvtest::TS::OK ); @@ -595,7 +617,7 @@ protected: TEST(Features2d_RotationInvariance_Detector_BRISK, regression) { DetectorRotationInvarianceTest test(BRISK::create(), - 0.32f, + 0.45f, 0.76f); test.safe_run(); } @@ -603,7 +625,7 @@ TEST(Features2d_RotationInvariance_Detector_BRISK, regression) TEST(Features2d_RotationInvariance_Detector_ORB, regression) { DetectorRotationInvarianceTest test(ORB::create(), - 0.47f, + 0.5f, 0.76f); test.safe_run(); } @@ -657,13 +679,11 @@ TEST(Features2d_ScaleInvariance_Detector_AKAZE, regression) test.safe_run(); } -//TEST(Features2d_ScaleInvariance_Detector_ORB, regression) -//{ -// DetectorScaleInvarianceTest test(Algorithm::create("Feature2D.ORB"), -// 0.22f, -// 0.83f); -// test.safe_run(); -//} +TEST(Features2d_ScaleInvariance_Detector_ORB, regression) +{ + DetectorScaleInvarianceTest test(ORB::create(), 0.08f, 0.49f); + test.safe_run(); +} /* * Descriptor's scale invariance check From 39d7ecc93b0cf8d596f23fc293341d86b51e87ea Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 17 Nov 2016 16:58:12 +0300 Subject: [PATCH 078/409] perf: disabled checks for magic numbers Results are not bit-exact --- modules/features2d/perf/opencl/perf_orb.cpp | 21 ++++++++------------- modules/features2d/perf/perf_orb.cpp | 17 ++++++++++------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/modules/features2d/perf/opencl/perf_orb.cpp b/modules/features2d/perf/opencl/perf_orb.cpp index a7d96f12a5..5d9aa2fec1 100644 --- a/modules/features2d/perf/opencl/perf_orb.cpp +++ b/modules/features2d/perf/opencl/perf_orb.cpp @@ -27,8 +27,8 @@ OCL_PERF_TEST_P(ORBFixture, ORB_Detect, ORB_IMAGES) OCL_TEST_CYCLE() detector->detect(frame, points, mask); - std::sort(points.begin(), points.end(), comparators::KeypointGreater()); - SANITY_CHECK_KEYPOINTS(points, 1e-5); + EXPECT_GT(points.size(), 20u); + SANITY_CHECK_NOTHING(); } OCL_PERF_TEST_P(ORBFixture, ORB_Extract, ORB_IMAGES) @@ -47,13 +47,14 @@ OCL_PERF_TEST_P(ORBFixture, ORB_Extract, ORB_IMAGES) Ptr detector = ORB::create(1500, 1.3f, 1); vector points; detector->detect(frame, points, mask); - std::sort(points.begin(), points.end(), comparators::KeypointGreater()); + EXPECT_GT(points.size(), 20u); UMat descriptors; OCL_TEST_CYCLE() detector->compute(frame, points, descriptors); - SANITY_CHECK(descriptors); + EXPECT_EQ((size_t)descriptors.rows, points.size()); + SANITY_CHECK_NOTHING(); } OCL_PERF_TEST_P(ORBFixture, ORB_Full, ORB_IMAGES) @@ -61,12 +62,6 @@ OCL_PERF_TEST_P(ORBFixture, ORB_Full, ORB_IMAGES) string filename = getDataPath(GetParam()); Mat mframe = imread(filename, IMREAD_GRAYSCALE); - double desc_eps = 1e-6; -#ifdef ANDROID - if (cv::ocl::Device::getDefault().isNVidia()) - desc_eps = 2; -#endif - if (mframe.empty()) FAIL() << "Unable to load source image " << filename; @@ -81,9 +76,9 @@ OCL_PERF_TEST_P(ORBFixture, ORB_Full, ORB_IMAGES) OCL_TEST_CYCLE() detector->detectAndCompute(frame, mask, points, descriptors, false); - ::perf::sort(points, descriptors); - SANITY_CHECK_KEYPOINTS(points, 1e-5); - SANITY_CHECK(descriptors, desc_eps); + EXPECT_GT(points.size(), 20u); + EXPECT_EQ((size_t)descriptors.rows, points.size()); + SANITY_CHECK_NOTHING(); } } // ocl diff --git a/modules/features2d/perf/perf_orb.cpp b/modules/features2d/perf/perf_orb.cpp index 96cfc3eb38..0397125f6e 100644 --- a/modules/features2d/perf/perf_orb.cpp +++ b/modules/features2d/perf/perf_orb.cpp @@ -27,8 +27,9 @@ PERF_TEST_P(orb, detect, testing::Values(ORB_IMAGES)) TEST_CYCLE() detector->detect(frame, points, mask); - sort(points.begin(), points.end(), comparators::KeypointGreater()); - SANITY_CHECK_KEYPOINTS(points, 1e-5); + EXPECT_GT(points.size(), 20u); + + SANITY_CHECK_NOTHING(); } PERF_TEST_P(orb, extract, testing::Values(ORB_IMAGES)) @@ -45,13 +46,15 @@ PERF_TEST_P(orb, extract, testing::Values(ORB_IMAGES)) Ptr detector = ORB::create(1500, 1.3f, 1); vector points; detector->detect(frame, points, mask); - sort(points.begin(), points.end(), comparators::KeypointGreater()); + + EXPECT_GT(points.size(), 20u); Mat descriptors; TEST_CYCLE() detector->compute(frame, points, descriptors); - SANITY_CHECK(descriptors); + EXPECT_EQ((size_t)descriptors.rows, points.size()); + SANITY_CHECK_NOTHING(); } PERF_TEST_P(orb, full, testing::Values(ORB_IMAGES)) @@ -71,7 +74,7 @@ PERF_TEST_P(orb, full, testing::Values(ORB_IMAGES)) TEST_CYCLE() detector->detectAndCompute(frame, mask, points, descriptors, false); - perf::sort(points, descriptors); - SANITY_CHECK_KEYPOINTS(points, 1e-5); - SANITY_CHECK(descriptors); + EXPECT_GT(points.size(), 20u); + EXPECT_EQ((size_t)descriptors.rows, points.size()); + SANITY_CHECK_NOTHING(); } From 21167b1bf1b8471101732073d9b4803b80fe0ce4 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Wed, 31 Aug 2016 23:12:52 +0300 Subject: [PATCH 079/409] Fixed several GCC 5.x warnings --- 3rdparty/libtiff/CMakeLists.txt | 4 ++-- 3rdparty/openexr/CMakeLists.txt | 4 ++-- cmake/OpenCVCompilerOptions.cmake | 1 + modules/calib3d/src/calibinit.cpp | 4 +++- .../calib3d/test/test_cameracalibration.cpp | 2 ++ .../include/opencv2/core/cuda_devptrs.hpp | 23 ++++++++----------- modules/core/include/opencv2/core/gpumat.hpp | 7 +++--- .../core/include/opencv2/core/internal.hpp | 2 +- modules/core/include/opencv2/core/types_c.h | 9 ++++++++ modules/core/test/test_ds.cpp | 2 +- modules/flann/include/opencv2/flann/any.h | 2 +- modules/flann/include/opencv2/flann/flann.hpp | 23 +++++++++++-------- modules/highgui/src/cap_v4l.cpp | 11 ++++----- modules/legacy/test/test_optflow.cpp | 11 +++++---- modules/legacy/test/test_stereomatching.cpp | 2 ++ modules/ocl/src/mssegmentation.cpp | 11 ++------- modules/ocl/test/utility.hpp | 2 +- modules/stitching/src/matchers.cpp | 1 + modules/ts/include/opencv2/ts/gpu_test.hpp | 2 +- modules/ts/include/opencv2/ts/ts_gtest.h | 4 ++-- modules/ts/src/ts_func.cpp | 4 ++-- 21 files changed, 70 insertions(+), 61 deletions(-) diff --git a/3rdparty/libtiff/CMakeLists.txt b/3rdparty/libtiff/CMakeLists.txt index 7d7febac7a..a241dd6f91 100644 --- a/3rdparty/libtiff/CMakeLists.txt +++ b/3rdparty/libtiff/CMakeLists.txt @@ -86,7 +86,7 @@ else() endif() ocv_warnings_disable(CMAKE_C_FLAGS -Wno-unused-but-set-variable -Wmissing-prototypes -Wmissing-declarations -Wundef -Wunused -Wsign-compare - -Wcast-align -Wshadow -Wno-maybe-uninitialized -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast) + -Wcast-align -Wshadow -Wno-maybe-uninitialized -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -Wmisleading-indentation) ocv_warnings_disable(CMAKE_C_FLAGS -Wunused-parameter) # clang ocv_warnings_disable(CMAKE_CXX_FLAGS -Wmissing-declarations -Wunused-parameter) ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4018 /wd4100 /wd4127 /wd4311 /wd4701 /wd4706) # vs2005 @@ -95,7 +95,7 @@ ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4267 /wd4305 /wd4306) # vs2008 Win64 ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4703) # vs2012 ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4456 /wd4457 /wd4312) # vs2015 -ocv_warnings_disable(CMAKE_C_FLAGS /wd4267 /wd4244 /wd4018) +ocv_warnings_disable(CMAKE_C_FLAGS /wd4267 /wd4244 /wd4018 /wd4311 /wd4312) if(UNIX AND (CMAKE_COMPILER_IS_GNUCXX OR CV_ICC)) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") diff --git a/3rdparty/openexr/CMakeLists.txt b/3rdparty/openexr/CMakeLists.txt index 85f750d1d7..dddf3738c4 100644 --- a/3rdparty/openexr/CMakeLists.txt +++ b/3rdparty/openexr/CMakeLists.txt @@ -37,8 +37,8 @@ endif() source_group("Include" FILES ${lib_hdrs} ) source_group("Src" FILES ${lib_srcs}) -ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow -Wunused -Wsign-compare -Wundef -Wmissing-declarations -Wuninitialized -Wswitch -Wparentheses -Warray-bounds -Wextra) -ocv_warnings_disable(CMAKE_CXX_FLAGS -Wdeprecated-declarations) +ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow -Wunused -Wsign-compare -Wundef -Wmissing-declarations -Wuninitialized -Wswitch -Wparentheses -Warray-bounds -Wextra + -Wdeprecated-declarations -Wmisleading-indentation) ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4018 /wd4099 /wd4100 /wd4101 /wd4127 /wd4189 /wd4245 /wd4305 /wd4389 /wd4512 /wd4701 /wd4702 /wd4706 /wd4800) # vs2005 ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4334) # vs2005 Win64 ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4244) # vs2008 diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index 3f7cbf2352..499061e28c 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -95,6 +95,7 @@ if(CMAKE_COMPILER_IS_GNUCXX) add_extra_compiler_option(-Wno-narrowing) add_extra_compiler_option(-Wno-delete-non-virtual-dtor) add_extra_compiler_option(-Wno-unnamed-type-template-args) + add_extra_compiler_option(-Wno-comment) add_extra_compiler_option(-Wno-array-bounds) add_extra_compiler_option(-Wno-aggressive-loop-optimizations) endif() diff --git a/modules/calib3d/src/calibinit.cpp b/modules/calib3d/src/calibinit.cpp index 000ac5435d..a189213403 100644 --- a/modules/calib3d/src/calibinit.cpp +++ b/modules/calib3d/src/calibinit.cpp @@ -1036,13 +1036,15 @@ icvRemoveQuadFromGroup(CvCBQuad **quads, int count, CvCBQuad *q0) q->neighbors[j] = 0; q->count--; for(int k = 0; k < 4; k++ ) + { if( q0->neighbors[k] == q ) { q0->neighbors[k] = 0; q0->count--; break; } - break; + } + break; } } } diff --git a/modules/calib3d/test/test_cameracalibration.cpp b/modules/calib3d/test/test_cameracalibration.cpp index 0cc5701bf1..d358e8fd94 100644 --- a/modules/calib3d/test/test_cameracalibration.cpp +++ b/modules/calib3d/test/test_cameracalibration.cpp @@ -1339,6 +1339,7 @@ bool CV_StereoCalibrationTest::checkPandROI( int test_case_idx, const Mat& M, co undistortPoints(Mat(pts), upts, M, D, R, P ); for( k = 0; k < N*N; k++ ) + { if( upts[k].x < -imgsize.width*eps || upts[k].x > imgsize.width*(1+eps) || upts[k].y < -imgsize.height*eps || upts[k].y > imgsize.height*(1+eps) ) { @@ -1346,6 +1347,7 @@ bool CV_StereoCalibrationTest::checkPandROI( int test_case_idx, const Mat& M, co test_case_idx, pts[k].x, pts[k].y, upts[k].x, upts[k].y); return false; } + } // step 2. check that all the points inside ROI belong to the original source image Mat temp(imgsize, CV_8U), utemp, map1, map2; diff --git a/modules/core/include/opencv2/core/cuda_devptrs.hpp b/modules/core/include/opencv2/core/cuda_devptrs.hpp index 15340455e2..d54a32876f 100644 --- a/modules/core/include/opencv2/core/cuda_devptrs.hpp +++ b/modules/core/include/opencv2/core/cuda_devptrs.hpp @@ -122,27 +122,23 @@ namespace cv typedef PtrStep PtrStepf; typedef PtrStep PtrStepi; - #if defined __GNUC__ - #define __CV_GPU_DEPR_BEFORE__ - #define __CV_GPU_DEPR_AFTER__ __attribute__ ((deprecated)) + #define CV_GPU_DEPRECATED __attribute__ ((deprecated)) #elif defined(__MSVC__) //|| defined(__CUDACC__) #pragma deprecated(DevMem2D_) - #define __CV_GPU_DEPR_BEFORE__ __declspec(deprecated) - #define __CV_GPU_DEPR_AFTER__ + #define CV_GPU_DEPRECATED __declspec(deprecated) #else - #define __CV_GPU_DEPR_BEFORE__ - #define __CV_GPU_DEPR_AFTER__ + #define CV_GPU_DEPRECATED #endif - template struct __CV_GPU_DEPR_BEFORE__ DevMem2D_ : public PtrStepSz + template struct DevMem2D_ : public PtrStepSz { - DevMem2D_() {} - DevMem2D_(int rows_, int cols_, T* data_, size_t step_) : PtrStepSz(rows_, cols_, data_, step_) {} + CV_GPU_DEPRECATED DevMem2D_() {} + CV_GPU_DEPRECATED DevMem2D_(int rows_, int cols_, T* data_, size_t step_) : PtrStepSz(rows_, cols_, data_, step_) {} template - explicit __CV_GPU_DEPR_BEFORE__ DevMem2D_(const DevMem2D_& d) : PtrStepSz(d.rows, d.cols, (T*)d.data, d.step) {} - } __CV_GPU_DEPR_AFTER__ ; + explicit CV_GPU_DEPRECATED DevMem2D_(const DevMem2D_& d) : PtrStepSz(d.rows, d.cols, (T*)d.data, d.step) {} + }; typedef DevMem2D_ DevMem2Db; typedef DevMem2Db DevMem2D; @@ -174,8 +170,7 @@ namespace cv typedef PtrElemStep_ PtrElemStepf; typedef PtrElemStep_ PtrElemStepi; -//#undef __CV_GPU_DEPR_BEFORE__ -//#undef __CV_GPU_DEPR_AFTER__ +//#undef CV_GPU_DEPRECATED namespace device { diff --git a/modules/core/include/opencv2/core/gpumat.hpp b/modules/core/include/opencv2/core/gpumat.hpp index 68647d9bf1..6679ab2a29 100644 --- a/modules/core/include/opencv2/core/gpumat.hpp +++ b/modules/core/include/opencv2/core/gpumat.hpp @@ -277,10 +277,9 @@ namespace cv { namespace gpu template operator PtrStep<_Tp>() const; // Deprecated function - __CV_GPU_DEPR_BEFORE__ template operator DevMem2D_<_Tp>() const __CV_GPU_DEPR_AFTER__; - __CV_GPU_DEPR_BEFORE__ template operator PtrStep_<_Tp>() const __CV_GPU_DEPR_AFTER__; - #undef __CV_GPU_DEPR_BEFORE__ - #undef __CV_GPU_DEPR_AFTER__ + template CV_GPU_DEPRECATED operator DevMem2D_<_Tp>() const; + template CV_GPU_DEPRECATED operator PtrStep_<_Tp>() const; + #undef CV_GPU_DEPRECATED /*! includes several bit-fields: - the magic signature diff --git a/modules/core/include/opencv2/core/internal.hpp b/modules/core/include/opencv2/core/internal.hpp index c2c89613ac..4933654735 100644 --- a/modules/core/include/opencv2/core/internal.hpp +++ b/modules/core/include/opencv2/core/internal.hpp @@ -292,7 +292,7 @@ namespace cv return classname##_info_var; \ } \ \ - static ::cv::AlgorithmInfo& classname##_info_auto = classname##_info(); \ + CV_ATTR_USED static ::cv::AlgorithmInfo& classname##_info_auto = classname##_info(); \ \ ::cv::AlgorithmInfo* classname::info() const \ { \ diff --git a/modules/core/include/opencv2/core/types_c.h b/modules/core/include/opencv2/core/types_c.h index 771715d5df..0e2de71d37 100644 --- a/modules/core/include/opencv2/core/types_c.h +++ b/modules/core/include/opencv2/core/types_c.h @@ -43,6 +43,15 @@ #ifndef __OPENCV_CORE_TYPES_H__ #define __OPENCV_CORE_TYPES_H__ +#if defined(__GNUC__) && !defined(COMPILER_ICC) +# define CV_ATTR_UNUSED __attribute__((unused)) +# define CV_ATTR_USED __attribute__((used)) +#else +# define CV_ATTR_UNUSED +# define CV_ATTR_USED +#endif + + #if !defined _CRT_SECURE_NO_DEPRECATE && defined _MSC_VER # if _MSC_VER > 1300 # define _CRT_SECURE_NO_DEPRECATE /* to avoid multiple Visual Studio 2005 warnings */ diff --git a/modules/core/test/test_ds.cpp b/modules/core/test/test_ds.cpp index ded52583a2..bbe75ad871 100644 --- a/modules/core/test/test_ds.cpp +++ b/modules/core/test/test_ds.cpp @@ -56,7 +56,7 @@ static void cvTsSimpleSeqShiftAndCopy( CvTsSimpleSeq* seq, int from_idx, int to_ (seq->count - from_idx)*elem_size ); } seq->count += to_idx - from_idx; - if( elem && to_idx > from_idx ) + if( elem ) memcpy( seq->array + from_idx*elem_size, elem, (to_idx - from_idx)*elem_size ); } diff --git a/modules/flann/include/opencv2/flann/any.h b/modules/flann/include/opencv2/flann/any.h index 7e3fd797af..cf7707a2fa 100644 --- a/modules/flann/include/opencv2/flann/any.h +++ b/modules/flann/include/opencv2/flann/any.h @@ -80,7 +80,7 @@ struct big_any_policy : typed_base_any_policy { virtual void static_delete(void** x) { - if (* x) delete (* reinterpret_cast(x)); *x = NULL; + if (* x) { delete (* reinterpret_cast(x)); *x = NULL; } } virtual void copy_from_value(void const* src, void** dest) { diff --git a/modules/flann/include/opencv2/flann/flann.hpp b/modules/flann/include/opencv2/flann/flann.hpp index d053488ed4..3b5634c382 100644 --- a/modules/flann/include/opencv2/flann/flann.hpp +++ b/modules/flann/include/opencv2/flann/flann.hpp @@ -225,43 +225,49 @@ int GenericIndex::radiusSearch(const Mat& query, Mat& indices, Mat& di * @deprecated Use GenericIndex class instead */ template -class -#ifndef _MSC_VER - FLANN_DEPRECATED -#endif - Index_ { +class Index_ { public: typedef typename L2::ElementType ElementType; typedef typename L2::ResultType DistanceType; + FLANN_DEPRECATED Index_(const Mat& features, const ::cvflann::IndexParams& params); + FLANN_DEPRECATED ~Index_(); + FLANN_DEPRECATED void knnSearch(const vector& query, vector& indices, vector& dists, int knn, const ::cvflann::SearchParams& params); + FLANN_DEPRECATED void knnSearch(const Mat& queries, Mat& indices, Mat& dists, int knn, const ::cvflann::SearchParams& params); + FLANN_DEPRECATED int radiusSearch(const vector& query, vector& indices, vector& dists, DistanceType radius, const ::cvflann::SearchParams& params); + FLANN_DEPRECATED int radiusSearch(const Mat& query, Mat& indices, Mat& dists, DistanceType radius, const ::cvflann::SearchParams& params); + FLANN_DEPRECATED void save(std::string filename) { if (nnIndex_L1) nnIndex_L1->save(filename); if (nnIndex_L2) nnIndex_L2->save(filename); } + FLANN_DEPRECATED int veclen() const { if (nnIndex_L1) return nnIndex_L1->veclen(); if (nnIndex_L2) return nnIndex_L2->veclen(); } + FLANN_DEPRECATED int size() const { if (nnIndex_L1) return nnIndex_L1->size(); if (nnIndex_L2) return nnIndex_L2->size(); } + FLANN_DEPRECATED ::cvflann::IndexParams getParameters() { if (nnIndex_L1) return nnIndex_L1->getParameters(); @@ -269,7 +275,8 @@ public: } - FLANN_DEPRECATED const ::cvflann::IndexParams* getIndexParameters() + FLANN_DEPRECATED + const ::cvflann::IndexParams* getIndexParameters() { if (nnIndex_L1) return nnIndex_L1->getIndexParameters(); if (nnIndex_L2) return nnIndex_L2->getIndexParameters(); @@ -281,10 +288,6 @@ private: ::cvflann::Index< L1 >* nnIndex_L1; }; -#ifdef _MSC_VER -template -class FLANN_DEPRECATED Index_; -#endif template Index_::Index_(const Mat& dataset, const ::cvflann::IndexParams& params) diff --git a/modules/highgui/src/cap_v4l.cpp b/modules/highgui/src/cap_v4l.cpp index 4093219ca6..ed5c09c442 100644 --- a/modules/highgui/src/cap_v4l.cpp +++ b/modules/highgui/src/cap_v4l.cpp @@ -380,12 +380,11 @@ static void icvInitCapture_V4L() { deviceHandle = open(deviceName, O_RDONLY); if (deviceHandle != -1) { /* This device does indeed exist - add it to the total so far */ - // add indexList - indexList|=(1 << CameraNumber); - numCameras++; - } - if (deviceHandle != -1) - close(deviceHandle); + // add indexList + indexList|=(1 << CameraNumber); + numCameras++; + close(deviceHandle); + } /* Set up to test the next /dev/video source in line */ CameraNumber++; } /* End while */ diff --git a/modules/legacy/test/test_optflow.cpp b/modules/legacy/test/test_optflow.cpp index 3d74ff6b56..4eb0a13540 100644 --- a/modules/legacy/test/test_optflow.cpp +++ b/modules/legacy/test/test_optflow.cpp @@ -166,6 +166,7 @@ double showFlowAndCalcError(const string& name, const Mat& gray, const Mat& flow bool all = true; Mat inner = flow(where); for(int y = 0; y < inner.rows; ++y) + { for(int x = 0; x < inner.cols; ++x) { const Point2f f = inner.at(y, x); @@ -178,12 +179,14 @@ double showFlowAndCalcError(const string& name, const Mat& gray, const Mat& flow double a = atan2(f.y, f.x); error += fabs(angle - a); } - double res = all ? numeric_limits::max() : error / (inner.cols * inner.rows); + } - if (writeError) - cout << "Error " + name << " = " << res << endl; + double res = all ? numeric_limits::max() : error / (inner.cols * inner.rows); - return res; + if (writeError) + cout << "Error " + name << " = " << res << endl; + + return res; } diff --git a/modules/legacy/test/test_stereomatching.cpp b/modules/legacy/test/test_stereomatching.cpp index a6524bb712..7513bbb1e5 100644 --- a/modules/legacy/test/test_stereomatching.cpp +++ b/modules/legacy/test/test_stereomatching.cpp @@ -461,7 +461,9 @@ void CV_StereoMatchingTest::run(int) int dispScaleFactor = datasetsParams[datasetName].dispScaleFactor; Mat tmp; trueLeftDisp.convertTo( tmp, CV_32FC1, 1.f/dispScaleFactor ); trueLeftDisp = tmp; tmp.release(); if( !trueRightDisp.empty() ) + { trueRightDisp.convertTo( tmp, CV_32FC1, 1.f/dispScaleFactor ); trueRightDisp = tmp; tmp.release(); + } Mat leftDisp, rightDisp; int ignBorder = max(runStereoMatchingAlgorithm(leftImg, rightImg, leftDisp, rightDisp, ci), EVAL_IGNORE_BORDER); diff --git a/modules/ocl/src/mssegmentation.cpp b/modules/ocl/src/mssegmentation.cpp index 5edca3e2f3..71c793ca28 100644 --- a/modules/ocl/src/mssegmentation.cpp +++ b/modules/ocl/src/mssegmentation.cpp @@ -71,8 +71,8 @@ namespace vector rank; vector size; private: - DjSets(const DjSets &) {} - DjSets operator =(const DjSets &); + DjSets(const DjSets &); // = delete + DjSets& operator =(const DjSets &); // = delete }; template @@ -139,13 +139,6 @@ namespace // Implementation // - DjSets DjSets::operator = (const DjSets &/*obj*/) - { - //cout << "Invalid DjSets constructor\n"; - CV_Error(-1, "Invalid DjSets constructor\n"); - return *this; - } - DjSets::DjSets(int n) : parent(n), rank(n, 0), size(n, 1) { for (int i = 0; i < n; ++i) diff --git a/modules/ocl/test/utility.hpp b/modules/ocl/test/utility.hpp index 6591456ee0..8766920023 100644 --- a/modules/ocl/test/utility.hpp +++ b/modules/ocl/test/utility.hpp @@ -296,7 +296,7 @@ CV_FLAGS(DftFlags, DFT_INVERSE, DFT_SCALE, DFT_ROWS, DFT_COMPLEX_OUTPUT, DFT_REA }; \ \ int GTEST_TEST_CLASS_NAME_(test_case_name, \ - test_name)::gtest_registering_dummy_ = \ + test_name)::gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_ = \ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ \ void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() \ diff --git a/modules/stitching/src/matchers.cpp b/modules/stitching/src/matchers.cpp index e0e4779403..29ce8e4836 100644 --- a/modules/stitching/src/matchers.cpp +++ b/modules/stitching/src/matchers.cpp @@ -50,6 +50,7 @@ using namespace cv::gpu; #ifdef HAVE_OPENCV_NONFREE #include "opencv2/nonfree/nonfree.hpp" +CV_ATTR_USED static bool makeUseOfNonfree = initModule_nonfree(); #endif diff --git a/modules/ts/include/opencv2/ts/gpu_test.hpp b/modules/ts/include/opencv2/ts/gpu_test.hpp index 01737bc951..705722a202 100644 --- a/modules/ts/include/opencv2/ts/gpu_test.hpp +++ b/modules/ts/include/opencv2/ts/gpu_test.hpp @@ -190,7 +190,7 @@ namespace cvtest GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ }; \ int GTEST_TEST_CLASS_NAME_(test_case_name, \ - test_name)::gtest_registering_dummy_ = \ + test_name)::gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_ = \ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() \ { \ diff --git a/modules/ts/include/opencv2/ts/ts_gtest.h b/modules/ts/include/opencv2/ts/ts_gtest.h index 75453d204e..0690698d2a 100644 --- a/modules/ts/include/opencv2/ts/ts_gtest.h +++ b/modules/ts/include/opencv2/ts/ts_gtest.h @@ -17026,14 +17026,14 @@ internal::CartesianProductHolder10 \ gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ - int gtest_##prefix##test_case_name##_dummy_ = \ + int gtest_##prefix##test_case_name##_dummy_ GTEST_ATTRIBUTE_UNUSED_ = \ ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ GetTestCasePatternHolder(\ #test_case_name, __FILE__, __LINE__)->AddTestCaseInstantiation(\ diff --git a/modules/ts/src/ts_func.cpp b/modules/ts/src/ts_func.cpp index a4fa5c388a..d11fdc3966 100644 --- a/modules/ts/src/ts_func.cpp +++ b/modules/ts/src/ts_func.cpp @@ -1757,7 +1757,7 @@ cmpUlpsFlt_(const int* src1, const int* src2, size_t total, int imaxdiff, size_t for( i = 0; i < total; i++ ) { int a = src1[i], b = src2[i]; - if( a < 0 ) a ^= C; if( b < 0 ) b ^= C; + if( a < 0 ) { a ^= C; } if( b < 0 ) { b ^= C; } int diff = std::abs(a - b); if( realmaxdiff < diff ) { @@ -1779,7 +1779,7 @@ cmpUlpsFlt_(const int64* src1, const int64* src2, size_t total, int imaxdiff, si for( i = 0; i < total; i++ ) { int64 a = src1[i], b = src2[i]; - if( a < 0 ) a ^= C; if( b < 0 ) b ^= C; + if( a < 0 ) { a ^= C; } if( b < 0 ) { b ^= C; } double diff = fabs((double)a - (double)b); if( realmaxdiff < diff ) { From 497d0fd2e7fc4f1ad11bf2ab5245073a52059c01 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 10 Nov 2016 20:04:08 +0300 Subject: [PATCH 080/409] build: OSSpinLock deprecation warning --- modules/core/src/system.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index f5a1af2ac4..e552723ed8 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -908,6 +908,11 @@ int _interlockedExchangeAdd(int* addr, int delta) #elif defined __APPLE__ +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + #include struct Mutex::Impl @@ -923,6 +928,10 @@ struct Mutex::Impl int refcount; }; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + #elif defined __linux__ && !defined ANDROID && !defined __LINUXTHREADS_OLD__ struct Mutex::Impl From 19270eeab41e678e3bae64f55c941fa95222e364 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 15 Nov 2016 16:13:30 +0300 Subject: [PATCH 081/409] warnings --- apps/haartraining/createsamples.cpp | 5 +++++ modules/core/src/gpumat.cpp | 2 +- modules/gpu/include/opencv2/gpu/gpu.hpp | 10 ++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/haartraining/createsamples.cpp b/apps/haartraining/createsamples.cpp index 4d01fbd10f..f0a8eabe52 100644 --- a/apps/haartraining/createsamples.cpp +++ b/apps/haartraining/createsamples.cpp @@ -52,6 +52,11 @@ #include #include +// std::auto_ptr +#if defined(__GNUC__) && __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + using namespace std; #include "cvhaartraining.h" diff --git a/modules/core/src/gpumat.cpp b/modules/core/src/gpumat.cpp index 96691919fd..5065deb58a 100644 --- a/modules/core/src/gpumat.cpp +++ b/modules/core/src/gpumat.cpp @@ -700,7 +700,7 @@ void cv::gpu::GpuMat::create(int _rows, int _cols, int _type) size_t esz = elemSize(); - void* devPtr; + void* devPtr = NULL; gpuFuncTable()->mallocPitch(&devPtr, &step, esz * cols, rows); // Single row must be continuous diff --git a/modules/gpu/include/opencv2/gpu/gpu.hpp b/modules/gpu/include/opencv2/gpu/gpu.hpp index de16982627..088b5242d2 100644 --- a/modules/gpu/include/opencv2/gpu/gpu.hpp +++ b/modules/gpu/include/opencv2/gpu/gpu.hpp @@ -54,6 +54,12 @@ #include "opencv2/objdetect/objdetect.hpp" #include "opencv2/features2d/features2d.hpp" +// std::auto_ptr +#if defined(__GNUC__) && __GNUC__ >= 6 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + namespace cv { namespace gpu { //////////////////////////////// CudaMem //////////////////////////////// @@ -2527,4 +2533,8 @@ CV_EXPORTS void calcWobbleSuppressionMaps( } // namespace cv +#if defined(__GNUC__) && __GNUC__ >= 6 +#pragma GCC diagnostic pop +#endif + #endif /* __OPENCV_GPU_HPP__ */ From a42be1f9bd02a67688cb6b5be833b0d7059026ce Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 14 Nov 2016 18:59:18 +0300 Subject: [PATCH 082/409] backport ARM toolchain from master --- platforms/linux/aarch64-gnu.toolchain.cmake | 4 + platforms/linux/arm-gnueabi.toolchain.cmake | 92 +---------------- platforms/linux/arm.toolchain.cmake | 97 ++++++++++++++++++ platforms/linux/gnu.toolchain.cmake | 106 ++++++++++++++++++++ 4 files changed, 210 insertions(+), 89 deletions(-) create mode 100644 platforms/linux/aarch64-gnu.toolchain.cmake create mode 100644 platforms/linux/arm.toolchain.cmake create mode 100644 platforms/linux/gnu.toolchain.cmake diff --git a/platforms/linux/aarch64-gnu.toolchain.cmake b/platforms/linux/aarch64-gnu.toolchain.cmake new file mode 100644 index 0000000000..ae16337ce7 --- /dev/null +++ b/platforms/linux/aarch64-gnu.toolchain.cmake @@ -0,0 +1,4 @@ +set(CMAKE_SYSTEM_PROCESSOR aarch64) +set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version") +set(GNU_MACHINE "aarch64-linux-gnu" CACHE STRING "GNU compiler triple") +include("${CMAKE_CURRENT_LIST_DIR}/arm.toolchain.cmake") diff --git a/platforms/linux/arm-gnueabi.toolchain.cmake b/platforms/linux/arm-gnueabi.toolchain.cmake index 2c5b7406d8..90217f005d 100644 --- a/platforms/linux/arm-gnueabi.toolchain.cmake +++ b/platforms/linux/arm-gnueabi.toolchain.cmake @@ -1,89 +1,3 @@ -set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_VERSION 1) -set(CMAKE_SYSTEM_PROCESSOR arm) - -set(GCC_COMPILER_VERSION "4.6" CACHE STRING "GCC Compiler version") - -set(FLOAT_ABI_SUFFIX "") - -if (NOT SOFTFP) - set(FLOAT_ABI_SUFFIX "hf") -endif() - -set(CMAKE_C_COMPILER arm-linux-gnueabi${FLOAT_ABI_SUFFIX}-gcc-${GCC_COMPILER_VERSION}) -set(CMAKE_CXX_COMPILER arm-linux-gnueabi${FLOAT_ABI_SUFFIX}-g++-${GCC_COMPILER_VERSION}) -set(ARM_LINUX_SYSROOT /usr/arm-linux-gnueabi${FLOAT_ABI_SUFFIX} CACHE PATH "ARM cross compilation system root") - -set(CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags") -set(CMAKE_C_FLAGS "" CACHE STRING "c flags") -set(CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "shared linker flags") -set(CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "module linker flags") -set(CMAKE_EXE_LINKER_FLAGS "-Wl,-z,nocopyreloc" CACHE STRING "executable linker flags") - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mthumb -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthumb -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi") - -set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now ${CMAKE_SHARED_LINKER_FLAGS}") -set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now ${CMAKE_MODULE_LINKER_FLAGS}") -set(CMAKE_EXE_LINKER_FLAGS "-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now ${CMAKE_EXE_LINKER_FLAGS}") - -if(USE_NEON) - message(WARNING "You use obsolete variable USE_NEON to enable NEON instruction set. Use -DENABLE_NEON=ON instead." ) - set(ENABLE_NEON TRUE) -elseif(USE_VFPV3) - message(WARNING "You use obsolete variable USE_VFPV3 to enable VFPV3 instruction set. Use -DENABLE_VFPV3=ON instead." ) - set(ENABLE_VFPV3 TRUE) -endif() - -set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${ARM_LINUX_SYSROOT}) - -if(EXISTS ${CUDA_TOOLKIT_ROOT_DIR}) - set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${CUDA_TOOLKIT_ROOT_DIR}) -endif() - -set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." ) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) - -# macro to find programs on the host OS -macro( find_host_program ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) - if( CMAKE_HOST_WIN32 ) - SET( WIN32 1 ) - SET( UNIX ) - elseif( CMAKE_HOST_APPLE ) - SET( APPLE 1 ) - SET( UNIX ) - endif() - find_program( ${ARGN} ) - SET( WIN32 ) - SET( APPLE ) - SET( UNIX 1 ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) -endmacro() - -# macro to find packages on the host OS -macro( find_host_package ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) - if( CMAKE_HOST_WIN32 ) - SET( WIN32 1 ) - SET( UNIX ) - elseif( CMAKE_HOST_APPLE ) - SET( APPLE 1 ) - SET( UNIX ) - endif() - find_package( ${ARGN} ) - SET( WIN32 ) - SET( APPLE ) - SET( UNIX 1 ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) -endmacro() +set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version") +set(GNU_MACHINE "arm-linux-gnueabi" CACHE STRING "GNU compiler triple") +include("${CMAKE_CURRENT_LIST_DIR}/arm.toolchain.cmake") diff --git a/platforms/linux/arm.toolchain.cmake b/platforms/linux/arm.toolchain.cmake new file mode 100644 index 0000000000..75c9194dde --- /dev/null +++ b/platforms/linux/arm.toolchain.cmake @@ -0,0 +1,97 @@ +if(COMMAND toolchain_save_config) + return() # prevent recursive call +endif() + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_VERSION 1) +if(NOT DEFINED CMAKE_SYSTEM_PROCESSOR) + set(CMAKE_SYSTEM_PROCESSOR arm) +else() + #message("CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/gnu.toolchain.cmake") + +if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm AND NOT ARM_IGNORE_FP) + set(FLOAT_ABI_SUFFIX "") + if(NOT SOFTFP) + set(FLOAT_ABI_SUFFIX "hf") + endif() +endif() + +if(NOT "x${GCC_COMPILER_VERSION}" STREQUAL "x") + set(__GCC_VER_SUFFIX "-${GCC_COMPILER_VERSION}") +endif() + +if(NOT DEFINED CMAKE_C_COMPILER) + find_program(CMAKE_C_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-gcc${__GCC_VER_SUFFIX}) +else() + #message(WARNING "CMAKE_C_COMPILER=${CMAKE_C_COMPILER} is defined") +endif() +if(NOT DEFINED CMAKE_CXX_COMPILER) + find_program(CMAKE_CXX_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-g++${__GCC_VER_SUFFIX}) +else() + #message(WARNING "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} is defined") +endif() +if(NOT DEFINED CMAKE_LINKER) + find_program(CMAKE_LINKER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld) +else() + #message(WARNING "CMAKE_LINKER=${CMAKE_LINKER} is defined") +endif() +if(NOT DEFINED CMAKE_AR) + find_program(CMAKE_AR NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar) +else() + #message(WARNING "CMAKE_AR=${CMAKE_AR} is defined") +endif() + +if(NOT DEFINED ARM_LINUX_SYSROOT AND DEFINED GNU_MACHINE) + set(ARM_LINUX_SYSROOT /usr/${GNU_MACHINE}${FLOAT_ABI_SUFFIX}) +endif() + +if(NOT DEFINED CMAKE_CXX_FLAGS) + set(CMAKE_CXX_FLAGS "" CACHE INTERAL "") + set(CMAKE_C_FLAGS "" CACHE INTERAL "") + set(CMAKE_SHARED_LINKER_FLAGS "" CACHE INTERAL "") + set(CMAKE_MODULE_LINKER_FLAGS "" CACHE INTERAL "") + set(CMAKE_EXE_LINKER_FLAGS "" CACHE INTERAL "") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi") + if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm) + set(CMAKE_CXX_FLAGS "-mthumb ${CMAKE_CXX_FLAGS}") + set(CMAKE_C_FLAGS "-mthumb ${CMAKE_C_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,nocopyreloc") + endif() + if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm) + set(ARM_LINKER_FLAGS "-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now") + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64) + set(ARM_LINKER_FLAGS "-Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now") + endif() + set(CMAKE_SHARED_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}") + set(CMAKE_MODULE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}") +else() + #message(WARNING "CMAKE_CXX_FLAGS='${CMAKE_CXX_FLAGS}' is defined") +endif() + +if(USE_NEON) + message(WARNING "You use obsolete variable USE_NEON to enable NEON instruction set. Use -DENABLE_NEON=ON instead." ) + set(ENABLE_NEON TRUE) +elseif(USE_VFPV3) + message(WARNING "You use obsolete variable USE_VFPV3 to enable VFPV3 instruction set. Use -DENABLE_VFPV3=ON instead." ) + set(ENABLE_VFPV3 TRUE) +endif() + +set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${ARM_LINUX_SYSROOT}) + +if(EXISTS ${CUDA_TOOLKIT_ROOT_DIR}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${CUDA_TOOLKIT_ROOT_DIR}) +endif() + +set(TOOLCHAIN_CONFIG_VARS ${TOOLCHAIN_CONFIG_VARS} + ARM_LINUX_SYSROOT + ENABLE_NEON + ENABLE_VFPV3 + CUDA_TOOLKIT_ROOT_DIR +) +toolchain_save_config() diff --git a/platforms/linux/gnu.toolchain.cmake b/platforms/linux/gnu.toolchain.cmake new file mode 100644 index 0000000000..4050d83f61 --- /dev/null +++ b/platforms/linux/gnu.toolchain.cmake @@ -0,0 +1,106 @@ +cmake_minimum_required(VERSION 2.8) + +# load settings in case of "try compile" +set(TOOLCHAIN_CONFIG_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain.config.cmake") +get_property(__IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE) +if(__IN_TRY_COMPILE) + include("${CMAKE_CURRENT_SOURCE_DIR}/../toolchain.config.cmake" OPTIONAL) # CMAKE_BINARY_DIR is different + macro(toolchain_save_config) + # nothing + endmacro() +else() + macro(toolchain_save_config) + set(__config "#message(\"Load TOOLCHAIN config...\")\n") + get_cmake_property(__variableNames VARIABLES) + set(__vars_list ${ARGN}) + list(APPEND __vars_list + ${TOOLCHAIN_CONFIG_VARS} + CMAKE_SYSTEM_NAME + CMAKE_SYSTEM_VERSION + CMAKE_SYSTEM_PROCESSOR + CMAKE_C_COMPILER + CMAKE_CXX_COMPILER + CMAKE_C_FLAGS + CMAKE_CXX_FLAGS + CMAKE_SHARED_LINKER_FLAGS + CMAKE_MODULE_LINKER_FLAGS + CMAKE_EXE_LINKER_FLAGS + CMAKE_SKIP_RPATH + CMAKE_FIND_ROOT_PATH + GCC_COMPILER_VERSION + ) + foreach(__var ${__variableNames}) + foreach(_v ${__vars_list}) + if("x${__var}" STREQUAL "x${_v}") + if(${__var} MATCHES " ") + set(__config "${__config}set(${__var} \"${${__var}}\")\n") + else() + set(__config "${__config}set(${__var} ${${__var}})\n") + endif() + endif() + endforeach() + endforeach() + if(EXISTS "${TOOLCHAIN_CONFIG_FILE}") + file(READ "${TOOLCHAIN_CONFIG_FILE}" __config_old) + endif() + if("${__config_old}" STREQUAL "${__config}") + # nothing + else() + #message("Update TOOLCHAIN config: ${__config}") + file(WRITE "${TOOLCHAIN_CONFIG_FILE}" "${__config}") + endif() + unset(__config) + unset(__config_old) + unset(__vars_list) + unset(__variableNames) + endmacro() +endif() # IN_TRY_COMPILE + +set(CMAKE_SKIP_RPATH TRUE) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) + +# macro to find programs on the host OS +macro(find_host_program) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + if(CMAKE_HOST_WIN32) + SET(WIN32 1) + SET(UNIX) + elseif(CMAKE_HOST_APPLE) + SET(APPLE 1) + SET(UNIX) + endif() + find_program(${ARGN}) + SET(WIN32) + SET(APPLE) + SET(UNIX 1) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endmacro() + +# macro to find packages on the host OS +macro(find_host_package) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + if(CMAKE_HOST_WIN32) + SET(WIN32 1) + SET(UNIX) + elseif(CMAKE_HOST_APPLE) + SET(APPLE 1) + SET(UNIX) + endif() + find_package(${ARGN}) + SET(WIN32) + SET(APPLE) + SET(UNIX 1) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endmacro() + +set(CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries.") From 0a543c553112cd2791f56bd6bb380f77a4b3df4f Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 15 Nov 2016 18:51:17 +0300 Subject: [PATCH 083/409] test: fix Legacy_StereoGC.regression assertion --- modules/legacy/test/test_stereomatching.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/legacy/test/test_stereomatching.cpp b/modules/legacy/test/test_stereomatching.cpp index 7513bbb1e5..2a1d905cbe 100644 --- a/modules/legacy/test/test_stereomatching.cpp +++ b/modules/legacy/test/test_stereomatching.cpp @@ -532,7 +532,7 @@ int CV_StereoMatchingTest::processStereoMatchingResults( FileStorage& fs, int ca // rightDisp is not used in current test virsion int code = cvtest::TS::OK; assert( fs.isOpened() ); - assert( trueLeftDisp.type() == CV_32FC1 && trueRightDisp.type() == CV_32FC1 ); + assert( trueLeftDisp.type() == CV_32FC1 && (trueRightDisp.empty() || trueRightDisp.type() == CV_32FC1) ); assert( leftDisp.type() == CV_32FC1 && rightDisp.type() == CV_32FC1 ); // get masks for unknown ground truth disparity values From 866f8eb1e7c61b50336a33bea84244f00c2e6d8f Mon Sep 17 00:00:00 2001 From: Arek Date: Mon, 21 Nov 2016 02:22:43 +0100 Subject: [PATCH 084/409] Merge pull request #7695 from ArkadiuszRaj:aravis-release-bug-fix * Aravis: fixing releasing object when no communication with camera is possible * Removing unnecessary include --- modules/videoio/src/cap_aravis.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/videoio/src/cap_aravis.cpp b/modules/videoio/src/cap_aravis.cpp index 3423ca6ad4..c272bee726 100644 --- a/modules/videoio/src/cap_aravis.cpp +++ b/modules/videoio/src/cap_aravis.cpp @@ -188,11 +188,12 @@ CvCaptureCAM_Aravis::CvCaptureCAM_Aravis() void CvCaptureCAM_Aravis::close() { - if(camera) + if(camera) { stopCapture(); - g_object_unref(camera); - camera = NULL; + g_object_unref(camera); + camera = NULL; + } } bool CvCaptureCAM_Aravis::getDeviceNameById(int id, std::string &device) @@ -559,8 +560,10 @@ void CvCaptureCAM_Aravis::stopCapture() { arv_camera_stop_acquisition(camera); - g_object_unref(stream); - stream = NULL; + if(stream) { + g_object_unref(stream); + stream = NULL; + } } bool CvCaptureCAM_Aravis::startCapture() From fb456eb69c273c68e85602e774589163d0ffae9b Mon Sep 17 00:00:00 2001 From: LaurentBerger Date: Mon, 21 Nov 2016 02:28:59 +0100 Subject: [PATCH 085/409] Merge pull request #7691 from LaurentBerger:I7676 * Solve issue 7676 * I7676 bis --- samples/cpp/detect_blob.cpp | 18 +++++++++--------- samples/cpp/detect_mser.cpp | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/samples/cpp/detect_blob.cpp b/samples/cpp/detect_blob.cpp index 9c9670632f..6abe03be4f 100644 --- a/samples/cpp/detect_blob.cpp +++ b/samples/cpp/detect_blob.cpp @@ -21,14 +21,14 @@ static String Legende(SimpleBlobDetector::Params &pAct) String s = ""; if (pAct.filterByArea) { - String inf = static_cast(&(ostringstream() << pAct.minArea))->str(); - String sup = static_cast(&(ostringstream() << pAct.maxArea))->str(); + String inf = static_cast(ostringstream() << pAct.minArea).str(); + String sup = static_cast(ostringstream() << pAct.maxArea).str(); s = " Area range [" + inf + " to " + sup + "]"; } if (pAct.filterByCircularity) { - String inf = static_cast(&(ostringstream() << pAct.minCircularity))->str(); - String sup = static_cast(&(ostringstream() << pAct.maxCircularity))->str(); + String inf = static_cast(ostringstream() << pAct.minCircularity).str(); + String sup = static_cast(ostringstream() << pAct.maxCircularity).str(); if (s.length() == 0) s = " Circularity range [" + inf + " to " + sup + "]"; else @@ -36,7 +36,7 @@ static String Legende(SimpleBlobDetector::Params &pAct) } if (pAct.filterByColor) { - String inf = static_cast(&(ostringstream() << (int)pAct.blobColor))->str(); + String inf = static_cast(ostringstream() << (int)pAct.blobColor).str(); if (s.length() == 0) s = " Blob color " + inf; else @@ -44,8 +44,8 @@ static String Legende(SimpleBlobDetector::Params &pAct) } if (pAct.filterByConvexity) { - String inf = static_cast(&(ostringstream() << pAct.minConvexity))->str(); - String sup = static_cast(&(ostringstream() << pAct.maxConvexity))->str(); + String inf = static_cast(ostringstream() << pAct.minConvexity).str(); + String sup = static_cast(ostringstream() << pAct.maxConvexity).str(); if (s.length() == 0) s = " Convexity range[" + inf + " to " + sup + "]"; else @@ -53,8 +53,8 @@ static String Legende(SimpleBlobDetector::Params &pAct) } if (pAct.filterByInertia) { - String inf = static_cast(&(ostringstream() << pAct.minInertiaRatio))->str(); - String sup = static_cast(&(ostringstream() << pAct.maxInertiaRatio))->str(); + String inf = static_cast(ostringstream() << pAct.minInertiaRatio).str(); + String sup = static_cast(ostringstream() << pAct.maxInertiaRatio).str(); if (s.length() == 0) s = " Inertia ratio range [" + inf + " to " + sup + "]"; else diff --git a/samples/cpp/detect_mser.cpp b/samples/cpp/detect_mser.cpp index 7e3248db45..ce29db3d3b 100644 --- a/samples/cpp/detect_mser.cpp +++ b/samples/cpp/detect_mser.cpp @@ -73,25 +73,25 @@ struct MSERParams static String Legende(MSERParams &pAct) { String s=""; - String inf = static_cast(&(ostringstream() << pAct.minArea))->str(); - String sup = static_cast(&(ostringstream() << pAct.maxArea))->str(); + String inf = static_cast(ostringstream() << pAct.minArea).str(); + String sup = static_cast(ostringstream() << pAct.maxArea).str(); s = " Area[" + inf + "," + sup + "]"; - inf = static_cast(&(ostringstream() << pAct.delta))->str(); + inf = static_cast(ostringstream() << pAct.delta).str(); s += " del. [" + inf + "]"; - inf = static_cast(&(ostringstream() << pAct.maxVariation))->str(); + inf = static_cast(ostringstream() << pAct.maxVariation).str(); s += " var. [" + inf + "]"; - inf = static_cast(&(ostringstream() << (int)pAct.minDiversity))->str(); + inf = static_cast(ostringstream() << (int)pAct.minDiversity).str(); s += " div. [" + inf + "]"; - inf = static_cast(&(ostringstream() << (int)pAct.pass2Only))->str(); + inf = static_cast(ostringstream() << (int)pAct.pass2Only).str(); s += " pas. [" + inf + "]"; - inf = static_cast(&(ostringstream() << (int)pAct.maxEvolution))->str(); + inf = static_cast(ostringstream() << (int)pAct.maxEvolution).str(); s += "RGb-> evo. [" + inf + "]"; - inf = static_cast(&(ostringstream() << (int)pAct.areaThreshold))->str(); + inf = static_cast(ostringstream() << (int)pAct.areaThreshold).str(); s += " are. [" + inf + "]"; - inf = static_cast(&(ostringstream() << (int)pAct.minMargin))->str(); + inf = static_cast(ostringstream() << (int)pAct.minMargin).str(); s += " mar. [" + inf + "]"; - inf = static_cast(&(ostringstream() << (int)pAct.edgeBlurSize))->str(); + inf = static_cast(ostringstream() << (int)pAct.edgeBlurSize).str(); s += " siz. [" + inf + "]"; return s; } From 3ea4f72e35c7bd7cd866c44b43461896f2ab7c65 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Fri, 20 May 2016 18:17:22 +0900 Subject: [PATCH 086/409] fix Core_MulSpectrum test on 64bit ARM + release mode * fix issue 6536 * increase threshold in particular tests due to the difference of fmsub and fsub --- modules/core/test/test_dxt.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/modules/core/test/test_dxt.cpp b/modules/core/test/test_dxt.cpp index 33ddb02660..2aaabbf0f8 100644 --- a/modules/core/test/test_dxt.cpp +++ b/modules/core/test/test_dxt.cpp @@ -778,6 +778,9 @@ public: protected: void run_func(); void prepare_to_validation( int test_case_idx ); +#if defined(__aarch64__) && defined(NDEBUG) + double get_success_error_level( int test_case_idx, int i, int j ); +#endif }; @@ -785,6 +788,31 @@ CxCore_MulSpectrumsTest::CxCore_MulSpectrumsTest() : CxCore_DXTBaseTest( true, t { } +#if defined(__aarch64__) && defined(NDEBUG) +double CxCore_MulSpectrumsTest::get_success_error_level( int test_case_idx, int i, int j ) +{ + int elem_depth = CV_MAT_DEPTH(cvGetElemType(test_array[i][j])); + if( elem_depth <= CV_32F ) + { + return ArrayTest::get_success_error_level( test_case_idx, i, j ); + } + switch( test_case_idx ) + { + // Usual threshold is too strict for these test cases due to the difference of fmsub and fsub + case 399: + case 420: + return DBL_EPSILON * 20000; + case 65: + case 161: + case 287: + case 351: + case 458: + return DBL_EPSILON * 10000; + default: + return ArrayTest::get_success_error_level( test_case_idx, i, j ); + } +} +#endif void CxCore_MulSpectrumsTest::run_func() { From f21f153a5f18bc82e129ab5e7a9dd2965b91c2b7 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 21 Nov 2016 14:31:26 +0300 Subject: [PATCH 087/409] test: backport check for Image_KernelSize_GaborFilter2d perf test --- modules/imgproc/perf/perf_filter2d.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/perf/perf_filter2d.cpp b/modules/imgproc/perf/perf_filter2d.cpp index 98992e98e5..2c20e1c451 100644 --- a/modules/imgproc/perf/perf_filter2d.cpp +++ b/modules/imgproc/perf/perf_filter2d.cpp @@ -70,5 +70,5 @@ PERF_TEST_P( Image_KernelSize, GaborFilter2d, filter2D(sourceImage, filteredImage, CV_32F, gaborKernel); } - SANITY_CHECK(filteredImage, 1e-3); + SANITY_CHECK(filteredImage, 1e-6, ERROR_RELATIVE); } From 771fa5b8ac6e7d81cbcb8a4629f7bbe3cd907134 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 21 Nov 2016 14:20:25 +0300 Subject: [PATCH 088/409] test: change EPS in Objdetect_HOGDetector tests --- modules/objdetect/test/test_cascadeandhog.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/objdetect/test/test_cascadeandhog.cpp b/modules/objdetect/test/test_cascadeandhog.cpp index 31afbe6dac..1c844da88b 100644 --- a/modules/objdetect/test/test_cascadeandhog.cpp +++ b/modules/objdetect/test/test_cascadeandhog.cpp @@ -1086,7 +1086,7 @@ void HOGDescriptorTester::detect(const Mat& img, return; } - const double eps = 0.0; + const double eps = FLT_EPSILON * 100; double diff_norm = cvtest::norm(actual_weights, weights, NORM_L2); if (diff_norm > eps) { @@ -1096,7 +1096,6 @@ void HOGDescriptorTester::detect(const Mat& img, failed = true; ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); ts->set_gtest_status(); - return; } } @@ -1169,7 +1168,7 @@ void HOGDescriptorTester::compute(InputArray _img, vector& descriptors, actual_hog->compute(img, actual_descriptors, winStride, padding, locations); double diff_norm = cvtest::norm(actual_descriptors, descriptors, NORM_L2); - const double eps = 0.0; + const double eps = FLT_EPSILON * 100; if (diff_norm > eps) { ts->printf(cvtest::TS::SUMMARY, "Norm of the difference: %lf\n", diff_norm); @@ -1178,7 +1177,6 @@ void HOGDescriptorTester::compute(InputArray _img, vector& descriptors, ts->printf(cvtest::TS::LOG, "Channels: %d\n", img.channels()); ts->set_gtest_status(); failed = true; - return; } } @@ -1315,7 +1313,7 @@ void HOGDescriptorTester::computeGradient(const Mat& img, Mat& grad, Mat& qangle const char* args[] = { "Gradient's", "Qangles's" }; actual_hog->computeGradient(img, actual_mats[0], actual_mats[1], paddingTL, paddingBR); - const double eps = 0.0; + const double eps = FLT_EPSILON * 100; for (i = 0; i < 2; ++i) { double diff_norm = cvtest::norm(reference_mats[i], actual_mats[i], NORM_L2); @@ -1327,7 +1325,6 @@ void HOGDescriptorTester::computeGradient(const Mat& img, Mat& grad, Mat& qangle ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); ts->set_gtest_status(); failed = true; - return; } } } From dfb9c574a4fc6077bb275cc110f066dbfab408b6 Mon Sep 17 00:00:00 2001 From: Pavel Vlasov Date: Mon, 21 Nov 2016 17:18:03 +0300 Subject: [PATCH 089/409] IPP 2017 filter2D fix; filter2D IPP runtime check; --- modules/imgproc/src/filter.cpp | 42 ++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/modules/imgproc/src/filter.cpp b/modules/imgproc/src/filter.cpp index 68a168f617..e379df2bb0 100644 --- a/modules/imgproc/src/filter.cpp +++ b/modules/imgproc/src/filter.cpp @@ -4640,6 +4640,11 @@ struct IppFilter : public hal::Filter2D int ddepth = CV_MAT_DEPTH(dtype); int sdepth = CV_MAT_DEPTH(stype); +#if IPP_VERSION_X100 >= 201700 && IPP_VERSION_X100 < 201702 // IPP bug with 1x1 kernel + if(kernel_width == 1 && kernel_height == 1) + return false; +#endif + bool runIpp = true && (borderTypeNI == BORDER_CONSTANT || borderTypeNI == BORDER_REPLICATE) && (sdepth == ddepth) @@ -4911,26 +4916,29 @@ Ptr Filter2D::create(uchar* kernel_data, size_t kernel_step, int } #ifdef HAVE_IPP - if (kernel_type == CV_32FC1) { - IppFilter* impl = new IppFilter(); - if (impl->init(kernel_data, kernel_step, kernel_type, kernel_width, kernel_height, - max_width, max_height, stype, dtype, - borderType, delta, anchor_x, anchor_y, isSubmatrix, isInplace)) - { - return Ptr(impl); + CV_IPP_CHECK() + { + if (kernel_type == CV_32FC1) { + IppFilter* impl = new IppFilter(); + if (impl->init(kernel_data, kernel_step, kernel_type, kernel_width, kernel_height, + max_width, max_height, stype, dtype, + borderType, delta, anchor_x, anchor_y, isSubmatrix, isInplace)) + { + return Ptr(impl); + } + delete impl; } - delete impl; - } - if (kernel_type == CV_16SC1) { - IppFilter* impl = new IppFilter(); - if (impl->init(kernel_data, kernel_step, kernel_type, kernel_width, kernel_height, - max_width, max_height, stype, dtype, - borderType, delta, anchor_x, anchor_y, isSubmatrix, isInplace)) - { - return Ptr(impl); + if (kernel_type == CV_16SC1) { + IppFilter* impl = new IppFilter(); + if (impl->init(kernel_data, kernel_step, kernel_type, kernel_width, kernel_height, + max_width, max_height, stype, dtype, + borderType, delta, anchor_x, anchor_y, isSubmatrix, isInplace)) + { + return Ptr(impl); + } + delete impl; } - delete impl; } #endif From 5c969d1972f6e90ede402b0cdaf108396e841e6c Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Thu, 1 Sep 2016 14:34:34 +0300 Subject: [PATCH 090/409] interoperability OpenVX samples added --- samples/CMakeLists.txt | 4 + samples/openvx/CMakeLists.txt | 36 +++ samples/openvx/no_wrappers.cpp | 382 ++++++++++++++++++++++++++++++ samples/openvx/wrappers.cpp | 216 +++++++++++++++++ samples/openvx/wrappers_video.cpp | 253 ++++++++++++++++++++ 5 files changed, 891 insertions(+) create mode 100644 samples/openvx/CMakeLists.txt create mode 100644 samples/openvx/no_wrappers.cpp create mode 100644 samples/openvx/wrappers.cpp create mode 100644 samples/openvx/wrappers_video.cpp diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 70b85eecd3..b1f98e969d 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -22,6 +22,10 @@ if((NOT ANDROID) AND HAVE_OPENGL) add_subdirectory(opengl) endif() +if(HAVE_OPENVX) + add_subdirectory(openvx) +endif() + if(UNIX AND NOT ANDROID AND (HAVE_VA OR HAVE_VA_INTEL)) add_subdirectory(va_intel) endif() diff --git a/samples/openvx/CMakeLists.txt b/samples/openvx/CMakeLists.txt new file mode 100644 index 0000000000..3f891f2794 --- /dev/null +++ b/samples/openvx/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.9) + +set(OPENCV_OPENVX_SAMPLE_REQUIRED_DEPS opencv_core opencv_imgproc opencv_imgcodecs opencv_videoio opencv_highgui) + +ocv_check_dependencies(${OPENCV_OPENVX_SAMPLE_REQUIRED_DEPS}) + +if(BUILD_EXAMPLES AND OCV_DEPENDENCIES_FOUND) + set(group "openvx") + set(name_wrapped "interop") + set(name_orig "interop_orig") + set(name_video "interop_video") + + project("${group}_sample") + + ocv_include_modules_recurse(${OPENCV_OPENVX_SAMPLE_REQUIRED_DEPS}) + + add_definitions(-DIVX_USE_OPENCV) + + file(GLOB srcs_wrapped wrappers.cpp *.hpp) + file(GLOB srcs_orig no_wrappers.cpp *.hpp) + file(GLOB srcs_video wrappers_video.cpp *.hpp) + + MACRO(OPENVX_DEFINE_SAMPLE name srcs) + set(target "example_${group}_${name}") + add_executable(${target} ${srcs}) + ocv_target_link_libraries(${target} ${OPENCV_LINKER_LIBS} ${OPENCV_OPENVX_SAMPLE_REQUIRED_DEPS} ${OPENVX_LIBRARIES}) + if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(${target} PROPERTIES FOLDER "samples//${group}") + endif() + ENDMACRO() + + OPENVX_DEFINE_SAMPLE(${name_wrapped} ${srcs_wrapped}) + OPENVX_DEFINE_SAMPLE(${name_orig} ${srcs_orig}) + OPENVX_DEFINE_SAMPLE(${name_video} ${srcs_video}) + +endif() diff --git a/samples/openvx/no_wrappers.cpp b/samples/openvx/no_wrappers.cpp new file mode 100644 index 0000000000..c15367a526 --- /dev/null +++ b/samples/openvx/no_wrappers.cpp @@ -0,0 +1,382 @@ +#include +#include + +//OpenVX includes +#include + +//OpenCV includes +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/highgui.hpp" + +#ifndef VX_VERSION_1_1 +const vx_enum VX_IMAGE_FORMAT = VX_IMAGE_ATTRIBUTE_FORMAT; +const vx_enum VX_IMAGE_WIDTH = VX_IMAGE_ATTRIBUTE_WIDTH; +const vx_enum VX_IMAGE_HEIGHT = VX_IMAGE_ATTRIBUTE_HEIGHT; +const vx_enum VX_MEMORY_TYPE_HOST = VX_IMPORT_TYPE_HOST; +const vx_enum VX_MEMORY_TYPE_NONE = VX_IMPORT_TYPE_NONE; +const vx_enum VX_THRESHOLD_THRESHOLD_VALUE = VX_THRESHOLD_ATTRIBUTE_THRESHOLD_VALUE; +const vx_enum VX_THRESHOLD_THRESHOLD_LOWER = VX_THRESHOLD_ATTRIBUTE_THRESHOLD_LOWER; +const vx_enum VX_THRESHOLD_THRESHOLD_UPPER = VX_THRESHOLD_ATTRIBUTE_THRESHOLD_UPPER; +typedef uintptr_t vx_map_id; +#endif + +enum UserMemoryMode +{ + COPY, MAP_TO_VX +}; + +vx_image convertCvMatToVxImage(vx_context context, cv::Mat image, bool toCopy) +{ + if (!(!image.empty() && image.dims <= 2 && image.channels() == 1)) + throw std::runtime_error("Invalid format"); + + vx_uint32 width = image.cols; + vx_uint32 height = image.rows; + + vx_df_image color; + switch (image.depth()) + { + case CV_8U: + color = VX_DF_IMAGE_U8; + break; + case CV_16U: + color = VX_DF_IMAGE_U16; + break; + case CV_16S: + color = VX_DF_IMAGE_S16; + break; + case CV_32S: + color = VX_DF_IMAGE_S32; + break; + default: + throw std::runtime_error("Invalid format"); + break; + } + + vx_imagepatch_addressing_t addr; + addr.dim_x = width; + addr.dim_y = height; + addr.stride_x = (vx_uint32)image.elemSize(); + addr.stride_y = (vx_uint32)image.step.p[0]; + vx_uint8* ovxData = image.data; + + vx_image ovxImage; + if (toCopy) + { + ovxImage = vxCreateImage(context, width, height, color); + if (vxGetStatus((vx_reference)ovxImage) != VX_SUCCESS) + throw std::runtime_error("Failed to create image"); + vx_rectangle_t rect; + + vx_status status = vxGetValidRegionImage(ovxImage, &rect); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to get valid region"); + +#ifdef VX_VERSION_1_1 + status = vxCopyImagePatch(ovxImage, &rect, 0, &addr, ovxData, VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to copy image patch"); +#else + status = vxAccessImagePatch(ovxImage, &rect, 0, &addr, (void**)&ovxData, VX_WRITE_ONLY); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to access image patch"); + status = vxCommitImagePatch(ovxImage, &rect, 0, &addr, ovxData); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to commit image patch"); +#endif + } + else + { +#ifdef VX_VERSION_1_1 + ovxImage = vxCreateImageFromHandle(context, color, &addr, (void*const*)&ovxData, VX_MEMORY_TYPE_HOST); +#else + ovxImage = vxCreateImageFromHandle(context, color, &addr, (void**)&ovxData, VX_MEMORY_TYPE_HOST); +#endif + if (vxGetStatus((vx_reference)ovxImage) != VX_SUCCESS) + throw std::runtime_error("Failed to create image from handle"); + } + + return ovxImage; +} + + +cv::Mat copyVxImageToCvMat(vx_image ovxImage) +{ + vx_status status; + vx_df_image df_image = 0; + vx_uint32 width, height; + status = vxQueryImage(ovxImage, VX_IMAGE_FORMAT, &df_image, sizeof(vx_df_image)); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to query image"); + status = vxQueryImage(ovxImage, VX_IMAGE_WIDTH, &width, sizeof(vx_uint32)); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to query image"); + status = vxQueryImage(ovxImage, VX_IMAGE_HEIGHT, &height, sizeof(vx_uint32)); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to query image"); + + if (!(width > 0 && height > 0)) throw std::runtime_error("Invalid format"); + + int depth; + switch (df_image) + { + case VX_DF_IMAGE_U8: + depth = CV_8U; + break; + case VX_DF_IMAGE_U16: + depth = CV_16U; + break; + case VX_DF_IMAGE_S16: + depth = CV_16S; + break; + case VX_DF_IMAGE_S32: + depth = CV_32S; + break; + default: + throw std::runtime_error("Invalid format"); + break; + } + + cv::Mat image(height, width, CV_MAKE_TYPE(depth, 1)); + + vx_rectangle_t rect; + rect.start_x = rect.start_y = 0; + rect.end_x = width; rect.end_y = height; + + vx_imagepatch_addressing_t addr; + addr.dim_x = width; + addr.dim_y = height; + addr.stride_x = (vx_uint32)image.elemSize(); + addr.stride_y = (vx_uint32)image.step.p[0]; + vx_uint8* matData = image.data; + +#ifdef VX_VERSION_1_1 + status = vxCopyImagePatch(ovxImage, &rect, 0, &addr, matData, VX_READ_ONLY, VX_MEMORY_TYPE_HOST); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to copy image patch"); +#else + status = vxAccessImagePatch(ovxImage, &rect, 0, &addr, (void**)&matData, VX_READ_ONLY); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to access image patch"); + status = vxCommitImagePatch(ovxImage, &rect, 0, &addr, matData); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to commit image patch"); +#endif + + return image; +} + + +void swapVxImage(vx_image ovxImage) +{ +#ifdef VX_VERSION_1_1 + vx_status status; + vx_memory_type_e memType; + status = vxQueryImage(ovxImage, VX_IMAGE_MEMORY_TYPE, &memType, sizeof(vx_memory_type_e)); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to query image"); + if (memType == VX_MEMORY_TYPE_NONE) + { + //was created by copying user data + throw std::runtime_error("Image wasn't created from user handle"); + } + else + { + //was created from user handle + status = vxSwapImageHandle(ovxImage, NULL, NULL, 0); + if (status != VX_SUCCESS) + throw std::runtime_error("Failed to swap image handle"); + } +#else + //not supported until OpenVX 1.1 + (void) ovxImage; +#endif +} + + +vx_status createProcessingGraph(vx_image inputImage, vx_image outputImage, vx_graph& graph) +{ + vx_status status; + vx_context context = vxGetContext((vx_reference)inputImage); + status = vxGetStatus((vx_reference)context); + if(status != VX_SUCCESS) return status; + + graph = vxCreateGraph(context); + status = vxGetStatus((vx_reference)graph); + if (status != VX_SUCCESS) return status; + + vx_uint32 width, height; + status = vxQueryImage(inputImage, VX_IMAGE_WIDTH, &width, sizeof(vx_uint32)); + if (status != VX_SUCCESS) return status; + status = vxQueryImage(inputImage, VX_IMAGE_HEIGHT, &height, sizeof(vx_uint32)); + if (status != VX_SUCCESS) return status; + + // Intermediate images + vx_image + smoothed = vxCreateVirtualImage(graph, 0, 0, VX_DF_IMAGE_VIRT), + cannied = vxCreateVirtualImage(graph, 0, 0, VX_DF_IMAGE_VIRT), + halfImg = vxCreateImage(context, width, height, VX_DF_IMAGE_U8), + halfCanny = vxCreateImage(context, width, height, VX_DF_IMAGE_U8); + + vx_image virtualImages[] = {smoothed, cannied, halfImg, halfCanny}; + for(size_t i = 0; i < sizeof(virtualImages)/sizeof(vx_image); i++) + { + status = vxGetStatus((vx_reference)virtualImages[i]); + if (status != VX_SUCCESS) return status; + } + + // Constants + vx_uint32 threshValue = 50; + vx_threshold thresh = vxCreateThreshold(context, VX_THRESHOLD_TYPE_BINARY, VX_TYPE_UINT8); + vxSetThresholdAttribute(thresh, VX_THRESHOLD_THRESHOLD_VALUE, + &threshValue, sizeof(threshValue)); + + vx_uint32 threshCannyMin = 127; + vx_uint32 threshCannyMax = 192; + vx_threshold threshCanny = vxCreateThreshold(context, VX_THRESHOLD_TYPE_RANGE, VX_TYPE_UINT8); + vxSetThresholdAttribute(threshCanny, VX_THRESHOLD_THRESHOLD_LOWER, &threshCannyMin, + sizeof(threshCannyMin)); + vxSetThresholdAttribute(threshCanny, VX_THRESHOLD_THRESHOLD_UPPER, &threshCannyMax, + sizeof(threshCannyMax)); + vx_float32 alphaValue = 0.5; + vx_scalar alpha = vxCreateScalar(context, VX_TYPE_FLOAT32, &alphaValue); + + // Sequence of meaningless image operations + vx_node nodes[] = { + vxGaussian3x3Node(graph, inputImage, smoothed), + vxCannyEdgeDetectorNode(graph, smoothed, threshCanny, 3, VX_NORM_L2, cannied), + vxAccumulateWeightedImageNode(graph, inputImage, alpha, halfImg), + vxAccumulateWeightedImageNode(graph, cannied, alpha, halfCanny), + vxAddNode(graph, halfImg, halfCanny, VX_CONVERT_POLICY_SATURATE, outputImage) + }; + + for (size_t i = 0; i < sizeof(nodes) / sizeof(vx_node); i++) + { + status = vxGetStatus((vx_reference)nodes[i]); + if (status != VX_SUCCESS) return status; + } + + status = vxVerifyGraph(graph); + return status; +} + + +int ovxDemo(std::string inputPath, UserMemoryMode mode) +{ + cv::Mat image = cv::imread(inputPath, cv::IMREAD_GRAYSCALE); + if (image.empty()) return -1; + + //check image format + if (image.depth() != CV_8U || image.channels() != 1) return -1; + + vx_status status; + vx_context context = vxCreateContext(); + status = vxGetStatus((vx_reference)context); + if (status != VX_SUCCESS) return status; + + //put user data from cv::Mat to vx_image + vx_image ovxImage; + ovxImage = convertCvMatToVxImage(context, image, mode == COPY); + + vx_uint32 width = image.cols, height = image.rows; + + vx_image ovxResult; + cv::Mat output; + if (mode == COPY) + { + //we will copy data from vx_image to cv::Mat + ovxResult = vxCreateImage(context, width, height, VX_DF_IMAGE_U8); + if (vxGetStatus((vx_reference)ovxResult) != VX_SUCCESS) + throw std::runtime_error("Failed to create image"); + } + else + { + //create vx_image based on user data, no copying required + output = cv::Mat(height, width, CV_8U, cv::Scalar(0)); + ovxResult = convertCvMatToVxImage(context, output, false); + } + + vx_graph graph; + status = createProcessingGraph(ovxImage, ovxResult, graph); + if (status != VX_SUCCESS) return status; + + // Graph execution + status = vxProcessGraph(graph); + if (status != VX_SUCCESS) return status; + + //getting resulting image in cv::Mat + if (mode == COPY) + { + output = copyVxImageToCvMat(ovxResult); + } + else + { + //we should take user memory back from vx_image before using it (even before reading) + swapVxImage(ovxResult); + } + + //here output goes + cv::imshow("processing result", output); + cv::waitKey(0); + + //we need to take user memory back before releasing the image + if (mode == MAP_TO_VX) + swapVxImage(ovxImage); + + cv::destroyAllWindows(); + + status = vxReleaseContext(&context); + return status; +} + + +int main(int argc, char *argv[]) +{ + const std::string keys = + "{help h usage ? | | }" + "{image | | image to be processed}" + "{mode | copy | user memory interaction mode: \n" + "copy: create VX images and copy data to/from them\n" + "map_to_vx: use handles to user-allocated memory}" + ; + + cv::CommandLineParser parser(argc, argv, keys); + parser.about("OpenVX interoperability sample demonstrating standard OpenVX API." + "The application loads an image, processes it with OpenVX graph and outputs result in a window"); + if (parser.has("help")) + { + parser.printMessage(); + return 0; + } + std::string imgPath = parser.get("image"); + std::string modeString = parser.get("mode"); + UserMemoryMode mode; + if(modeString == "copy") + { + mode = COPY; + } + else if(modeString == "map_to_vx") + { + mode = MAP_TO_VX; + } + else if(modeString == "map_from_vx") + { + std::cerr << modeString << " is not implemented in this sample" << std::endl; + return -1; + } + else + { + std::cerr << modeString << ": unknown memory mode" << std::endl; + return -1; + } + + if (!parser.check()) + { + parser.printErrors(); + return -1; + } + + return ovxDemo(imgPath, mode); +} diff --git a/samples/openvx/wrappers.cpp b/samples/openvx/wrappers.cpp new file mode 100644 index 0000000000..5dd7da17c7 --- /dev/null +++ b/samples/openvx/wrappers.cpp @@ -0,0 +1,216 @@ +#include +#include + +//wrappers +#include "ivx.hpp" + +//OpenCV includes +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/highgui.hpp" + +enum UserMemoryMode +{ + COPY, MAP_TO_VX, MAP_FROM_VX +}; + +ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage) +{ + using namespace ivx; + + Context context = inputImage.getContext(); + + Graph graph = Graph::create(context); + + vx_uint32 width = inputImage.width(); + vx_uint32 height = inputImage.height(); + + // Intermediate images + Image + smoothed = Image::createVirtual(graph), + cannied = Image::createVirtual(graph), + halfImg = Image::create(context, width, height, VX_DF_IMAGE_U8), + halfCanny = Image::create(context, width, height, VX_DF_IMAGE_U8); + + // Constants + vx_uint32 threshCannyMin = 127; + vx_uint32 threshCannyMax = 192; + Threshold threshCanny = Threshold::createRange(context, VX_TYPE_UINT8, threshCannyMin, threshCannyMax); + + ivx::Scalar alpha = ivx::Scalar::create(context, 0.5); + + // Sequence of some image operations + // Node can also be added in function-like style + nodes::gaussian3x3(graph, inputImage, smoothed); + Node::create(graph, VX_KERNEL_CANNY_EDGE_DETECTOR, smoothed, threshCanny, + ivx::Scalar::create(context, 3), + ivx::Scalar::create(context, VX_NORM_L2), cannied); + Node::create(graph, VX_KERNEL_ACCUMULATE_WEIGHTED, inputImage, alpha, halfImg); + Node::create(graph, VX_KERNEL_ACCUMULATE_WEIGHTED, cannied, alpha, halfCanny); + Node::create(graph, VX_KERNEL_ADD, halfImg, halfCanny, + ivx::Scalar::create(context, VX_CONVERT_POLICY_SATURATE), outputImage); + + graph.verify(); + + return graph; +} + + +int ovxDemo(std::string inputPath, UserMemoryMode mode) +{ + using namespace cv; + using namespace ivx; + + Mat image = imread(inputPath, IMREAD_GRAYSCALE); + if (image.empty()) return -1; + + //check image format + if (image.depth() != CV_8U || image.channels() != 1) return -1; + + try + { + Context context = Context::create(); + //put user data from cv::Mat to vx_image + vx_df_image color = Image::matTypeToFormat(image.type()); + vx_uint32 width = image.cols, height = image.rows; + Image ivxImage; + if (mode == COPY) + { + ivxImage = Image::create(context, width, height, color); + ivxImage.copyFrom(0, image); + } + else + { + vx_imagepatch_addressing_t addressing = Image::createAddressing(image); + const std::vector addrs(1, addressing); + const std::vector ptrs(1, image.data); + ivxImage = Image::createFromHandle(context, color, addrs, ptrs); + } + + Image ivxResult; + Image::Patch resultPatch; + Mat output; + if (mode == COPY || mode == MAP_FROM_VX) + { + //we will copy or map data from vx_image to cv::Mat + ivxResult = ivx::Image::create(context, width, height, VX_DF_IMAGE_U8); + } + else // if (mode == MAP_TO_VX) + { + //create vx_image based on user data, no copying required + output = cv::Mat(height, width, CV_8U, cv::Scalar(0)); + vx_imagepatch_addressing_t addressing = Image::createAddressing(output); + const std::vector addrs(1, addressing); + const std::vector ptrs(1, output.data); + ivxResult = Image::createFromHandle(context, Image::matTypeToFormat(CV_8U), addrs, ptrs); + } + + Graph graph = createProcessingGraph(ivxImage, ivxResult); + + // Graph execution + graph.process(); + + //getting resulting image in cv::Mat + if (mode == COPY) + { + ivxResult.copyTo(0, output); + } + else if (mode == MAP_FROM_VX) + { + //create cv::Mat based on vx_image mapped data + resultPatch.map(ivxResult, 0, ivxResult.getValidRegion()); + //generally this is very bad idea! + //but in our case unmap() won't happen until output is in use + output = resultPatch.getMat(); + } + else // if (mode == MAP_TO_VX) + { +#ifdef VX_VERSION_1_1 + //we should take user memory back from vx_image before using it (even before reading) + ivxResult.swapHandle(); +#endif + } + + //here output goes + cv::imshow("processing result", output); + cv::waitKey(0); + + cv::destroyAllWindows(); + +#ifdef VX_VERSION_1_1 + if (mode != COPY) + { + //we should take user memory back before release + //(it's not done automatically according to standard) + ivxImage.swapHandle(); + if (mode == MAP_TO_VX) ivxResult.swapHandle(); + } +#endif + + //the line is unnecessary since unmapping is done on destruction of patch + //resultPatch.unmap(); + } + catch (const ivx::RuntimeError& e) + { + std::cerr << "Error: code = " << e.status() << ", message = " << e.what() << std::endl; + return e.status(); + } + catch (const ivx::WrapperError& e) + { + std::cerr << "Error: message = " << e.what() << std::endl; + return -1; + } + + return 0; +} + + +int main(int argc, char *argv[]) +{ + const std::string keys = + "{help h usage ? | | }" + "{image | | image to be processed}" + "{mode | copy | user memory interaction mode: \n" + "copy: create VX images and copy data to/from them\n" + "map_to_vx: use handles to user-allocated memory\n" + "map_from_vx: map resulting VX image to user memory}" + ; + + cv::CommandLineParser parser(argc, argv, keys); + parser.about("OpenVX interoperability sample demonstrating OpenVX wrappers usage." + "The application loads an image, processes it with OpenVX graph and outputs result in a window"); + if (parser.has("help")) + { + parser.printMessage(); + return 0; + } + std::string imgPath = parser.get("image"); + std::string modeString = parser.get("mode"); + UserMemoryMode mode; + if(modeString == "copy") + { + mode = COPY; + } + else if(modeString == "map_to_vx") + { + mode = MAP_TO_VX; + } + else if(modeString == "map_from_vx") + { + mode = MAP_FROM_VX; + } + else + { + std::cerr << modeString << ": unknown memory mode" << std::endl; + return -1; + } + + if (!parser.check()) + { + parser.printErrors(); + return -1; + } + + return ovxDemo(imgPath, mode); +} diff --git a/samples/openvx/wrappers_video.cpp b/samples/openvx/wrappers_video.cpp new file mode 100644 index 0000000000..df7be472db --- /dev/null +++ b/samples/openvx/wrappers_video.cpp @@ -0,0 +1,253 @@ +#include +#include + +//wrappers +#include "ivx.hpp" + +//OpenCV includes +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/highgui.hpp" + +enum UserMemoryMode +{ + COPY, MAP_TO_VX, MAP_FROM_VX +}; + +ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage) +{ + using namespace ivx; + + Context context = inputImage.getContext(); + + Graph graph = Graph::create(context); + + vx_uint32 width = inputImage.width(); + vx_uint32 height = inputImage.height(); + + // Intermediate images + Image + yuv = Image::createVirtual(graph, 0, 0, VX_DF_IMAGE_YUV4), + gray = Image::createVirtual(graph), + smoothed = Image::createVirtual(graph), + cannied = Image::createVirtual(graph), + halfImg = Image::create(context, width, height, VX_DF_IMAGE_U8), + halfCanny = Image::create(context, width, height, VX_DF_IMAGE_U8); + + // Constants + vx_uint32 threshCannyMin = 127; + vx_uint32 threshCannyMax = 192; + Threshold threshCanny = Threshold::createRange(context, VX_TYPE_UINT8, threshCannyMin, threshCannyMax); + + ivx::Scalar alpha = ivx::Scalar::create(context, 0.5); + + // Sequence of some image operations + Node::create(graph, VX_KERNEL_COLOR_CONVERT, inputImage, yuv); + Node::create(graph, VX_KERNEL_CHANNEL_EXTRACT, yuv, + ivx::Scalar::create(context, VX_CHANNEL_Y), gray); + //node can also be added in function-like style + nodes::gaussian3x3(graph, gray, smoothed); + Node::create(graph, VX_KERNEL_CANNY_EDGE_DETECTOR, smoothed, threshCanny, + ivx::Scalar::create(context, 3), + ivx::Scalar::create(context, VX_NORM_L2), cannied); + Node::create(graph, VX_KERNEL_ACCUMULATE_WEIGHTED, gray, alpha, halfImg); + Node::create(graph, VX_KERNEL_ACCUMULATE_WEIGHTED, cannied, alpha, halfCanny); + Node::create(graph, VX_KERNEL_ADD, halfImg, halfCanny, + ivx::Scalar::create(context, VX_CONVERT_POLICY_SATURATE), outputImage); + + graph.verify(); + + return graph; +} + + +int ovxDemo(std::string inputPath, UserMemoryMode mode) +{ + using namespace cv; + using namespace ivx; + + Mat frame; + VideoCapture vc(inputPath); + if (!vc.isOpened()) + return -1; + + vc >> frame; + if (frame.empty()) return -1; + + //check frame format + if (frame.type() != CV_8UC3) return -1; + + try + { + Context context = Context::create(); + //put user data from cv::Mat to vx_image + vx_df_image color = Image::matTypeToFormat(frame.type()); + vx_uint32 width = frame.cols, height = frame.rows; + Image ivxImage; + if (mode == COPY) + { + ivxImage = Image::create(context, width, height, color); + } + else + { + vx_imagepatch_addressing_t addressing = Image::createAddressing(frame); + const std::vector addrs(1, addressing); + const std::vector ptrs(1, frame.data); + ivxImage = Image::createFromHandle(context, color, addrs, ptrs); + } + + Image ivxResult; + + Mat output; + if (mode == COPY || mode == MAP_FROM_VX) + { + //we will copy or map data from vx_image to cv::Mat + ivxResult = ivx::Image::create(context, width, height, VX_DF_IMAGE_U8); + } + else // if (mode == MAP_TO_VX) + { + //create vx_image based on user data, no copying required + output = cv::Mat(height, width, CV_8U, cv::Scalar(0)); + vx_imagepatch_addressing_t addressing = Image::createAddressing(output); + const std::vector addrs(1, addressing); + const std::vector ptrs(1, output.data); + ivxResult = Image::createFromHandle(context, Image::matTypeToFormat(CV_8U), addrs, ptrs); + } + + Graph graph = createProcessingGraph(ivxImage, ivxResult); + + std::vector ptrs; + + bool stop = false; + while (!stop) + { + if (mode == COPY) ivxImage.copyFrom(0, frame); + + // Graph execution + graph.process(); + + //getting resulting image in cv::Mat + Image::Patch resultPatch; + std::vector prevPtrs; + if (mode == COPY) + { + ivxResult.copyTo(0, output); + } + else if (mode == MAP_FROM_VX) + { + //create cv::Mat based on vx_image mapped data + resultPatch.map(ivxResult, 0, ivxResult.getValidRegion(), VX_READ_AND_WRITE); + //generally this is very bad idea! + //but in our case unmap() won't happen until output is in use + output = resultPatch.getMat(); + } + else // if(mode == MAP_TO_VX) + { +#ifdef VX_VERSION_1_1 + //we should take user memory back from vx_image before using it (even before reading) + ivxResult.swapHandle(ptrs, prevPtrs); +#endif + } + + //here output goes + imshow("press q to quit", output); + if ((char)waitKey(1) == 'q') stop = true; + +#ifdef VX_VERSION_1_1 + //restore handle + if (mode == MAP_TO_VX) + { + ivxResult.swapHandle(prevPtrs); + } +#endif + + //this line is unnecessary since unmapping is done on destruction of patch + //resultPatch.unmap(); + + //grab next frame + Mat temp = frame; + vc >> frame; + if (frame.empty()) stop = true; + if (mode != COPY && frame.data != temp.data) + { + //frame was reallocated, pointer to data changed + frame.copyTo(temp); + } + } + + destroyAllWindows(); + +#ifdef VX_VERSION_1_1 + if (mode != COPY) + { + //we should take user memory back before release + //(it's not done automatically according to standard) + ivxImage.swapHandle(); + if (mode == MAP_TO_VX) ivxResult.swapHandle(); + } +#endif + } + catch (const ivx::RuntimeError& e) + { + std::cerr << "Error: code = " << e.status() << ", message = " << e.what() << std::endl; + return e.status(); + } + catch (const ivx::WrapperError& e) + { + std::cerr << "Error: message = " << e.what() << std::endl; + return -1; + } + + return 0; +} + + +int main(int argc, char *argv[]) +{ + const std::string keys = + "{help h usage ? | | }" + "{video | | video file to be processed}" + "{mode | copy | user memory interaction mode: \n" + "copy: create VX images and copy data to/from them\n" + "map_to_vx: use handles to user-allocated memory\n" + "map_from_vx: map resulting VX image to user memory}" + ; + + cv::CommandLineParser parser(argc, argv, keys); + parser.about("OpenVX interoperability sample demonstrating OpenVX wrappers usage." + "The application opens a video and processes it with OpenVX graph while outputting result in a window"); + if (parser.has("help")) + { + parser.printMessage(); + return 0; + } + std::string videoPath = parser.get("video"); + std::string modeString = parser.get("mode"); + UserMemoryMode mode; + if(modeString == "copy") + { + mode = COPY; + } + else if(modeString == "map_to_vx") + { + mode = MAP_TO_VX; + } + else if(modeString == "map_from_vx") + { + mode = MAP_FROM_VX; + } + else + { + std::cerr << modeString << ": unknown memory mode" << std::endl; + return -1; + } + + if (!parser.check()) + { + parser.printErrors(); + return -1; + } + + return ovxDemo(videoPath, mode); +} From 9032a7ab8145c5907e2029fbb9319c7b5c2a376b Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Thu, 17 Nov 2016 15:58:23 +0300 Subject: [PATCH 091/409] minor fixes in samples and ivx.hpp --- 3rdparty/openvx/include/ivx.hpp | 9 +++++++-- samples/openvx/no_wrappers.cpp | 16 ++++++---------- samples/openvx/wrappers.cpp | 20 ++++++++++---------- samples/openvx/wrappers_video.cpp | 29 ++++++++++++++--------------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index c6602e4b99..eafac4c6dc 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -58,6 +58,11 @@ Details: TBD #ifndef IVX_USE_CXX98 #include + namespace ivx + { + using std::is_same; + using std::is_pointer; + } #else namespace ivx { @@ -1517,8 +1522,8 @@ static const vx_enum static Threshold createRange(vx_context c, vx_enum dataType, vx_int32 valLower, vx_int32 valUpper) { Threshold thr = create(c, VX_THRESHOLD_TYPE_RANGE, dataType); - IVX_CHECK_STATUS( vxSetThresholdAttribute(thr.ref, VX_THRESHOLD_THRESHOLD_LOWER, &val1, sizeof(val1)) ); - IVX_CHECK_STATUS( vxSetThresholdAttribute(thr.ref, VX_THRESHOLD_THRESHOLD_UPPER, &val2, sizeof(val2)) ); + IVX_CHECK_STATUS( vxSetThresholdAttribute(thr.ref, VX_THRESHOLD_THRESHOLD_LOWER, &valLower, sizeof(valLower)) ); + IVX_CHECK_STATUS( vxSetThresholdAttribute(thr.ref, VX_THRESHOLD_THRESHOLD_UPPER, &valUpper, sizeof(valUpper)) ); return thr; } diff --git a/samples/openvx/no_wrappers.cpp b/samples/openvx/no_wrappers.cpp index c15367a526..99a8ecab82 100644 --- a/samples/openvx/no_wrappers.cpp +++ b/samples/openvx/no_wrappers.cpp @@ -24,7 +24,7 @@ typedef uintptr_t vx_map_id; enum UserMemoryMode { - COPY, MAP_TO_VX + COPY, USER_MEM }; vx_image convertCvMatToVxImage(vx_context context, cv::Mat image, bool toCopy) @@ -89,11 +89,7 @@ vx_image convertCvMatToVxImage(vx_context context, cv::Mat image, bool toCopy) } else { -#ifdef VX_VERSION_1_1 - ovxImage = vxCreateImageFromHandle(context, color, &addr, (void*const*)&ovxData, VX_MEMORY_TYPE_HOST); -#else ovxImage = vxCreateImageFromHandle(context, color, &addr, (void**)&ovxData, VX_MEMORY_TYPE_HOST); -#endif if (vxGetStatus((vx_reference)ovxImage) != VX_SUCCESS) throw std::runtime_error("Failed to create image from handle"); } @@ -322,7 +318,7 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) cv::waitKey(0); //we need to take user memory back before releasing the image - if (mode == MAP_TO_VX) + if (mode == USER_MEM) swapVxImage(ovxImage); cv::destroyAllWindows(); @@ -339,7 +335,7 @@ int main(int argc, char *argv[]) "{image | | image to be processed}" "{mode | copy | user memory interaction mode: \n" "copy: create VX images and copy data to/from them\n" - "map_to_vx: use handles to user-allocated memory}" + "user_mem: use handles to user-allocated memory}" ; cv::CommandLineParser parser(argc, argv, keys); @@ -357,11 +353,11 @@ int main(int argc, char *argv[]) { mode = COPY; } - else if(modeString == "map_to_vx") + else if(modeString == "user_mem") { - mode = MAP_TO_VX; + mode = USER_MEM; } - else if(modeString == "map_from_vx") + else if(modeString == "map") { std::cerr << modeString << " is not implemented in this sample" << std::endl; return -1; diff --git a/samples/openvx/wrappers.cpp b/samples/openvx/wrappers.cpp index 5dd7da17c7..6655d3fd24 100644 --- a/samples/openvx/wrappers.cpp +++ b/samples/openvx/wrappers.cpp @@ -12,7 +12,7 @@ enum UserMemoryMode { - COPY, MAP_TO_VX, MAP_FROM_VX + COPY, USER_MEM, MAP }; ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage) @@ -91,7 +91,7 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) Image ivxResult; Image::Patch resultPatch; Mat output; - if (mode == COPY || mode == MAP_FROM_VX) + if (mode == COPY || mode == MAP) { //we will copy or map data from vx_image to cv::Mat ivxResult = ivx::Image::create(context, width, height, VX_DF_IMAGE_U8); @@ -116,7 +116,7 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) { ivxResult.copyTo(0, output); } - else if (mode == MAP_FROM_VX) + else if (mode == MAP) { //create cv::Mat based on vx_image mapped data resultPatch.map(ivxResult, 0, ivxResult.getValidRegion()); @@ -144,7 +144,7 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) //we should take user memory back before release //(it's not done automatically according to standard) ivxImage.swapHandle(); - if (mode == MAP_TO_VX) ivxResult.swapHandle(); + if (mode == USER_MEM) ivxResult.swapHandle(); } #endif @@ -173,8 +173,8 @@ int main(int argc, char *argv[]) "{image | | image to be processed}" "{mode | copy | user memory interaction mode: \n" "copy: create VX images and copy data to/from them\n" - "map_to_vx: use handles to user-allocated memory\n" - "map_from_vx: map resulting VX image to user memory}" + "user_mem: use handles to user-allocated memory\n" + "map: map resulting VX image to user memory}" ; cv::CommandLineParser parser(argc, argv, keys); @@ -192,13 +192,13 @@ int main(int argc, char *argv[]) { mode = COPY; } - else if(modeString == "map_to_vx") + else if(modeString == "user_mem") { - mode = MAP_TO_VX; + mode = USER_MEM; } - else if(modeString == "map_from_vx") + else if(modeString == "map") { - mode = MAP_FROM_VX; + mode = MAP; } else { diff --git a/samples/openvx/wrappers_video.cpp b/samples/openvx/wrappers_video.cpp index df7be472db..c24d8ceedb 100644 --- a/samples/openvx/wrappers_video.cpp +++ b/samples/openvx/wrappers_video.cpp @@ -12,7 +12,7 @@ enum UserMemoryMode { - COPY, MAP_TO_VX, MAP_FROM_VX + COPY, USER_MEM, MAP }; ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage) @@ -100,7 +100,7 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) Image ivxResult; Mat output; - if (mode == COPY || mode == MAP_FROM_VX) + if (mode == COPY || mode == MAP) { //we will copy or map data from vx_image to cv::Mat ivxResult = ivx::Image::create(context, width, height, VX_DF_IMAGE_U8); @@ -117,8 +117,6 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) Graph graph = createProcessingGraph(ivxImage, ivxResult); - std::vector ptrs; - bool stop = false; while (!stop) { @@ -129,12 +127,13 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) //getting resulting image in cv::Mat Image::Patch resultPatch; - std::vector prevPtrs; + std::vector ptrs; + std::vector prevPtrs(ivxResult.planes()); if (mode == COPY) { ivxResult.copyTo(0, output); } - else if (mode == MAP_FROM_VX) + else if (mode == MAP) { //create cv::Mat based on vx_image mapped data resultPatch.map(ivxResult, 0, ivxResult.getValidRegion(), VX_READ_AND_WRITE); @@ -156,9 +155,9 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) #ifdef VX_VERSION_1_1 //restore handle - if (mode == MAP_TO_VX) + if (mode == USER_MEM) { - ivxResult.swapHandle(prevPtrs); + ivxResult.swapHandle(prevPtrs, ptrs); } #endif @@ -184,7 +183,7 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) //we should take user memory back before release //(it's not done automatically according to standard) ivxImage.swapHandle(); - if (mode == MAP_TO_VX) ivxResult.swapHandle(); + if (mode == USER_MEM) ivxResult.swapHandle(); } #endif } @@ -210,8 +209,8 @@ int main(int argc, char *argv[]) "{video | | video file to be processed}" "{mode | copy | user memory interaction mode: \n" "copy: create VX images and copy data to/from them\n" - "map_to_vx: use handles to user-allocated memory\n" - "map_from_vx: map resulting VX image to user memory}" + "user_mem: use handles to user-allocated memory\n" + "map: map resulting VX image to user memory}" ; cv::CommandLineParser parser(argc, argv, keys); @@ -229,13 +228,13 @@ int main(int argc, char *argv[]) { mode = COPY; } - else if(modeString == "map_to_vx") + else if(modeString == "user_mem") { - mode = MAP_TO_VX; + mode = USER_MEM; } - else if(modeString == "map_from_vx") + else if(modeString == "map") { - mode = MAP_FROM_VX; + mode = MAP; } else { From e8d2aad978a1c22165c2d1f773ea8537db730b91 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Thu, 17 Nov 2016 19:27:14 +0300 Subject: [PATCH 092/409] sugar for single plane images --- 3rdparty/openvx/include/ivx.hpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index eafac4c6dc..abf266e6db 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -871,6 +871,10 @@ public: static Image createVirtual(vx_graph graph, vx_uint32 width = 0, vx_uint32 height = 0, vx_df_image format = VX_DF_IMAGE_VIRT) { return Image(vxCreateVirtualImage(graph, width, height, format)); } + /// vxCreateUniformImage() wrapper + static Image createUniform(vx_context context, vx_uint32 width, vx_uint32 height, vx_df_image format, const vx_pixel_value_t& value) + { return Image(vxCreateUniformImage(context, width, height, format, &value)); } + /// Planes number for the specified image format (fourcc) /// \return 0 for unknown formats static vx_size planes(vx_df_image format) @@ -954,6 +958,13 @@ public: #endif } + /// vxCreateImageFromHandle() wrapper for a single plane image + static Image createFromHandle(vx_context context, vx_df_image format,const vx_imagepatch_addressing_t& addr, void* ptr) + { + if(planes(format) != 1) throw WrapperError(std::string(__func__)+"(): not a single plane format"); + return Image(vxCreateImageFromHandle(context, format, &addr, &ptr, VX_MEMORY_TYPE_HOST)); + } + #ifdef VX_VERSION_1_1 /// vxSwapImageHandle() wrapper /// \param newPtrs keeps addresses of new image planes data, can be of image planes size or empty when new pointers are not provided @@ -973,6 +984,17 @@ public: num ) ); } + /// vxSwapImageHandle() wrapper for a single plane image + /// \param newPtr an address of new image data, can be zero when new pointer is not provided + /// \return the previuos address of image data + void* swapHandle(void* newPtr) + { + if(planes() != 1) throw WrapperError(std::string(__func__)+"(): not a single plane image"); + void* prevPtr = 0; + IVX_CHECK_STATUS( vxSwapImageHandle(ref, &newPtr, &prevPtr, 1) ); + return prevPtr; + } + /// vxSwapImageHandle() wrapper for the case when no new pointers provided and previous ones are not needed (retrive memory back) void swapHandle() { IVX_CHECK_STATUS( vxSwapImageHandle(ref, 0, 0, 0) ); } @@ -1251,6 +1273,14 @@ static const vx_enum //vx_rectangle_t r = getValidRegion(); copyFrom(planeIdx, createAddressing((vx_uint32)m.cols, (vx_uint32)m.rows, (vx_int32)m.elemSize(), (vx_int32)m.step), m.ptr()); } + + /* + 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"); } + */ #endif //IVX_USE_OPENCV struct Patch; From 2c63d2bd729d3272fd64cb6eaf32d708a6df826c Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Thu, 17 Nov 2016 19:42:58 +0300 Subject: [PATCH 093/409] changed image creation from handle --- samples/openvx/wrappers.cpp | 11 +++-------- samples/openvx/wrappers_video.cpp | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/samples/openvx/wrappers.cpp b/samples/openvx/wrappers.cpp index 6655d3fd24..da75857f5c 100644 --- a/samples/openvx/wrappers.cpp +++ b/samples/openvx/wrappers.cpp @@ -82,10 +82,7 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) } else { - vx_imagepatch_addressing_t addressing = Image::createAddressing(image); - const std::vector addrs(1, addressing); - const std::vector ptrs(1, image.data); - ivxImage = Image::createFromHandle(context, color, addrs, ptrs); + ivxImage = Image::createFromHandle(context, color, Image::createAddressing(image), image.data); } Image ivxResult; @@ -100,10 +97,8 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) { //create vx_image based on user data, no copying required output = cv::Mat(height, width, CV_8U, cv::Scalar(0)); - vx_imagepatch_addressing_t addressing = Image::createAddressing(output); - const std::vector addrs(1, addressing); - const std::vector ptrs(1, output.data); - ivxResult = Image::createFromHandle(context, Image::matTypeToFormat(CV_8U), addrs, ptrs); + ivxResult = Image::createFromHandle(context, Image::matTypeToFormat(CV_8U), + Image::createAddressing(output), output.data); } Graph graph = createProcessingGraph(ivxImage, ivxResult); diff --git a/samples/openvx/wrappers_video.cpp b/samples/openvx/wrappers_video.cpp index c24d8ceedb..d08a845979 100644 --- a/samples/openvx/wrappers_video.cpp +++ b/samples/openvx/wrappers_video.cpp @@ -91,10 +91,7 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) } else { - vx_imagepatch_addressing_t addressing = Image::createAddressing(frame); - const std::vector addrs(1, addressing); - const std::vector ptrs(1, frame.data); - ivxImage = Image::createFromHandle(context, color, addrs, ptrs); + ivxImage = Image::createFromHandle(context, color, Image::createAddressing(frame), frame.data); } Image ivxResult; @@ -109,10 +106,8 @@ int ovxDemo(std::string inputPath, UserMemoryMode mode) { //create vx_image based on user data, no copying required output = cv::Mat(height, width, CV_8U, cv::Scalar(0)); - vx_imagepatch_addressing_t addressing = Image::createAddressing(output); - const std::vector addrs(1, addressing); - const std::vector ptrs(1, output.data); - ivxResult = Image::createFromHandle(context, Image::matTypeToFormat(CV_8U), addrs, ptrs); + ivxResult = Image::createFromHandle(context, Image::matTypeToFormat(CV_8U), + Image::createAddressing(output), output.data); } Graph graph = createProcessingGraph(ivxImage, ivxResult); From 289d879d48d4e8cc8a60f89118558fb904ec1b79 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 18 Nov 2016 15:11:17 +0300 Subject: [PATCH 094/409] build fixes for openvx-1.0.1 and c++03 * fixing 1.0.1 * fixing build without c++11 --- 3rdparty/openvx/include/ivx.hpp | 12 +++++++++--- samples/openvx/wrappers.cpp | 3 +-- samples/openvx/wrappers_video.cpp | 3 +-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index abf266e6db..5162adf379 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -532,7 +532,6 @@ public: operator vx_reference() const { return castToReference(ref); } -#ifdef IVX_USE_CXX98 /// Getting a context that is kept in each OpenVX 'object' (call get()) template C get() const @@ -542,7 +541,8 @@ public: // vxGetContext doesn't increment ref count, let do it in wrapper c-tor return C(c, true); } -#else + +#ifndef IVX_USE_CXX98 /// Getting a context that is kept in each OpenVX 'object' template::value>::type> C getContext() const @@ -871,9 +871,15 @@ public: static Image createVirtual(vx_graph graph, vx_uint32 width = 0, vx_uint32 height = 0, vx_df_image format = VX_DF_IMAGE_VIRT) { return Image(vxCreateVirtualImage(graph, width, height, format)); } +#ifdef VX_VERSION_1_1 /// vxCreateUniformImage() wrapper static Image createUniform(vx_context context, vx_uint32 width, vx_uint32 height, vx_df_image format, const vx_pixel_value_t& value) { return Image(vxCreateUniformImage(context, width, height, format, &value)); } +#else + /// vxCreateUniformImage() wrapper + static Image createUniform(vx_context context, vx_uint32 width, vx_uint32 height, vx_df_image format, const void* value) + { return Image(vxCreateUniformImage(context, width, height, format, value)); } +#endif /// Planes number for the specified image format (fourcc) /// \return 0 for unknown formats @@ -962,7 +968,7 @@ public: static Image createFromHandle(vx_context context, vx_df_image format,const vx_imagepatch_addressing_t& addr, void* ptr) { if(planes(format) != 1) throw WrapperError(std::string(__func__)+"(): not a single plane format"); - return Image(vxCreateImageFromHandle(context, format, &addr, &ptr, VX_MEMORY_TYPE_HOST)); + return Image(vxCreateImageFromHandle(context, format, const_cast (&addr), &ptr, VX_MEMORY_TYPE_HOST)); } #ifdef VX_VERSION_1_1 diff --git a/samples/openvx/wrappers.cpp b/samples/openvx/wrappers.cpp index da75857f5c..32a1442c55 100644 --- a/samples/openvx/wrappers.cpp +++ b/samples/openvx/wrappers.cpp @@ -19,8 +19,7 @@ ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage { using namespace ivx; - Context context = inputImage.getContext(); - + Context context = inputImage.get(); Graph graph = Graph::create(context); vx_uint32 width = inputImage.width(); diff --git a/samples/openvx/wrappers_video.cpp b/samples/openvx/wrappers_video.cpp index d08a845979..35caa2844c 100644 --- a/samples/openvx/wrappers_video.cpp +++ b/samples/openvx/wrappers_video.cpp @@ -19,8 +19,7 @@ ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage { using namespace ivx; - Context context = inputImage.getContext(); - + Context context = inputImage.get(); Graph graph = Graph::create(context); vx_uint32 width = inputImage.width(); From d7db22019695cf4f31eea46ffdd12982b12b2356 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Fri, 18 Nov 2016 16:11:32 +0300 Subject: [PATCH 095/409] "no previous declaration" warnings fixed in samples --- samples/openvx/no_wrappers.cpp | 7 +++++++ samples/openvx/wrappers.cpp | 4 ++++ samples/openvx/wrappers_video.cpp | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/samples/openvx/no_wrappers.cpp b/samples/openvx/no_wrappers.cpp index 99a8ecab82..7809f7b9ca 100644 --- a/samples/openvx/no_wrappers.cpp +++ b/samples/openvx/no_wrappers.cpp @@ -27,6 +27,13 @@ enum UserMemoryMode COPY, USER_MEM }; +vx_image convertCvMatToVxImage(vx_context context, cv::Mat image, bool toCopy); +cv::Mat copyVxImageToCvMat(vx_image ovxImage); +void swapVxImage(vx_image ovxImage); +vx_status createProcessingGraph(vx_image inputImage, vx_image outputImage, vx_graph& graph); +int ovxDemo(std::string inputPath, UserMemoryMode mode); + + vx_image convertCvMatToVxImage(vx_context context, cv::Mat image, bool toCopy) { if (!(!image.empty() && image.dims <= 2 && image.channels() == 1)) diff --git a/samples/openvx/wrappers.cpp b/samples/openvx/wrappers.cpp index 32a1442c55..b591862c10 100644 --- a/samples/openvx/wrappers.cpp +++ b/samples/openvx/wrappers.cpp @@ -15,6 +15,10 @@ enum UserMemoryMode COPY, USER_MEM, MAP }; +ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage); +int ovxDemo(std::string inputPath, UserMemoryMode mode); + + ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage) { using namespace ivx; diff --git a/samples/openvx/wrappers_video.cpp b/samples/openvx/wrappers_video.cpp index 35caa2844c..737f052b85 100644 --- a/samples/openvx/wrappers_video.cpp +++ b/samples/openvx/wrappers_video.cpp @@ -15,6 +15,10 @@ enum UserMemoryMode COPY, USER_MEM, MAP }; +ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage); +int ovxDemo(std::string inputPath, UserMemoryMode mode); + + ivx::Graph createProcessingGraph(ivx::Image& inputImage, ivx::Image& outputImage) { using namespace ivx; From bfecea489e63c5ab29be5666ac18de97aab1e23e Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 18 Nov 2016 16:22:11 +0300 Subject: [PATCH 096/409] warnings in ivx.hpp fixed * fixing 1.0.1 * fixing build without c++11 * hiding warnings --- 3rdparty/openvx/include/ivx.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index 5162adf379..6dc417051d 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -89,8 +89,8 @@ class RuntimeError : public std::runtime_error { public: /// Constructor - explicit RuntimeError(vx_status status, const std::string& msg = "") - : runtime_error(msg), _status(status) + explicit RuntimeError(vx_status st, const std::string& msg = "") + : runtime_error(msg), _status(st) {} /// OpenVX error code @@ -142,7 +142,7 @@ template using EnumToType_t = typename EnumToType::type; #endif /// Gets size in bytes for the provided OpenVX type enum -vx_size enumToTypeSize(vx_enum type) +inline vx_size enumToTypeSize(vx_enum type) { switch (type) { @@ -1314,16 +1314,16 @@ public: { return _mapId; } #else /// reference to vx_rectangle_t for the current mapping - const vx_rectangle_t& rect() const + const vx_rectangle_t& rectangle() const { return _rect; } /// Image plane index for the current mapping - vx_uint32 planeIdx() const + vx_uint32 planeIndex() const { return _planeIdx; } #endif // VX_VERSION_1_1 /// vx_image for the current mapping - vx_image img() const + vx_image image() const { return _img; } /// where this patch is mapped @@ -1393,6 +1393,7 @@ public: IVX_CHECK_STATUS(vxMapImagePatch(img, &rect, planeIdx, &_mapId, &_addr, &_data, usage, _memType, flags) ); #else IVX_CHECK_STATUS(vxAccessImagePatch(img, &rect, planeIdx, &_addr, &_data, usage)); + (void)flags; _rect = rect; _planeIdx = planeIdx; #endif @@ -1565,8 +1566,8 @@ static const vx_enum /// vxQueryThreshold() wrapper template - void query(vx_enum att, T& value) const - { IVX_CHECK_STATUS( vxQueryThreshold(ref, att, &value, sizeof(value)) ); } + void query(vx_enum att, T& val) const + { IVX_CHECK_STATUS( vxQueryThreshold(ref, att, &value, sizeof(val)) ); } /// vxQueryThreshold(VX_THRESHOLD_TYPE) wrapper vx_enum type() const From fa8add8686976d7f006bd10a8e5624f1d692c62f Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Mon, 21 Nov 2016 10:50:29 +0300 Subject: [PATCH 097/409] compilation fixed for C++98 --- 3rdparty/openvx/include/ivx.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index 6dc417051d..cd36e071c7 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -410,7 +410,6 @@ public: { return ref != 0; } #endif -#ifdef IVX_USE_CXX98 /// Getting a context that is kept in each OpenVX 'object' (call get()) template C get() const @@ -420,7 +419,8 @@ public: // vxGetContext doesn't increment ref count, let do it in wrapper c-tor return C(c, true); } -#else + +#ifndef IVX_USE_CXX98 /// Getting a context that is kept in each OpenVX 'object' template::value>::type> C getContext() const @@ -1567,7 +1567,7 @@ static const vx_enum /// vxQueryThreshold() wrapper template void query(vx_enum att, T& val) const - { IVX_CHECK_STATUS( vxQueryThreshold(ref, att, &value, sizeof(val)) ); } + { IVX_CHECK_STATUS( vxQueryThreshold(ref, att, &val, sizeof(val)) ); } /// vxQueryThreshold(VX_THRESHOLD_TYPE) wrapper vx_enum type() const From 1c9262dce4af416c21bdfc869357bc575d718839 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Mon, 21 Nov 2016 12:31:48 +0300 Subject: [PATCH 098/409] hiding silly warnings --- 3rdparty/openvx/include/ivx.hpp | 36 +++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index cd36e071c7..2d3275dada 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -81,6 +81,21 @@ Details: TBD #include "opencv2/core.hpp" #endif +// disabling false alarm warnings +#if defined(_MSC_VER) + #pragma warning(push) + //#pragma warning( disable : 4??? ) +#elif defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunused-local-typedef" + #pragma clang diagnostic ignored "-Wmissing-prototypes" +#elif defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-local-typedefs" + #pragma GCC diagnostic ignored "-Wunused-value" + #pragma GCC diagnostic ignored "-Wmissing-declarations" +#endif // compiler macro + namespace ivx { @@ -273,12 +288,12 @@ template <> struct RefTypeTraits /// Casting to vx_reference with compile-time check // takes 'vx_reference' itself and RefWrapper via 'operator vx_reference()' -vx_reference castToReference(vx_reference ref) +inline vx_reference castToReference(vx_reference ref) { return ref; } // takes vx_reference extensions that have RefTypeTraits specializations template -vx_reference castToReference(const T& ref, typename RefTypeTraits::vxType dummy = 0) +inline vx_reference castToReference(const T& ref, typename RefTypeTraits::vxType dummy = 0) { (void)dummy; return (vx_reference)ref; } #else @@ -302,7 +317,7 @@ struct is_ref::vxTypeEnum, void())> : std::true_typ /// Casting to vx_reference with compile-time check template -vx_reference castToReference(const T& obj) +inline vx_reference castToReference(const T& obj) { static_assert(is_ref::value, "unsupported conversion"); return (vx_reference) obj; @@ -1162,12 +1177,12 @@ static const vx_enum /// vxCopyImagePatch() wrapper (or vxAccessImagePatch() + vxCommitImagePatch() for OpenVX 1.0) void copy( vx_uint32 planeIdx, vx_rectangle_t rect, const vx_imagepatch_addressing_t& addr, void* data, - vx_enum usage, vx_enum memType = VX_MEMORY_TYPE_HOST ) + vx_enum usage, vx_enum memoryType = VX_MEMORY_TYPE_HOST ) { #ifdef VX_VERSION_1_1 - IVX_CHECK_STATUS(vxCopyImagePatch(ref, &rect, planeIdx, &addr, (void*)data, usage, memType)); + IVX_CHECK_STATUS(vxCopyImagePatch(ref, &rect, planeIdx, &addr, (void*)data, usage, memoryType)); #else - (void)memType; + (void)memoryType; vx_imagepatch_addressing_t* a = const_cast(&addr); IVX_CHECK_STATUS(vxAccessImagePatch(ref, &rect, planeIdx, a, &data, usage)); IVX_CHECK_STATUS(vxCommitImagePatch(ref, &rect, planeIdx, a, data)); @@ -1653,4 +1668,13 @@ Node gaussian3x3(vx_graph graph, vx_image inImg, vx_image outImg) } // namespace ivx +// restore warnings +#if defined(_MSC_VER) + #pragma warning(pop) +#elif defined(__clang__) + #pragma clang diagnostic pop +#elif defined(__GNUC__) + #pragma GCC diagnostic pop +#endif // compiler macro + #endif //IVX_HPP From f89ea6d7fa458bd4c03f1a661d13e0c00411dfec Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Tue, 22 Nov 2016 16:27:06 +0300 Subject: [PATCH 099/409] hide info warnings --- samples/openvx/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/openvx/CMakeLists.txt b/samples/openvx/CMakeLists.txt index 3f891f2794..f2a5680f43 100644 --- a/samples/openvx/CMakeLists.txt +++ b/samples/openvx/CMakeLists.txt @@ -15,6 +15,7 @@ if(BUILD_EXAMPLES AND OCV_DEPENDENCIES_FOUND) ocv_include_modules_recurse(${OPENCV_OPENVX_SAMPLE_REQUIRED_DEPS}) add_definitions(-DIVX_USE_OPENCV) + add_definitions(-DIVX_HIDE_INFO_WARNINGS) file(GLOB srcs_wrapped wrappers.cpp *.hpp) file(GLOB srcs_orig no_wrappers.cpp *.hpp) From 284006e336a3b0bb3ce3824f508f2d41a2f21039 Mon Sep 17 00:00:00 2001 From: k-shinotsuka Date: Wed, 23 Nov 2016 19:18:41 +0900 Subject: [PATCH 100/409] add NEON code for RGB2Luv_f. --- modules/imgproc/src/color.cpp | 97 +++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/modules/imgproc/src/color.cpp b/modules/imgproc/src/color.cpp index 9f3e6ac992..6c10e373b8 100644 --- a/modules/imgproc/src/color.cpp +++ b/modules/imgproc/src/color.cpp @@ -141,7 +141,32 @@ template static inline _Tp splineInterpolate(_Tp x, const _Tp* tab return ((tab[3]*x + tab[2])*x + tab[1])*x + tab[0]; } -#if CV_SSE2 +#if CV_NEON +template static inline void splineInterpolate(float32x4_t& v_x, const _Tp* tab, int n) +{ + int32x4_t v_ix = vcvtq_s32_f32(vminq_f32(vmaxq_f32(v_x, vdupq_n_f32(0)), vdupq_n_f32(n - 1))); + v_x = vsubq_f32(v_x, vcvtq_f32_s32(v_ix)); + v_ix = vshlq_n_s32(v_ix, 2); + + int CV_DECL_ALIGNED(16) ix[4]; + vst1q_s32(ix, v_ix); + + float32x4_t v_tab0 = vld1q_f32(tab + ix[0]); + float32x4_t v_tab1 = vld1q_f32(tab + ix[1]); + float32x4_t v_tab2 = vld1q_f32(tab + ix[2]); + float32x4_t v_tab3 = vld1q_f32(tab + ix[3]); + + float32x4x2_t v01 = vtrnq_f32(v_tab0, v_tab1); + float32x4x2_t v23 = vtrnq_f32(v_tab2, v_tab3); + + v_tab0 = vcombine_f32(vget_low_f32(v01.val[0]), vget_low_f32(v23.val[0])); + v_tab1 = vcombine_f32(vget_low_f32(v01.val[1]), vget_low_f32(v23.val[1])); + v_tab2 = vcombine_f32(vget_high_f32(v01.val[0]), vget_high_f32(v23.val[0])); + v_tab3 = vcombine_f32(vget_high_f32(v01.val[1]), vget_high_f32(v23.val[1])); + + v_x = vmlaq_f32(v_tab0, vmlaq_f32(v_tab1, vmlaq_f32(v_tab2, v_tab3, v_x), v_x), v_x); +} +#elif CV_SSE2 template static inline void splineInterpolate(__m128& v_x, const _Tp* tab, int n) { __m128i v_ix = _mm_cvttps_epi32(_mm_min_ps(_mm_max_ps(v_x, _mm_setzero_ps()), _mm_set1_ps(float(n - 1)))); @@ -5968,7 +5993,28 @@ struct RGB2Luv_f CV_Assert(whitept[1] == 1.f); } - #if CV_SSE2 + #if CV_NEON + void process(float32x4x3_t& v_src) const + { + float32x4_t v_x = vmlaq_f32(vmlaq_f32(vmulq_f32(v_src.val[0], vdupq_n_f32(coeffs[0])), v_src.val[1], vdupq_n_f32(coeffs[1])), v_src.val[2], vdupq_n_f32(coeffs[2])); + float32x4_t v_y = vmlaq_f32(vmlaq_f32(vmulq_f32(v_src.val[0], vdupq_n_f32(coeffs[3])), v_src.val[1], vdupq_n_f32(coeffs[4])), v_src.val[2], vdupq_n_f32(coeffs[5])); + float32x4_t v_z = vmlaq_f32(vmlaq_f32(vmulq_f32(v_src.val[0], vdupq_n_f32(coeffs[6])), v_src.val[1], vdupq_n_f32(coeffs[7])), v_src.val[2], vdupq_n_f32(coeffs[8])); + + v_src.val[0] = vmulq_f32(v_y, vdupq_n_f32(LabCbrtTabScale)); + splineInterpolate(v_src.val[0], LabCbrtTab, LAB_CBRT_TAB_SIZE); + + v_src.val[0] = vmlaq_f32(vdupq_n_f32(-16.f), v_src.val[0], vdupq_n_f32(116.f)); + + float32x4_t v_div = vmaxq_f32(vmlaq_f32(vmlaq_f32(v_x, vdupq_n_f32(15.f), v_y), vdupq_n_f32(3.f), v_z), vdupq_n_f32(FLT_EPSILON)); + float32x4_t v_reciprocal = vrecpeq_f32(v_div); + v_reciprocal = vmulq_f32(vrecpsq_f32(v_div, v_reciprocal), v_reciprocal); + v_reciprocal = vmulq_f32(vrecpsq_f32(v_div, v_reciprocal), v_reciprocal); + float32x4_t v_d = vmulq_f32(vdupq_n_f32(52.f), v_reciprocal); + + v_src.val[1] = vmulq_f32(v_src.val[0], vmlaq_f32(vdupq_n_f32(-un), v_x, v_d)); + v_src.val[2] = vmulq_f32(v_src.val[0], vmlaq_f32(vdupq_n_f32(-vn), vmulq_f32(vdupq_n_f32(2.25f), v_y), v_d)); + } + #elif CV_SSE2 void process(__m128& v_r0, __m128& v_r1, __m128& v_g0, __m128& v_g1, __m128& v_b0, __m128& v_b1) const { @@ -6042,7 +6088,52 @@ struct RGB2Luv_f C6 = coeffs[6], C7 = coeffs[7], C8 = coeffs[8]; n *= 3; - #if CV_SSE2 + #if CV_NEON + if (scn == 3) + { + for( ; i <= n - 12; i += 12, src += scn * 4 ) + { + float32x4x3_t v_src = vld3q_f32(src); + if( gammaTab ) + { + v_src.val[0] = vmulq_f32(v_src.val[0], vdupq_n_f32(gscale)); + v_src.val[1] = vmulq_f32(v_src.val[1], vdupq_n_f32(gscale)); + v_src.val[2] = vmulq_f32(v_src.val[2], vdupq_n_f32(gscale)); + splineInterpolate(v_src.val[0], gammaTab, GAMMA_TAB_SIZE); + splineInterpolate(v_src.val[1], gammaTab, GAMMA_TAB_SIZE); + splineInterpolate(v_src.val[2], gammaTab, GAMMA_TAB_SIZE); + } + + process(v_src); + + vst3q_f32(dst + i, v_src); + } + } + else + { + for( ; i <= n - 12; i += 12, src += scn * 4 ) + { + float32x4x4_t v_src = vld4q_f32(src); + if( gammaTab ) + { + v_src.val[0] = vmulq_f32(v_src.val[0], vdupq_n_f32(gscale)); + v_src.val[1] = vmulq_f32(v_src.val[1], vdupq_n_f32(gscale)); + v_src.val[2] = vmulq_f32(v_src.val[2], vdupq_n_f32(gscale)); + splineInterpolate(v_src.val[0], gammaTab, GAMMA_TAB_SIZE); + splineInterpolate(v_src.val[1], gammaTab, GAMMA_TAB_SIZE); + splineInterpolate(v_src.val[2], gammaTab, GAMMA_TAB_SIZE); + } + + float32x4x3_t v_dst; + v_dst.val[0] = v_src.val[0]; + v_dst.val[1] = v_src.val[1]; + v_dst.val[2] = v_src.val[2]; + process(v_dst); + + vst3q_f32(dst + i, v_dst); + } + } + #elif CV_SSE2 if (haveSIMD) { for( ; i <= n - 24; i += 24, src += scn * 8 ) From 69949025db6d7fa5be370de736b0d029101567c2 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 15 Nov 2016 22:40:55 +0300 Subject: [PATCH 101/409] core: drop type/dims/rows/cols information in Mat::release() --- modules/core/include/opencv2/core/mat.inl.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/core/include/opencv2/core/mat.inl.hpp b/modules/core/include/opencv2/core/mat.inl.hpp index 178db1b882..31638afd01 100644 --- a/modules/core/include/opencv2/core/mat.inl.hpp +++ b/modules/core/include/opencv2/core/mat.inl.hpp @@ -689,6 +689,16 @@ void Mat::release() datastart = dataend = datalimit = data = 0; for(int i = 0; i < dims; i++) size.p[i] = 0; +#ifdef _DEBUG + flags = MAGIC_VAL; + dims = rows = cols = 0; + if(step.p != step.buf) + { + fastFree(step.p); + step.p = step.buf; + size.p = &rows; + } +#endif } inline From a936cb087e71e386c9a00e7c1e0544b8b76b15cc Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 15 Nov 2016 22:25:42 +0300 Subject: [PATCH 102/409] calib3d: fix fisheye for test fisheyeTest.stereoCalibrateFixIntrinsic --- modules/calib3d/src/fisheye.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 0a8db58d83..32dc15c3f2 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -1040,8 +1040,10 @@ double cv::fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO int b = cv::countNonZero(intrinsicRight.isEstimate); cv::Mat deltas; solve(J.t() * J, J.t()*e, deltas); - intrinsicLeft = intrinsicLeft + deltas.rowRange(0, a); - intrinsicRight = intrinsicRight + deltas.rowRange(a, a + b); + if (a > 0) + intrinsicLeft = intrinsicLeft + deltas.rowRange(0, a); + if (b > 0) + intrinsicRight = intrinsicRight + deltas.rowRange(a, a + b); omcur = omcur + cv::Vec3d(deltas.rowRange(a + b, a + b + 3)); Tcur = Tcur + cv::Vec3d(deltas.rowRange(a + b + 3, a + b + 6)); for (int image_idx = 0; image_idx < n_images; ++image_idx) From d65df72754a5e8fead86539f81bd9a46e116431c Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 15 Nov 2016 19:24:03 +0300 Subject: [PATCH 103/409] java: fix testIsSubmatrix test --- modules/core/misc/java/test/MatTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/misc/java/test/MatTest.java b/modules/core/misc/java/test/MatTest.java index a2570f4342..3ff0c8be07 100644 --- a/modules/core/misc/java/test/MatTest.java +++ b/modules/core/misc/java/test/MatTest.java @@ -494,7 +494,7 @@ public class MatTest extends OpenCVTestCase { public void testIsSubmatrix() { assertFalse(gray0.isSubmatrix()); - Mat subMat = gray0.submat(0, 0, gray0.rows() / 2, gray0.cols() / 2); + Mat subMat = gray0.submat(0, gray0.rows() / 2, 0, gray0.cols() / 2); assertTrue(subMat.isSubmatrix()); } From fe29080d59cb86d4dd06b2aa6b0b44e969130cdd Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 22 Nov 2016 19:49:55 +0300 Subject: [PATCH 104/409] java: skip test in case of missed classes from opencv_contrib --- .../src/org/opencv/test/OpenCVTestCase.java | 39 ++++++++++++++++++- modules/java/pure_test/build.xml | 2 +- .../src/org/opencv/test/OpenCVTestCase.java | 39 ++++++++++++++++++- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java b/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java index 54bb79ec1e..70d0744948 100644 --- a/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java +++ b/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java @@ -29,6 +29,11 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; public class OpenCVTestCase extends TestCase { + + public static class TestSkipException extends RuntimeException { + public TestSkipException() {} + } + //change to 'true' to unblock fail on fail("Not yet implemented") public static final boolean passNYI = true; @@ -188,12 +193,40 @@ public class OpenCVTestCase extends TestCase { protected void runTest() throws Throwable { // Do nothing if the precondition does not hold. if (isTestCaseEnabled) { - super.runTest(); + try { + super.runTest(); + } catch (TestSkipException ex) { + Log.w(TAG, "Test case \"" + this.getClass().getName() + "\" skipped!"); + assertTrue(true); + } } else { Log.e(TAG, "Test case \"" + this.getClass().getName() + "\" disabled!"); } } + public void runBare() throws Throwable { + Throwable exception = null; + try { + setUp(); + } catch (TestSkipException ex) { + Log.w(TAG, "Test case \"" + this.getClass().getName() + "\" skipped!"); + assertTrue(true); + return; + } + try { + runTest(); + } catch (Throwable running) { + exception = running; + } finally { + try { + tearDown(); + } catch (Throwable tearingDown) { + if (exception == null) exception = tearingDown; + } + } + if (exception != null) throw exception; + } + protected Mat getMat(int type, double... vals) { return new Mat(matSize, matSize, type, new Scalar(vals)); @@ -497,6 +530,10 @@ public class OpenCVTestCase extends TestCase { } } catch(Exception ex) { + if (cname.startsWith(XFEATURES2D)) + { + throw new TestSkipException(); + } message = TAG + " :: " + "could not instantiate " + cname + "! Exception: " + ex.getMessage(); } diff --git a/modules/java/pure_test/build.xml b/modules/java/pure_test/build.xml index 4b25a3c1e3..dac4d4b455 100644 --- a/modules/java/pure_test/build.xml +++ b/modules/java/pure_test/build.xml @@ -37,7 +37,7 @@ - + diff --git a/modules/java/pure_test/src/org/opencv/test/OpenCVTestCase.java b/modules/java/pure_test/src/org/opencv/test/OpenCVTestCase.java index 98b1448fe3..400338ddd4 100644 --- a/modules/java/pure_test/src/org/opencv/test/OpenCVTestCase.java +++ b/modules/java/pure_test/src/org/opencv/test/OpenCVTestCase.java @@ -27,6 +27,11 @@ import org.opencv.core.KeyPoint; import org.opencv.imgcodecs.Imgcodecs; public class OpenCVTestCase extends TestCase { + + public static class TestSkipException extends RuntimeException { + public TestSkipException() {} + } + //change to 'true' to unblock fail on fail("Not yet implemented") public static final boolean passNYI = true; @@ -214,12 +219,40 @@ public class OpenCVTestCase extends TestCase { protected void runTest() throws Throwable { // Do nothing if the precondition does not hold. if (isTestCaseEnabled) { - super.runTest(); + try { + super.runTest(); + } catch (TestSkipException ex) { + OpenCVTestRunner.Log(TAG + " :: " + "Test case \"" + this.getClass().getName() + "\" skipped!"); + assertTrue(true); + } } else { OpenCVTestRunner.Log(TAG + " :: " + "Test case \"" + this.getClass().getName() + "\" disabled!"); } } + public void runBare() throws Throwable { + Throwable exception = null; + try { + setUp(); + } catch (TestSkipException ex) { + OpenCVTestRunner.Log(TAG + " :: " + "Test case \"" + this.getClass().getName() + "\" skipped!"); + assertTrue(true); + return; + } + try { + runTest(); + } catch (Throwable running) { + exception = running; + } finally { + try { + tearDown(); + } catch (Throwable tearingDown) { + if (exception == null) exception = tearingDown; + } + } + if (exception != null) throw exception; + } + protected Mat getMat(int type, double... vals) { return new Mat(matSize, matSize, type, new Scalar(vals)); @@ -523,6 +556,10 @@ public class OpenCVTestCase extends TestCase { } } catch(Exception ex) { + if (cname.startsWith(XFEATURES2D)) + { + throw new TestSkipException(); + } message = TAG + " :: " + "could not instantiate " + cname + "! Exception: " + ex.getMessage(); } From 8019498c6ed26948e72178e877c9125b26a4bfcb Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 22 Nov 2016 20:08:27 +0300 Subject: [PATCH 105/409] java tests fixes --- modules/core/misc/java/test/CoreTest.java | 2 +- modules/imgproc/misc/java/test/ImgprocTest.java | 6 +++--- .../android_test/src/org/opencv/test/OpenCVTestCase.java | 8 ++++++-- .../pure_test/src/org/opencv/test/OpenCVTestCase.java | 8 ++++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/modules/core/misc/java/test/CoreTest.java b/modules/core/misc/java/test/CoreTest.java index 9c60fd5652..3187e7cd2d 100644 --- a/modules/core/misc/java/test/CoreTest.java +++ b/modules/core/misc/java/test/CoreTest.java @@ -1777,7 +1777,7 @@ public class CoreTest extends OpenCVTestCase { }; Mat roots = new Mat(); - assertEquals(0.0, Core.solvePoly(coeffs, roots)); + assertGE(1e-6, Math.abs(Core.solvePoly(coeffs, roots))); truth = new Mat(3, 1, CvType.CV_32FC2) { { diff --git a/modules/imgproc/misc/java/test/ImgprocTest.java b/modules/imgproc/misc/java/test/ImgprocTest.java index e619d016c6..8a5ce2b727 100644 --- a/modules/imgproc/misc/java/test/ImgprocTest.java +++ b/modules/imgproc/misc/java/test/ImgprocTest.java @@ -359,7 +359,7 @@ public class ImgprocTest extends OpenCVTestCase { double distance = Imgproc.compareHist(H1, H2, Imgproc.CV_COMP_CORREL); - assertEquals(1., distance); + assertEquals(1., distance, EPS); } public void testContourAreaMat() { @@ -368,7 +368,7 @@ public class ImgprocTest extends OpenCVTestCase { double area = Imgproc.contourArea(contour); - assertEquals(45., area); + assertEquals(45., area, EPS); } public void testContourAreaMatBoolean() { @@ -377,7 +377,7 @@ public class ImgprocTest extends OpenCVTestCase { double area = Imgproc.contourArea(contour, true); - assertEquals(45., area); + assertEquals(45., area, EPS); // TODO_: write better test } diff --git a/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java b/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java index 70d0744948..2cd2b86155 100644 --- a/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java +++ b/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java @@ -244,6 +244,10 @@ public class OpenCVTestCase extends TestCase { TestCase.fail(msg); } + public static void assertGE(double v1, double v2) { + assertTrue("Failed: " + v1 + " >= " + v2, v1 >= v2); + } + public static void assertListEquals(List list1, List list2) { if (list1.size() != list2.size()) { throw new UnsupportedOperationException(); @@ -458,10 +462,10 @@ public class OpenCVTestCase extends TestCase { if (isEqualityMeasured) assertTrue("Max difference between expected and actiual Mats is "+ maxDiff + ", that bigger than " + eps, - Core.checkRange(diff, true, 0.0, eps)); + maxDiff <= eps); else assertFalse("Max difference between expected and actiual Mats is "+ maxDiff + ", that less than " + eps, - Core.checkRange(diff, true, 0.0, eps)); + maxDiff <= eps); } protected static String readFile(String path) { diff --git a/modules/java/pure_test/src/org/opencv/test/OpenCVTestCase.java b/modules/java/pure_test/src/org/opencv/test/OpenCVTestCase.java index 400338ddd4..f369bb1783 100644 --- a/modules/java/pure_test/src/org/opencv/test/OpenCVTestCase.java +++ b/modules/java/pure_test/src/org/opencv/test/OpenCVTestCase.java @@ -270,6 +270,10 @@ public class OpenCVTestCase extends TestCase { TestCase.fail(msg); } + public static void assertGE(double v1, double v2) { + assertTrue("Failed: " + v1 + " >= " + v2, v1 >= v2); + } + public static void assertListEquals(List list1, List list2) { if (list1.size() != list2.size()) { throw new UnsupportedOperationException(); @@ -484,10 +488,10 @@ public class OpenCVTestCase extends TestCase { if (isEqualityMeasured) assertTrue("Max difference between expected and actiual Mats is "+ maxDiff + ", that bigger than " + eps, - Core.checkRange(diff, true, 0.0, eps)); + maxDiff <= eps); else assertFalse("Max difference between expected and actiual Mats is "+ maxDiff + ", that less than " + eps, - Core.checkRange(diff, true, 0.0, eps)); + maxDiff <= eps); } protected static String readFile(String path) { From 0bdea2b7148e57b22db66afb7d7b854e737f3fda Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 23 Nov 2016 13:43:08 +0300 Subject: [PATCH 106/409] core: fix absdiff (non-optimized, fp) to prevent "-0" results --- modules/core/src/arithm_core.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/core/src/arithm_core.hpp b/modules/core/src/arithm_core.hpp index b92d47a817..99b564cf74 100644 --- a/modules/core/src/arithm_core.hpp +++ b/modules/core/src/arithm_core.hpp @@ -97,6 +97,22 @@ template struct OpAbsDiff T operator()(T a, T b) const { return a > b ? a - b : b - a; } }; +// specializations to prevent "-0" results +template<> struct OpAbsDiff +{ + typedef float type1; + typedef float type2; + typedef float rtype; + float operator()(float a, float b) const { return std::abs(a - b); } +}; +template<> struct OpAbsDiff +{ + typedef double type1; + typedef double type2; + typedef double rtype; + double operator()(double a, double b) const { return std::abs(a - b); } +}; + template struct OpAnd { typedef T type1; From 8e22b1791964c37293981a350395e76d0f919f31 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 23 Nov 2016 14:20:06 +0300 Subject: [PATCH 107/409] java: backport test changes from master --- .../src/org/opencv/test/OpenCVTestCase.java | 43 +++++++++++++++++-- .../src/org/opencv/test/core/CoreTest.java | 2 +- .../src/org/opencv/test/core/MatTest.java | 4 +- .../org/opencv/test/imgproc/ImgprocTest.java | 8 ++-- modules/java/test/build.xml | 16 +++++-- .../src/org/opencv/test/OpenCVTestCase.java | 43 +++++++++++++++++-- 6 files changed, 99 insertions(+), 17 deletions(-) diff --git a/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java b/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java index 5c932110d3..198dac2ac1 100644 --- a/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java +++ b/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java @@ -26,6 +26,11 @@ import org.opencv.highgui.Highgui; import android.util.Log; public class OpenCVTestCase extends TestCase { + + public static class TestSkipException extends RuntimeException { + public TestSkipException() {} + } + //change to 'true' to unblock fail on fail("Not yet implemented") public static final boolean passNYI = true; @@ -182,12 +187,40 @@ public class OpenCVTestCase extends TestCase { protected void runTest() throws Throwable { // Do nothing if the precondition does not hold. if (isTestCaseEnabled) { - super.runTest(); + try { + super.runTest(); + } catch (TestSkipException ex) { + Log.w(TAG, "Test case \"" + this.getClass().getName() + "\" skipped!"); + assertTrue(true); + } } else { Log.e(TAG, "Test case \"" + this.getClass().getName() + "\" disabled!"); } } + public void runBare() throws Throwable { + Throwable exception = null; + try { + setUp(); + } catch (TestSkipException ex) { + Log.w(TAG, "Test case \"" + this.getClass().getName() + "\" skipped!"); + assertTrue(true); + return; + } + try { + runTest(); + } catch (Throwable running) { + exception = running; + } finally { + try { + tearDown(); + } catch (Throwable tearingDown) { + if (exception == null) exception = tearingDown; + } + } + if (exception != null) throw exception; + } + protected Mat getMat(int type, double... vals) { return new Mat(matSize, matSize, type, new Scalar(vals)); @@ -205,6 +238,10 @@ public class OpenCVTestCase extends TestCase { TestCase.fail(msg); } + public static void assertGE(double v1, double v2) { + assertTrue("Failed: " + v1 + " >= " + v2, v1 >= v2); + } + public static void assertListEquals(List list1, List list2) { if (list1.size() != list2.size()) { throw new UnsupportedOperationException(); @@ -419,10 +456,10 @@ public class OpenCVTestCase extends TestCase { if (isEqualityMeasured) assertTrue("Max difference between expected and actiual Mats is "+ maxDiff + ", that bigger than " + eps, - Core.checkRange(diff, true, 0.0, eps)); + maxDiff <= eps); else assertFalse("Max difference between expected and actiual Mats is "+ maxDiff + ", that less than " + eps, - Core.checkRange(diff, true, 0.0, eps)); + maxDiff <= eps); } protected static String readFile(String path) { diff --git a/modules/java/android_test/src/org/opencv/test/core/CoreTest.java b/modules/java/android_test/src/org/opencv/test/core/CoreTest.java index 45e435330c..83511b995b 100644 --- a/modules/java/android_test/src/org/opencv/test/core/CoreTest.java +++ b/modules/java/android_test/src/org/opencv/test/core/CoreTest.java @@ -2052,7 +2052,7 @@ public class CoreTest extends OpenCVTestCase { }; Mat roots = new Mat(); - assertEquals(0.0, Core.solvePoly(coeffs, roots)); + assertGE(1e-6, Math.abs(Core.solvePoly(coeffs, roots))); truth = new Mat(3, 1, CvType.CV_32FC2) { { diff --git a/modules/java/android_test/src/org/opencv/test/core/MatTest.java b/modules/java/android_test/src/org/opencv/test/core/MatTest.java index a2570f4342..610f0eb7eb 100644 --- a/modules/java/android_test/src/org/opencv/test/core/MatTest.java +++ b/modules/java/android_test/src/org/opencv/test/core/MatTest.java @@ -488,13 +488,13 @@ public class MatTest extends OpenCVTestCase { public void testIsContinuous() { assertTrue(gray0.isContinuous()); - Mat subMat = gray0.submat(0, 0, gray0.rows() / 2, gray0.cols() / 2); + Mat subMat = gray0.submat(0, gray0.rows() / 2, 0, gray0.cols() / 2); assertFalse(subMat.isContinuous()); } public void testIsSubmatrix() { assertFalse(gray0.isSubmatrix()); - Mat subMat = gray0.submat(0, 0, gray0.rows() / 2, gray0.cols() / 2); + Mat subMat = gray0.submat(0, gray0.rows() / 2, 0, gray0.cols() / 2); assertTrue(subMat.isSubmatrix()); } diff --git a/modules/java/android_test/src/org/opencv/test/imgproc/ImgprocTest.java b/modules/java/android_test/src/org/opencv/test/imgproc/ImgprocTest.java index 7eed06abd9..ac2fc18d26 100644 --- a/modules/java/android_test/src/org/opencv/test/imgproc/ImgprocTest.java +++ b/modules/java/android_test/src/org/opencv/test/imgproc/ImgprocTest.java @@ -165,7 +165,7 @@ public class ImgprocTest extends OpenCVTestCase { double arcLength = Imgproc.arcLength(curve, false); - assertEquals(5.656854152679443, arcLength); + assertEquals(5.656854152679443, arcLength, EPS); } public void testBilateralFilterMatMatIntDoubleDouble() { @@ -367,7 +367,7 @@ public class ImgprocTest extends OpenCVTestCase { double distance = Imgproc.compareHist(H1, H2, Imgproc.CV_COMP_CORREL); - assertEquals(1., distance); + assertEquals(1., distance, EPS); } public void testContourAreaMat() { @@ -376,7 +376,7 @@ public class ImgprocTest extends OpenCVTestCase { double area = Imgproc.contourArea(contour); - assertEquals(45., area); + assertEquals(45., area, EPS); } public void testContourAreaMatBoolean() { @@ -385,7 +385,7 @@ public class ImgprocTest extends OpenCVTestCase { double area = Imgproc.contourArea(contour, true); - assertEquals(45., area); + assertEquals(45., area, EPS); // TODO_: write better test } diff --git a/modules/java/test/build.xml b/modules/java/test/build.xml index d3f826cf00..dac4d4b455 100644 --- a/modules/java/test/build.xml +++ b/modules/java/test/build.xml @@ -1,5 +1,7 @@ + + @@ -12,7 +14,7 @@ - + @@ -34,8 +36,8 @@ - - + + @@ -45,12 +47,18 @@ - + + + + + + + diff --git a/modules/java/test/src/org/opencv/test/OpenCVTestCase.java b/modules/java/test/src/org/opencv/test/OpenCVTestCase.java index a9b5041189..df0bb04cfa 100644 --- a/modules/java/test/src/org/opencv/test/OpenCVTestCase.java +++ b/modules/java/test/src/org/opencv/test/OpenCVTestCase.java @@ -28,6 +28,11 @@ import org.opencv.features2d.KeyPoint; import org.opencv.highgui.Highgui; public class OpenCVTestCase extends TestCase { + + public static class TestSkipException extends RuntimeException { + public TestSkipException() {} + } + //change to 'true' to unblock fail on fail("Not yet implemented") public static final boolean passNYI = true; @@ -212,12 +217,40 @@ public class OpenCVTestCase extends TestCase { protected void runTest() throws Throwable { // Do nothing if the precondition does not hold. if (isTestCaseEnabled) { - super.runTest(); + try { + super.runTest(); + } catch (TestSkipException ex) { + OpenCVTestRunner.Log(TAG + " :: " + "Test case \"" + this.getClass().getName() + "\" skipped!"); + assertTrue(true); + } } else { OpenCVTestRunner.Log(TAG + " :: " + "Test case \"" + this.getClass().getName() + "\" disabled!"); } } + public void runBare() throws Throwable { + Throwable exception = null; + try { + setUp(); + } catch (TestSkipException ex) { + OpenCVTestRunner.Log(TAG + " :: " + "Test case \"" + this.getClass().getName() + "\" skipped!"); + assertTrue(true); + return; + } + try { + runTest(); + } catch (Throwable running) { + exception = running; + } finally { + try { + tearDown(); + } catch (Throwable tearingDown) { + if (exception == null) exception = tearingDown; + } + } + if (exception != null) throw exception; + } + protected Mat getMat(int type, double... vals) { return new Mat(matSize, matSize, type, new Scalar(vals)); @@ -235,6 +268,10 @@ public class OpenCVTestCase extends TestCase { TestCase.fail(msg); } + public static void assertGE(double v1, double v2) { + assertTrue("Failed: " + v1 + " >= " + v2, v1 >= v2); + } + public static void assertListEquals(List list1, List list2) { if (list1.size() != list2.size()) { throw new UnsupportedOperationException(); @@ -449,10 +486,10 @@ public class OpenCVTestCase extends TestCase { if (isEqualityMeasured) assertTrue("Max difference between expected and actiual Mats is "+ maxDiff + ", that bigger than " + eps, - Core.checkRange(diff, true, 0.0, eps)); + maxDiff <= eps); else assertFalse("Max difference between expected and actiual Mats is "+ maxDiff + ", that less than " + eps, - Core.checkRange(diff, true, 0.0, eps)); + maxDiff <= eps); } protected static String readFile(String path) { From b879126584d1f3f8d2422e15047cf6bbb6500f1d Mon Sep 17 00:00:00 2001 From: monoid911 Date: Wed, 23 Nov 2016 20:43:52 +0900 Subject: [PATCH 108/409] Fix docs explaining the returnPoints argument of convexHull. --- modules/imgproc/include/opencv2/imgproc.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 2a3173c8df..007a238d67 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -3930,8 +3930,8 @@ to the right, and its Y axis pointing upwards. @param returnPoints Operation flag. In case of a matrix, when the flag is true, the function returns convex hull points. Otherwise, it returns indices of the convex hull points. When the output array is std::vector, the flag is ignored, and the output depends on the type of the -vector: std::vector\ implies returnPoints=true, std::vector\ implies -returnPoints=false. +vector: std::vector\ implies returnPoints=false, std::vector\ implies +returnPoints=true. */ CV_EXPORTS_W void convexHull( InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true ); From a3bfa6f566da184ddceba6583f357125a2f21b9a Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 8 Nov 2016 18:00:03 +0300 Subject: [PATCH 109/409] cmake: ccache --- CMakeLists.txt | 2 ++ cmake/OpenCVCompilerOptions.cmake | 34 +++++++++++++++++++++++++++++ cmake/OpenCVDetectCXXCompiler.cmake | 4 ---- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 007b80d4da..91a6462012 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,7 @@ OCV_OPTION(INSTALL_TESTS "Install accuracy and performance test binar # OpenCV build options # =================================================== +OCV_OPTION(ENABLE_CCACHE "Use ccache" (UNIX AND NOT IOS AND (CMAKE_GENERATOR MATCHES "Makefile" OR CMAKE_GENERATOR MATCHES "Ninja")) ) OCV_OPTION(ENABLE_DYNAMIC_CUDA "Enabled dynamic CUDA linkage" ON IF ANDROID ) OCV_OPTION(ENABLE_PRECOMPILED_HEADERS "Use precompiled headers" ON IF (NOT IOS) ) OCV_OPTION(ENABLE_SOLUTION_FOLDERS "Solution folder in Visual Studio or in other IDEs" (MSVC_IDE OR CMAKE_GENERATOR MATCHES Xcode) IF (CMAKE_VERSION VERSION_GREATER "2.8.0") ) @@ -731,6 +732,7 @@ else() status(" Linker flags (Release):" ${CMAKE_SHARED_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS_RELEASE}) status(" Linker flags (Debug):" ${CMAKE_SHARED_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}) endif() +status(" ccache:" CMAKE_COMPILER_IS_CCACHE THEN YES ELSE NO) status(" Precompiled headers:" PCHSupport_FOUND AND ENABLE_PRECOMPILED_HEADERS THEN YES ELSE NO) # ========================== OpenCV modules ========================== diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index 499061e28c..57d142eb0b 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -1,3 +1,33 @@ +if(ENABLE_CCACHE AND NOT CMAKE_COMPILER_IS_CCACHE) + # This works fine with Unix Makefiles and Ninja generators + find_host_program(CCACHE_PROGRAM ccache) + if(CCACHE_PROGRAM) + message(STATUS "Looking for ccache - found (${CCACHE_PROGRAM})") + get_property(__OLD_RULE_LAUNCH_COMPILE GLOBAL PROPERTY RULE_LAUNCH_COMPILE) + if(__OLD_RULE_LAUNCH_COMPILE) + message(STATUS "Can't replace CMake compiler launcher") + else() + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") + # NOTE: Actually this check doesn't work as expected. + # "RULE_LAUNCH_COMPILE" is ignored by CMake during try_compile() step. + # ocv_check_compiler_flag(CXX "" IS_CCACHE_WORKS) + set(IS_CCACHE_WORKS 1) + if(IS_CCACHE_WORKS) + set(CMAKE_COMPILER_IS_CCACHE 1) + else() + message(STATUS "Unable to compile program with enabled ccache, reverting...") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${__OLD_RULE_LAUNCH_COMPILE}") + endif() + else() + message(STATUS "Looking for ccache - not found") + endif() + endif() +endif() + +if((CMAKE_COMPILER_IS_CLANGCXX OR CMAKE_COMPILER_IS_CLANGCC OR CMAKE_COMPILER_IS_CCACHE) AND NOT CMAKE_GENERATOR MATCHES "Xcode") + set(ENABLE_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE) +endif() + if(MINGW OR (X86 AND UNIX AND NOT APPLE)) # mingw compiler is known to produce unstable SSE code with -O3 hence we are trying to use -O2 instead if(CMAKE_COMPILER_IS_GNUCXX) @@ -111,6 +141,10 @@ if(CMAKE_COMPILER_IS_GNUCXX) add_extra_compiler_option(-pthread) endif() + if(CMAKE_COMPILER_IS_CLANGCXX) + add_extra_compiler_option(-Qunused-arguments) + endif() + if(OPENCV_WARNINGS_ARE_ERRORS) add_extra_compiler_option(-Werror) endif() diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake index aa7a8b6a6f..b53f8d7363 100644 --- a/cmake/OpenCVDetectCXXCompiler.cmake +++ b/cmake/OpenCVDetectCXXCompiler.cmake @@ -14,10 +14,6 @@ if(CMAKE_C_COMPILER_ID STREQUAL "Clang") set(CMAKE_COMPILER_IS_CLANGCC 1) endif() -if((CMAKE_COMPILER_IS_CLANGCXX OR CMAKE_COMPILER_IS_CLANGCC) AND NOT CMAKE_GENERATOR MATCHES "Xcode") - set(ENABLE_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE) -endif() - # ---------------------------------------------------------------------------- # Detect Intel ICC compiler -- for -fPIC in 3rdparty ( UNIX ONLY ): # see include/opencv/cxtypes.h file for related ICC & CV_ICC defines. From da75d129fe0e5238f9263e4cd6339b446ba8d3ad Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 15 Nov 2016 16:13:36 +0300 Subject: [PATCH 110/409] java: fix Calib3d test --- .../org/opencv/test/calib3d/Calib3dTest.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/modules/java/android_test/src/org/opencv/test/calib3d/Calib3dTest.java b/modules/java/android_test/src/org/opencv/test/calib3d/Calib3dTest.java index db806b6fc9..b79a99625b 100644 --- a/modules/java/android_test/src/org/opencv/test/calib3d/Calib3dTest.java +++ b/modules/java/android_test/src/org/opencv/test/calib3d/Calib3dTest.java @@ -249,9 +249,22 @@ public class Calib3dTest extends OpenCVTestCase { Mat fm = Calib3d.findFundamentalMat(pts, pts); - truth = new Mat(3, 3, CvType.CV_64F); - truth.put(0, 0, 0, -0.577, 0.288, 0.577, 0, 0.288, -0.288, -0.288, 0); - assertMatEqual(truth, fm, EPS); + // Check definition of fundamental matrix: + // [p2; 1]T * F * [p1; 1] = 0 + // (p2 == p1 in this testcase) + for (int i = 0; i < pts.rows(); i++) + { + Mat pt = new Mat(3, 1, fm.type()); + pt.put(0, 0, pts.get(i, 0)[0], pts.get(i, 0)[1], 1); + + Mat pt_t = pt.t(); + + Mat tmp = new Mat(); + Mat res = new Mat(); + Core.gemm(pt_t, fm, 1.0, new Mat(), 0.0, tmp); + Core.gemm(tmp, pt, 1.0, new Mat(), 0.0, res); + assertTrue(Math.abs(res.get(0, 0)[0]) <= 1e-6); + } } public void testFindFundamentalMatListOfPointListOfPointInt() { From 7db43f9fff9966ebe7cd6b8e62e317a4acb2035b Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Wed, 23 Nov 2016 18:39:18 +0400 Subject: [PATCH 111/409] fixed wrong equivalence in YUV conversion (#7481) * fixed wrong equivalence in YUV conversion * fixed channel order from YVU to YUV --- modules/imgproc/src/color.cpp | 374 +++++++++++++++---------- modules/imgproc/src/opencl/cvtcolor.cl | 51 +++- 2 files changed, 272 insertions(+), 153 deletions(-) diff --git a/modules/imgproc/src/color.cpp b/modules/imgproc/src/color.cpp index 3d392fb379..b571e1e566 100644 --- a/modules/imgproc/src/color.cpp +++ b/modules/imgproc/src/color.cpp @@ -105,6 +105,39 @@ namespace cv { +//constants for conversion from/to RGB and Gray, YUV, YCrCb according to BT.601 +const float B2YF = 0.114f; +const float G2YF = 0.587f; +const float R2YF = 0.299f; +//to YCbCr +const float YCBF = 0.564f; // == 1/2/(1-B2YF) +const float YCRF = 0.713f; // == 1/2/(1-R2YF) +const int YCBI = 9241; // == YCBF*16384 +const int YCRI = 11682; // == YCRF*16384 +//to YUV +const float B2UF = 0.492f; +const float R2VF = 0.877f; +const int B2UI = 8061; // == B2UF*16384 +const int R2VI = 14369; // == R2VF*16384 +//from YUV +const float U2BF = 2.032f; +const float U2GF = -0.395f; +const float V2GF = -0.581f; +const float V2RF = 1.140f; +const int U2BI = 33292; +const int U2GI = -6472; +const int V2GI = -9519; +const int V2RI = 18678; +//from YCrCb +const float CB2BF = 1.773f; +const float CB2GF = -0.344f; +const float CR2GF = -0.714f; +const float CR2RF = 1.403f; +const int CB2BI = 29049; +const int CB2GI = -5636; +const int CR2GI = -11698; +const int CR2RI = 22987; + // computes cubic spline coefficients for a function: (xi=i, yi=f[i]), i=0..n template static void splineBuild(const _Tp* f, int n, _Tp* tab) @@ -499,9 +532,9 @@ struct IPPColor2GrayFunctor IPPColor2GrayFunctor(ippiColor2GrayFunc _func) : ippiColorToGray(_func) { - coeffs[0] = 0.114f; - coeffs[1] = 0.587f; - coeffs[2] = 0.299f; + coeffs[0] = B2YF; + coeffs[1] = G2YF; + coeffs[2] = R2YF; } bool operator()(const void *src, int srcStep, void *dst, int dstStep, int cols, int rows) const { @@ -1143,9 +1176,9 @@ enum { yuv_shift = 14, xyz_shift = 12, - R2Y = 4899, - G2Y = 9617, - B2Y = 1868, + R2Y = 4899, // == R2YF*16384 + G2Y = 9617, // == G2YF*16384 + B2Y = 1868, // == B2YF*16384 BLOCK_SIZE = 256 }; @@ -1313,7 +1346,7 @@ template struct RGB2Gray RGB2Gray(int _srccn, int blueIdx, const float* _coeffs) : srccn(_srccn) { - static const float coeffs0[] = { 0.299f, 0.587f, 0.114f }; + static const float coeffs0[] = { R2YF, G2YF, B2YF }; memcpy( coeffs, _coeffs ? _coeffs : coeffs0, 3*sizeof(coeffs[0]) ); if(blueIdx == 0) std::swap(coeffs[0], coeffs[2]); @@ -1460,7 +1493,7 @@ struct RGB2Gray RGB2Gray(int _srccn, int blueIdx, const float* _coeffs) : srccn(_srccn) { - static const float coeffs0[] = { 0.299f, 0.587f, 0.114f }; + static const float coeffs0[] = { R2YF, G2YF, B2YF }; memcpy( coeffs, _coeffs ? _coeffs : coeffs0, 3*sizeof(coeffs[0]) ); if(blueIdx == 0) std::swap(coeffs[0], coeffs[2]); @@ -1650,7 +1683,7 @@ struct RGB2Gray RGB2Gray(int _srccn, int blueIdx, const float* _coeffs) : srccn(_srccn) { - static const float coeffs0[] = { 0.299f, 0.587f, 0.114f }; + static const float coeffs0[] = { R2YF, G2YF, B2YF }; memcpy( coeffs, _coeffs ? _coeffs : coeffs0, 3*sizeof(coeffs[0]) ); if(blueIdx == 0) std::swap(coeffs[0], coeffs[2]); @@ -1772,16 +1805,18 @@ template struct RGB2YCrCb_f { typedef _Tp channel_type; - RGB2YCrCb_f(int _srccn, int _blueIdx, const float* _coeffs) : srccn(_srccn), blueIdx(_blueIdx) + RGB2YCrCb_f(int _srccn, int _blueIdx, bool _isCrCb) : srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const float coeffs0[] = {0.299f, 0.587f, 0.114f, 0.713f, 0.564f}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 5*sizeof(coeffs[0])); + static const float coeffs_crb[] = { R2YF, G2YF, B2YF, YCRF, YCBF }; + static const float coeffs_yuv[] = { R2YF, G2YF, B2YF, R2VF, B2UF }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0])); if(blueIdx==0) std::swap(coeffs[0], coeffs[2]); } void operator()(const _Tp* src, _Tp* dst, int n) const { int scn = srccn, bidx = blueIdx; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const _Tp delta = ColorChannel<_Tp>::half(); float C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4]; n *= 3; @@ -1790,10 +1825,11 @@ template struct RGB2YCrCb_f _Tp Y = saturate_cast<_Tp>(src[0]*C0 + src[1]*C1 + src[2]*C2); _Tp Cr = saturate_cast<_Tp>((src[bidx^2] - Y)*C3 + delta); _Tp Cb = saturate_cast<_Tp>((src[bidx] - Y)*C4 + delta); - dst[i] = Y; dst[i+1] = Cr; dst[i+2] = Cb; + dst[i] = Y; dst[i+1+yuvOrder] = Cr; dst[i+2-yuvOrder] = Cb; } } int srccn, blueIdx; + bool isCrCb; float coeffs[5]; }; @@ -1804,11 +1840,12 @@ struct RGB2YCrCb_f { typedef float channel_type; - RGB2YCrCb_f(int _srccn, int _blueIdx, const float* _coeffs) : - srccn(_srccn), blueIdx(_blueIdx) + RGB2YCrCb_f(int _srccn, int _blueIdx, bool _isCrCb) : + srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const float coeffs0[] = {0.299f, 0.587f, 0.114f, 0.713f, 0.564f}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 5*sizeof(coeffs[0])); + static const float coeffs_crb[] = { R2YF, G2YF, B2YF, YCRF, YCBF }; + static const float coeffs_yuv[] = { R2YF, G2YF, B2YF, R2VF, B2UF }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0])); if(blueIdx==0) std::swap(coeffs[0], coeffs[2]); @@ -1823,6 +1860,7 @@ struct RGB2YCrCb_f void operator()(const float * src, float * dst, int n) const { int scn = srccn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const float delta = ColorChannel::half(); float C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4]; n *= 3; @@ -1832,8 +1870,8 @@ struct RGB2YCrCb_f { float32x4x3_t v_src = vld3q_f32(src), v_dst; v_dst.val[0] = vmlaq_f32(vmlaq_f32(vmulq_f32(v_src.val[0], v_c0), v_src.val[1], v_c1), v_src.val[2], v_c2); - v_dst.val[1] = vmlaq_f32(v_delta, vsubq_f32(v_src.val[bidx^2], v_dst.val[0]), v_c3); - v_dst.val[2] = vmlaq_f32(v_delta, vsubq_f32(v_src.val[bidx], v_dst.val[0]), v_c4); + v_dst.val[1+yuvOrder] = vmlaq_f32(v_delta, vsubq_f32(v_src.val[bidx^2], v_dst.val[0]), v_c3); + v_dst.val[2-yuvOrder] = vmlaq_f32(v_delta, vsubq_f32(v_src.val[bidx], v_dst.val[0]), v_c4); vst3q_f32(dst + i, v_dst); } @@ -1843,8 +1881,8 @@ struct RGB2YCrCb_f float32x4x4_t v_src = vld4q_f32(src); float32x4x3_t v_dst; v_dst.val[0] = vmlaq_f32(vmlaq_f32(vmulq_f32(v_src.val[0], v_c0), v_src.val[1], v_c1), v_src.val[2], v_c2); - v_dst.val[1] = vmlaq_f32(v_delta, vsubq_f32(v_src.val[bidx^2], v_dst.val[0]), v_c3); - v_dst.val[2] = vmlaq_f32(v_delta, vsubq_f32(v_src.val[bidx], v_dst.val[0]), v_c4); + v_dst.val[1+yuvOrder] = vmlaq_f32(v_delta, vsubq_f32(v_src.val[bidx^2], v_dst.val[0]), v_c3); + v_dst.val[2-yuvOrder] = vmlaq_f32(v_delta, vsubq_f32(v_src.val[bidx], v_dst.val[0]), v_c4); vst3q_f32(dst + i, v_dst); } @@ -1854,10 +1892,11 @@ struct RGB2YCrCb_f float Y = src[0]*C0 + src[1]*C1 + src[2]*C2; float Cr = (src[bidx^2] - Y)*C3 + delta; float Cb = (src[bidx] - Y)*C4 + delta; - dst[i] = Y; dst[i+1] = Cr; dst[i+2] = Cb; + dst[i] = Y; dst[i+1+yuvOrder] = Cr; dst[i+2-yuvOrder] = Cb; } } int srccn, blueIdx; + bool isCrCb; float coeffs[5]; float32x4_t v_c0, v_c1, v_c2, v_c3, v_c4, v_delta; }; @@ -1869,11 +1908,12 @@ struct RGB2YCrCb_f { typedef float channel_type; - RGB2YCrCb_f(int _srccn, int _blueIdx, const float* _coeffs) : - srccn(_srccn), blueIdx(_blueIdx) + RGB2YCrCb_f(int _srccn, int _blueIdx, bool _isCrCb) : + srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const float coeffs0[] = {0.299f, 0.587f, 0.114f, 0.713f, 0.564f}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 5*sizeof(coeffs[0])); + static const float coeffs_crb[] = { R2YF, G2YF, B2YF, YCRF, YCBF }; + static const float coeffs_yuv[] = { R2YF, G2YF, B2YF, R2VF, B2UF }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0])); if (blueIdx==0) std::swap(coeffs[0], coeffs[2]); @@ -1901,6 +1941,7 @@ struct RGB2YCrCb_f void operator()(const float * src, float * dst, int n) const { int scn = srccn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const float delta = ColorChannel::half(); float C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4]; n *= 3; @@ -1934,14 +1975,19 @@ struct RGB2YCrCb_f process(v_r1, v_g1, v_b1, v_y1, v_cr1, v_cb1); - _mm_interleave_ps(v_y0, v_y1, v_cr0, v_cr1, v_cb0, v_cb1); + if(isCrCb) + _mm_interleave_ps(v_y0, v_y1, v_cr0, v_cr1, v_cb0, v_cb1); + else //YUV + { + _mm_interleave_ps(v_y0, v_y1, v_cb0, v_cb1, v_cr0, v_cr1); + } _mm_storeu_ps(dst + i, v_y0); _mm_storeu_ps(dst + i + 4, v_y1); - _mm_storeu_ps(dst + i + 8, v_cr0); - _mm_storeu_ps(dst + i + 12, v_cr1); - _mm_storeu_ps(dst + i + 16, v_cb0); - _mm_storeu_ps(dst + i + 20, v_cb1); + _mm_storeu_ps(dst + i + 8 + yuvOrder*8, v_cr0); + _mm_storeu_ps(dst + i + 12 + yuvOrder*8, v_cr1); + _mm_storeu_ps(dst + i + 16 - yuvOrder*8, v_cb0); + _mm_storeu_ps(dst + i + 20 - yuvOrder*8, v_cb1); } } @@ -1950,10 +1996,11 @@ struct RGB2YCrCb_f float Y = src[0]*C0 + src[1]*C1 + src[2]*C2; float Cr = (src[bidx^2] - Y)*C3 + delta; float Cb = (src[bidx] - Y)*C4 + delta; - dst[i] = Y; dst[i+1] = Cr; dst[i+2] = Cb; + dst[i] = Y; dst[i+1+yuvOrder] = Cr; dst[i+2-yuvOrder] = Cb; } } int srccn, blueIdx; + bool isCrCb; float coeffs[5]; __m128 v_c0, v_c1, v_c2, v_c3, v_c4, v_delta; bool haveSIMD; @@ -1965,16 +2012,18 @@ template struct RGB2YCrCb_i { typedef _Tp channel_type; - RGB2YCrCb_i(int _srccn, int _blueIdx, const int* _coeffs) - : srccn(_srccn), blueIdx(_blueIdx) + RGB2YCrCb_i(int _srccn, int _blueIdx, bool _isCrCb) + : srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const int coeffs0[] = {R2Y, G2Y, B2Y, 11682, 9241}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 5*sizeof(coeffs[0])); + static const int coeffs_crb[] = { R2Y, G2Y, B2Y, YCRI, YCBI }; + static const int coeffs_yuv[] = { R2Y, G2Y, B2Y, R2VI, B2UI }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0])); if(blueIdx==0) std::swap(coeffs[0], coeffs[2]); } void operator()(const _Tp* src, _Tp* dst, int n) const { int scn = srccn, bidx = blueIdx; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4]; int delta = ColorChannel<_Tp>::half()*(1 << yuv_shift); n *= 3; @@ -1984,11 +2033,12 @@ template struct RGB2YCrCb_i int Cr = CV_DESCALE((src[bidx^2] - Y)*C3 + delta, yuv_shift); int Cb = CV_DESCALE((src[bidx] - Y)*C4 + delta, yuv_shift); dst[i] = saturate_cast<_Tp>(Y); - dst[i+1] = saturate_cast<_Tp>(Cr); - dst[i+2] = saturate_cast<_Tp>(Cb); + dst[i+1+yuvOrder] = saturate_cast<_Tp>(Cr); + dst[i+2-yuvOrder] = saturate_cast<_Tp>(Cb); } } int srccn, blueIdx; + bool isCrCb; int coeffs[5]; }; @@ -1999,11 +2049,12 @@ struct RGB2YCrCb_i { typedef uchar channel_type; - RGB2YCrCb_i(int _srccn, int _blueIdx, const int* _coeffs) - : srccn(_srccn), blueIdx(_blueIdx) + RGB2YCrCb_i(int _srccn, int _blueIdx, bool _isCrCb) + : srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const int coeffs0[] = {R2Y, G2Y, B2Y, 11682, 9241}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 5*sizeof(coeffs[0])); + static const int coeffs_crb[] = { R2Y, G2Y, B2Y, YCRI, YCBI }; + static const int coeffs_yuv[] = { R2Y, G2Y, B2Y, R2VI, B2UI }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0])); if (blueIdx==0) std::swap(coeffs[0], coeffs[2]); @@ -2019,6 +2070,7 @@ struct RGB2YCrCb_i void operator()(const uchar * src, uchar * dst, int n) const { int scn = srccn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4]; int delta = ColorChannel::half()*(1 << yuv_shift); n *= 3; @@ -2067,8 +2119,8 @@ struct RGB2YCrCb_i v_Cb1 = vshrq_n_s32(vaddq_s32(v_Cb1, v_delta2), yuv_shift); v_dst.val[0] = vqmovun_s16(vcombine_s16(vqmovn_s32(v_Y0), vqmovn_s32(v_Y1))); - v_dst.val[1] = vqmovun_s16(vcombine_s16(vqmovn_s32(v_Cr0), vqmovn_s32(v_Cr1))); - v_dst.val[2] = vqmovun_s16(vcombine_s16(vqmovn_s32(v_Cb0), vqmovn_s32(v_Cb1))); + v_dst.val[1+yuvOrder] = vqmovun_s16(vcombine_s16(vqmovn_s32(v_Cr0), vqmovn_s32(v_Cr1))); + v_dst.val[2-yuvOrder] = vqmovun_s16(vcombine_s16(vqmovn_s32(v_Cb0), vqmovn_s32(v_Cb1))); vst3_u8(dst + i, v_dst); } @@ -2079,11 +2131,12 @@ struct RGB2YCrCb_i int Cr = CV_DESCALE((src[bidx^2] - Y)*C3 + delta, yuv_shift); int Cb = CV_DESCALE((src[bidx] - Y)*C4 + delta, yuv_shift); dst[i] = saturate_cast(Y); - dst[i+1] = saturate_cast(Cr); - dst[i+2] = saturate_cast(Cb); + dst[i+1+yuvOrder] = saturate_cast(Cr); + dst[i+2-yuvOrder] = saturate_cast(Cb); } } int srccn, blueIdx, coeffs[5]; + bool isCrCb; int16x4_t v_c0, v_c1, v_c2; int32x4_t v_c3, v_c4, v_delta, v_delta2; }; @@ -2093,11 +2146,12 @@ struct RGB2YCrCb_i { typedef ushort channel_type; - RGB2YCrCb_i(int _srccn, int _blueIdx, const int* _coeffs) - : srccn(_srccn), blueIdx(_blueIdx) + RGB2YCrCb_i(int _srccn, int _blueIdx, bool _isCrCb) + : srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const int coeffs0[] = {R2Y, G2Y, B2Y, 11682, 9241}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 5*sizeof(coeffs[0])); + static const int coeffs_crb[] = { R2Y, G2Y, B2Y, YCRI, YCBI }; + static const int coeffs_yuv[] = { R2Y, G2Y, B2Y, R2VI, B2UI }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0])); if (blueIdx==0) std::swap(coeffs[0], coeffs[2]); @@ -2113,6 +2167,7 @@ struct RGB2YCrCb_i void operator()(const ushort * src, ushort * dst, int n) const { int scn = srccn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4]; int delta = ColorChannel::half()*(1 << yuv_shift); n *= 3; @@ -2155,8 +2210,8 @@ struct RGB2YCrCb_i v_Cb1 = vshrq_n_s32(vaddq_s32(v_Cb1, v_delta2), yuv_shift); v_dst.val[0] = vcombine_u16(vqmovun_s32(v_Y0), vqmovun_s32(v_Y1)); - v_dst.val[1] = vcombine_u16(vqmovun_s32(v_Cr0), vqmovun_s32(v_Cr1)); - v_dst.val[2] = vcombine_u16(vqmovun_s32(v_Cb0), vqmovun_s32(v_Cb1)); + v_dst.val[1+yuvOrder] = vcombine_u16(vqmovun_s32(v_Cr0), vqmovun_s32(v_Cr1)); + v_dst.val[2-yuvOrder] = vcombine_u16(vqmovun_s32(v_Cb0), vqmovun_s32(v_Cb1)); vst3q_u16(dst + i, v_dst); } @@ -2189,8 +2244,8 @@ struct RGB2YCrCb_i v_Cb = vshrq_n_s32(vaddq_s32(v_Cb, v_delta2), yuv_shift); v_dst.val[0] = vqmovun_s32(v_Y); - v_dst.val[1] = vqmovun_s32(v_Cr); - v_dst.val[2] = vqmovun_s32(v_Cb); + v_dst.val[1+yuvOrder] = vqmovun_s32(v_Cr); + v_dst.val[2-yuvOrder] = vqmovun_s32(v_Cb); vst3_u16(dst + i, v_dst); } @@ -2201,11 +2256,12 @@ struct RGB2YCrCb_i int Cr = CV_DESCALE((src[bidx^2] - Y)*C3 + delta, yuv_shift); int Cb = CV_DESCALE((src[bidx] - Y)*C4 + delta, yuv_shift); dst[i] = saturate_cast(Y); - dst[i+1] = saturate_cast(Cr); - dst[i+2] = saturate_cast(Cb); + dst[i+1+yuvOrder] = saturate_cast(Cr); + dst[i+2-yuvOrder] = saturate_cast(Cb); } } int srccn, blueIdx, coeffs[5]; + bool isCrCb; int32x4_t v_c0, v_c1, v_c2, v_c3, v_c4, v_delta, v_delta2; }; @@ -2216,11 +2272,12 @@ struct RGB2YCrCb_i { typedef uchar channel_type; - RGB2YCrCb_i(int _srccn, int _blueIdx, const int* _coeffs) - : srccn(_srccn), blueIdx(_blueIdx) + RGB2YCrCb_i(int _srccn, int _blueIdx, bool _isCrCb) + : srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const int coeffs0[] = {R2Y, G2Y, B2Y, 11682, 9241}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 5*sizeof(coeffs[0])); + static const int coeffs_crb[] = { R2Y, G2Y, B2Y, YCRI, YCBI }; + static const int coeffs_yuv[] = { R2Y, G2Y, B2Y, R2VI, B2UI }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0])); if (blueIdx==0) std::swap(coeffs[0], coeffs[2]); @@ -2229,7 +2286,10 @@ struct RGB2YCrCb_i v_delta_32 = _mm_set1_epi32(delta); short delta2 = 1 + ColorChannel::half() * 2; v_coeff = _mm_set_epi16(delta2, (short)coeffs[4], delta2, (short)coeffs[3], delta2, (short)coeffs[4], delta2, (short)coeffs[3]); - v_shuffle2 = _mm_set_epi8(0x0, 0x0, 0x0, 0x0, 0xf, 0xe, 0xc, 0xb, 0xa, 0x8, 0x7, 0x6, 0x4, 0x3, 0x2, 0x0); + if(isCrCb) + v_shuffle2 = _mm_set_epi8(0x0, 0x0, 0x0, 0x0, 0xf, 0xe, 0xc, 0xb, 0xa, 0x8, 0x7, 0x6, 0x4, 0x3, 0x2, 0x0); + else //if YUV + v_shuffle2 = _mm_set_epi8(0x0, 0x0, 0x0, 0x0, 0xe, 0xf, 0xc, 0xa, 0xb, 0x8, 0x6, 0x7, 0x4, 0x2, 0x3, 0x0); haveSIMD = checkHardwareSupport(CV_CPU_SSE4_1); } @@ -2283,6 +2343,7 @@ struct RGB2YCrCb_i void operator()(const uchar * src, uchar * dst, int n) const { int scn = srccn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4]; int delta = ColorChannel::half()*(1 << yuv_shift); n *= 3; @@ -2359,8 +2420,8 @@ struct RGB2YCrCb_i int Cr = CV_DESCALE((src[bidx^2] - Y)*C3 + delta, yuv_shift); int Cb = CV_DESCALE((src[bidx] - Y)*C4 + delta, yuv_shift); dst[i] = saturate_cast(Y); - dst[i+1] = saturate_cast(Cr); - dst[i+2] = saturate_cast(Cb); + dst[i+1+yuvOrder] = saturate_cast(Cr); + dst[i+2-yuvOrder] = saturate_cast(Cb); } } @@ -2368,6 +2429,7 @@ struct RGB2YCrCb_i __m128i v_coeff; __m128i v_shuffle2; int srccn, blueIdx, coeffs[5]; + bool isCrCb; bool haveSIMD; }; @@ -2376,11 +2438,12 @@ struct RGB2YCrCb_i { typedef ushort channel_type; - RGB2YCrCb_i(int _srccn, int _blueIdx, const int* _coeffs) - : srccn(_srccn), blueIdx(_blueIdx) + RGB2YCrCb_i(int _srccn, int _blueIdx, bool _isCrCb) + : srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const int coeffs0[] = {R2Y, G2Y, B2Y, 11682, 9241}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 5*sizeof(coeffs[0])); + static const int coeffs_crb[] = { R2Y, G2Y, B2Y, YCRI, YCBI }; + static const int coeffs_yuv[] = { R2Y, G2Y, B2Y, R2VI, B2UI }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0])); if (blueIdx==0) std::swap(coeffs[0], coeffs[2]); @@ -2437,6 +2500,7 @@ struct RGB2YCrCb_i void operator()(const ushort * src, ushort * dst, int n) const { int scn = srccn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4]; int delta = ColorChannel::half()*(1 << yuv_shift); n *= 3; @@ -2471,14 +2535,17 @@ struct RGB2YCrCb_i process(v_r1, v_g1, v_b1, v_y1, v_cr1, v_cb1); - _mm_interleave_epi16(v_y0, v_y1, v_cr0, v_cr1, v_cb0, v_cb1); + if(isCrCb) + _mm_interleave_epi16(v_y0, v_y1, v_cr0, v_cr1, v_cb0, v_cb1); + else //YUV + _mm_interleave_epi16(v_y0, v_y1, v_cb0, v_cb1, v_cr0, v_cr1); _mm_storeu_si128((__m128i *)(dst + i), v_y0); _mm_storeu_si128((__m128i *)(dst + i + 8), v_y1); - _mm_storeu_si128((__m128i *)(dst + i + 16), v_cr0); - _mm_storeu_si128((__m128i *)(dst + i + 24), v_cr1); - _mm_storeu_si128((__m128i *)(dst + i + 32), v_cb0); - _mm_storeu_si128((__m128i *)(dst + i + 40), v_cb1); + _mm_storeu_si128((__m128i *)(dst + i + 16 + yuvOrder*16), v_cr0); + _mm_storeu_si128((__m128i *)(dst + i + 24 + yuvOrder*16), v_cr1); + _mm_storeu_si128((__m128i *)(dst + i + 32 - yuvOrder*16), v_cb0); + _mm_storeu_si128((__m128i *)(dst + i + 40 - yuvOrder*16), v_cb1); } } @@ -2488,12 +2555,13 @@ struct RGB2YCrCb_i int Cr = CV_DESCALE((src[bidx^2] - Y)*C3 + delta, yuv_shift); int Cb = CV_DESCALE((src[bidx] - Y)*C4 + delta, yuv_shift); dst[i] = saturate_cast(Y); - dst[i+1] = saturate_cast(Cr); - dst[i+2] = saturate_cast(Cb); + dst[i+1+yuvOrder] = saturate_cast(Cr); + dst[i+2-yuvOrder] = saturate_cast(Cb); } } int srccn, blueIdx, coeffs[5]; + bool isCrCb; __m128i v_c0, v_c1, v_c2; __m128i v_c3, v_c4, v_delta, v_delta2; __m128i v_zero; @@ -2506,23 +2574,25 @@ template struct YCrCb2RGB_f { typedef _Tp channel_type; - YCrCb2RGB_f(int _dstcn, int _blueIdx, const float* _coeffs) - : dstcn(_dstcn), blueIdx(_blueIdx) + YCrCb2RGB_f(int _dstcn, int _blueIdx, bool _isCrCb) + : dstcn(_dstcn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const float coeffs0[] = {1.403f, -0.714f, -0.344f, 1.773f}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 4*sizeof(coeffs[0])); + static const float coeffs_cbr[] = {CR2RF, CR2GF, CB2GF, CB2BF}; + static const float coeffs_yuv[] = { V2RF, V2GF, U2GF, U2BF}; + memcpy(coeffs, isCrCb ? coeffs_cbr : coeffs_yuv, 4*sizeof(coeffs[0])); } void operator()(const _Tp* src, _Tp* dst, int n) const { int dcn = dstcn, bidx = blueIdx; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const _Tp delta = ColorChannel<_Tp>::half(), alpha = ColorChannel<_Tp>::max(); float C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3]; n *= 3; for(int i = 0; i < n; i += 3, dst += dcn) { _Tp Y = src[i]; - _Tp Cr = src[i+1]; - _Tp Cb = src[i+2]; + _Tp Cr = src[i+1+yuvOrder]; + _Tp Cb = src[i+2-yuvOrder]; _Tp b = saturate_cast<_Tp>(Y + (Cb - delta)*C3); _Tp g = saturate_cast<_Tp>(Y + (Cb - delta)*C2 + (Cr - delta)*C1); @@ -2534,6 +2604,7 @@ template struct YCrCb2RGB_f } } int dstcn, blueIdx; + bool isCrCb; float coeffs[4]; }; @@ -2544,11 +2615,12 @@ struct YCrCb2RGB_f { typedef float channel_type; - YCrCb2RGB_f(int _dstcn, int _blueIdx, const float* _coeffs) - : dstcn(_dstcn), blueIdx(_blueIdx) + YCrCb2RGB_f(int _dstcn, int _blueIdx, bool _isCrCb) + : dstcn(_dstcn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const float coeffs0[] = {1.403f, -0.714f, -0.344f, 1.773f}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 4*sizeof(coeffs[0])); + static const float coeffs_cbr[] = {CR2RF, CR2GF, CB2GF, CB2BF}; + static const float coeffs_yuv[] = { V2RF, V2GF, U2GF, U2BF}; + memcpy(coeffs, isCrCb ? coeffs_cbr : coeffs_yuv, 4*sizeof(coeffs[0])); v_c0 = vdupq_n_f32(coeffs[0]); v_c1 = vdupq_n_f32(coeffs[1]); @@ -2561,6 +2633,7 @@ struct YCrCb2RGB_f void operator()(const float* src, float* dst, int n) const { int dcn = dstcn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const float delta = ColorChannel::half(), alpha = ColorChannel::max(); float C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3]; n *= 3; @@ -2569,7 +2642,7 @@ struct YCrCb2RGB_f for ( ; i <= n - 12; i += 12, dst += 12) { float32x4x3_t v_src = vld3q_f32(src + i), v_dst; - float32x4_t v_Y = v_src.val[0], v_Cr = v_src.val[1], v_Cb = v_src.val[2]; + float32x4_t v_Y = v_src.val[0], v_Cr = v_src.val[1+yuvOrder], v_Cb = v_src.val[2-yuvOrder]; v_dst.val[bidx] = vmlaq_f32(v_Y, vsubq_f32(v_Cb, v_delta), v_c3); v_dst.val[1] = vaddq_f32(vmlaq_f32(vmulq_f32(vsubq_f32(v_Cb, v_delta), v_c2), vsubq_f32(v_Cr, v_delta), v_c1), v_Y); @@ -2582,7 +2655,7 @@ struct YCrCb2RGB_f { float32x4x3_t v_src = vld3q_f32(src + i); float32x4x4_t v_dst; - float32x4_t v_Y = v_src.val[0], v_Cr = v_src.val[1], v_Cb = v_src.val[2]; + float32x4_t v_Y = v_src.val[0], v_Cr = v_src.val[1+yuvOrder], v_Cb = v_src.val[2-yuvOrder]; v_dst.val[bidx] = vmlaq_f32(v_Y, vsubq_f32(v_Cb, v_delta), v_c3); v_dst.val[1] = vaddq_f32(vmlaq_f32(vmulq_f32(vsubq_f32(v_Cb, v_delta), v_c2), vsubq_f32(v_Cr, v_delta), v_c1), v_Y); @@ -2594,7 +2667,7 @@ struct YCrCb2RGB_f for ( ; i < n; i += 3, dst += dcn) { - float Y = src[i], Cr = src[i+1], Cb = src[i+2]; + float Y = src[i], Cr = src[i+1+yuvOrder], Cb = src[i+2-yuvOrder]; float b = Y + (Cb - delta)*C3; float g = Y + (Cb - delta)*C2 + (Cr - delta)*C1; @@ -2606,6 +2679,7 @@ struct YCrCb2RGB_f } } int dstcn, blueIdx; + bool isCrCb; float coeffs[4]; float32x4_t v_c0, v_c1, v_c2, v_c3, v_alpha, v_delta; }; @@ -2617,11 +2691,12 @@ struct YCrCb2RGB_f { typedef float channel_type; - YCrCb2RGB_f(int _dstcn, int _blueIdx, const float* _coeffs) - : dstcn(_dstcn), blueIdx(_blueIdx) + YCrCb2RGB_f(int _dstcn, int _blueIdx, bool _isCrCb) + : dstcn(_dstcn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const float coeffs0[] = {1.403f, -0.714f, -0.344f, 1.773f}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 4*sizeof(coeffs[0])); + static const float coeffs_cbr[] = {CR2RF, CR2GF, CB2GF, CB2BF}; + static const float coeffs_yuv[] = { V2RF, V2GF, U2GF, U2BF}; + memcpy(coeffs, isCrCb ? coeffs_cbr : coeffs_yuv, 4*sizeof(coeffs[0])); v_c0 = _mm_set1_ps(coeffs[0]); v_c1 = _mm_set1_ps(coeffs[1]); @@ -2639,6 +2714,9 @@ struct YCrCb2RGB_f v_cb = _mm_sub_ps(v_cb, v_delta); v_cr = _mm_sub_ps(v_cr, v_delta); + if (!isCrCb) + std::swap(v_cb, v_cr); + v_b = _mm_mul_ps(v_cb, v_c3); v_g = _mm_add_ps(_mm_mul_ps(v_cb, v_c2), _mm_mul_ps(v_cr, v_c1)); v_r = _mm_mul_ps(v_cr, v_c0); @@ -2654,6 +2732,7 @@ struct YCrCb2RGB_f void operator()(const float* src, float* dst, int n) const { int dcn = dstcn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const float delta = ColorChannel::half(), alpha = ColorChannel::max(); float C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3]; n *= 3; @@ -2704,7 +2783,7 @@ struct YCrCb2RGB_f for ( ; i < n; i += 3, dst += dcn) { - float Y = src[i], Cr = src[i+1], Cb = src[i+2]; + float Y = src[i], Cr = src[i+1+yuvOrder], Cb = src[i+2-yuvOrder]; float b = Y + (Cb - delta)*C3; float g = Y + (Cb - delta)*C2 + (Cr - delta)*C1; @@ -2716,6 +2795,7 @@ struct YCrCb2RGB_f } } int dstcn, blueIdx; + bool isCrCb; float coeffs[4]; __m128 v_c0, v_c1, v_c2, v_c3, v_alpha, v_delta; @@ -2728,24 +2808,26 @@ template struct YCrCb2RGB_i { typedef _Tp channel_type; - YCrCb2RGB_i(int _dstcn, int _blueIdx, const int* _coeffs) - : dstcn(_dstcn), blueIdx(_blueIdx) + YCrCb2RGB_i(int _dstcn, int _blueIdx, bool _isCrCb) + : dstcn(_dstcn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const int coeffs0[] = {22987, -11698, -5636, 29049}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 4*sizeof(coeffs[0])); + static const int coeffs_crb[] = { CR2RI, CR2GI, CB2GI, CB2BI}; + static const int coeffs_yuv[] = { V2RI, V2GI, U2GI, U2BI }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 4*sizeof(coeffs[0])); } void operator()(const _Tp* src, _Tp* dst, int n) const { int dcn = dstcn, bidx = blueIdx; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const _Tp delta = ColorChannel<_Tp>::half(), alpha = ColorChannel<_Tp>::max(); int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3]; n *= 3; for(int i = 0; i < n; i += 3, dst += dcn) { _Tp Y = src[i]; - _Tp Cr = src[i+1]; - _Tp Cb = src[i+2]; + _Tp Cr = src[i+1+yuvOrder]; + _Tp Cb = src[i+2-yuvOrder]; int b = Y + CV_DESCALE((Cb - delta)*C3, yuv_shift); int g = Y + CV_DESCALE((Cb - delta)*C2 + (Cr - delta)*C1, yuv_shift); @@ -2759,6 +2841,7 @@ template struct YCrCb2RGB_i } } int dstcn, blueIdx; + bool isCrCb; int coeffs[4]; }; @@ -2769,11 +2852,12 @@ struct YCrCb2RGB_i { typedef uchar channel_type; - YCrCb2RGB_i(int _dstcn, int _blueIdx, const int* _coeffs) - : dstcn(_dstcn), blueIdx(_blueIdx) + YCrCb2RGB_i(int _dstcn, int _blueIdx, bool _isCrCb) + : dstcn(_dstcn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const int coeffs0[] = {22987, -11698, -5636, 29049}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 4*sizeof(coeffs[0])); + static const int coeffs_crb[] = { CR2RI, CR2GI, CB2GI, CB2BI}; + static const int coeffs_yuv[] = { V2RI, V2GI, U2GI, U2BI }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 4*sizeof(coeffs[0])); v_c0 = vdupq_n_s32(coeffs[0]); v_c1 = vdupq_n_s32(coeffs[1]); @@ -2787,6 +2871,7 @@ struct YCrCb2RGB_i void operator()(const uchar* src, uchar* dst, int n) const { int dcn = dstcn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const uchar delta = ColorChannel::half(), alpha = ColorChannel::max(); int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3]; n *= 3; @@ -2800,8 +2885,8 @@ struct YCrCb2RGB_i v_src16.val[2] = vreinterpretq_s16_u16(vmovl_u8(v_src.val[2])); int16x4_t v_Y = vget_low_s16(v_src16.val[0]), - v_Cr = vget_low_s16(v_src16.val[1]), - v_Cb = vget_low_s16(v_src16.val[2]); + v_Cr = vget_low_s16(v_src16.val[1+yuvOrder]), + v_Cb = vget_low_s16(v_src16.val[2-yuvOrder]); int32x4_t v_b0 = vmulq_s32(v_c3, vsubl_s16(v_Cb, v_delta)); v_b0 = vaddw_s16(vshrq_n_s32(vaddq_s32(v_b0, v_delta2), yuv_shift), v_Y); @@ -2811,8 +2896,8 @@ struct YCrCb2RGB_i v_r0 = vaddw_s16(vshrq_n_s32(vaddq_s32(v_r0, v_delta2), yuv_shift), v_Y); v_Y = vget_high_s16(v_src16.val[0]); - v_Cr = vget_high_s16(v_src16.val[1]); - v_Cb = vget_high_s16(v_src16.val[2]); + v_Cr = vget_high_s16(v_src16.val[1+yuvOrder]); + v_Cb = vget_high_s16(v_src16.val[2-yuvOrder]); int32x4_t v_b1 = vmulq_s32(v_c3, vsubl_s16(v_Cb, v_delta)); v_b1 = vaddw_s16(vshrq_n_s32(vaddq_s32(v_b1, v_delta2), yuv_shift), v_Y); @@ -2847,8 +2932,8 @@ struct YCrCb2RGB_i for ( ; i < n; i += 3, dst += dcn) { uchar Y = src[i]; - uchar Cr = src[i+1]; - uchar Cb = src[i+2]; + uchar Cr = src[i+1+yuvOrder]; + uchar Cb = src[i+2-yuvOrder]; int b = Y + CV_DESCALE((Cb - delta)*C3, yuv_shift); int g = Y + CV_DESCALE((Cb - delta)*C2 + (Cr - delta)*C1, yuv_shift); @@ -2862,6 +2947,7 @@ struct YCrCb2RGB_i } } int dstcn, blueIdx; + bool isCrCb; int coeffs[4]; int32x4_t v_c0, v_c1, v_c2, v_c3, v_delta2; @@ -2874,11 +2960,12 @@ struct YCrCb2RGB_i { typedef ushort channel_type; - YCrCb2RGB_i(int _dstcn, int _blueIdx, const int* _coeffs) - : dstcn(_dstcn), blueIdx(_blueIdx) + YCrCb2RGB_i(int _dstcn, int _blueIdx, bool _isCrCb) + : dstcn(_dstcn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const int coeffs0[] = {22987, -11698, -5636, 29049}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 4*sizeof(coeffs[0])); + static const int coeffs_crb[] = { CR2RI, CR2GI, CB2GI, CB2BI}; + static const int coeffs_yuv[] = { V2RI, V2GI, U2GI, U2BI }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 4*sizeof(coeffs[0])); v_c0 = vdupq_n_s32(coeffs[0]); v_c1 = vdupq_n_s32(coeffs[1]); @@ -2893,6 +2980,7 @@ struct YCrCb2RGB_i void operator()(const ushort* src, ushort* dst, int n) const { int dcn = dstcn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const ushort delta = ColorChannel::half(), alpha = ColorChannel::max(); int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3]; n *= 3; @@ -2902,8 +2990,8 @@ struct YCrCb2RGB_i uint16x8x3_t v_src = vld3q_u16(src + i); int32x4_t v_Y = vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(v_src.val[0]))), - v_Cr = vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(v_src.val[1]))), - v_Cb = vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(v_src.val[2]))); + v_Cr = vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(v_src.val[1+yuvOrder]))), + v_Cb = vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(v_src.val[2-yuvOrder]))); int32x4_t v_b0 = vmulq_s32(v_c3, vsubq_s32(v_Cb, v_delta)); v_b0 = vaddq_s32(vshrq_n_s32(vaddq_s32(v_b0, v_delta2), yuv_shift), v_Y); @@ -2913,8 +3001,8 @@ struct YCrCb2RGB_i v_r0 = vaddq_s32(vshrq_n_s32(vaddq_s32(v_r0, v_delta2), yuv_shift), v_Y); v_Y = vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(v_src.val[0]))), - v_Cr = vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(v_src.val[1]))), - v_Cb = vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(v_src.val[2]))); + v_Cr = vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(v_src.val[1+yuvOrder]))), + v_Cb = vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(v_src.val[2-yuvOrder]))); int32x4_t v_b1 = vmulq_s32(v_c3, vsubq_s32(v_Cb, v_delta)); v_b1 = vaddq_s32(vshrq_n_s32(vaddq_s32(v_b1, v_delta2), yuv_shift), v_Y); @@ -2951,8 +3039,8 @@ struct YCrCb2RGB_i uint16x4x3_t v_src = vld3_u16(src + i); int32x4_t v_Y = vreinterpretq_s32_u32(vmovl_u16(v_src.val[0])), - v_Cr = vreinterpretq_s32_u32(vmovl_u16(v_src.val[1])), - v_Cb = vreinterpretq_s32_u32(vmovl_u16(v_src.val[2])); + v_Cr = vreinterpretq_s32_u32(vmovl_u16(v_src.val[1+yuvOrder])), + v_Cb = vreinterpretq_s32_u32(vmovl_u16(v_src.val[2-yuvOrder])); int32x4_t v_b = vmulq_s32(v_c3, vsubq_s32(v_Cb, v_delta)); v_b = vaddq_s32(vshrq_n_s32(vaddq_s32(v_b, v_delta2), yuv_shift), v_Y); @@ -2987,8 +3075,8 @@ struct YCrCb2RGB_i for ( ; i < n; i += 3, dst += dcn) { ushort Y = src[i]; - ushort Cr = src[i+1]; - ushort Cb = src[i+2]; + ushort Cr = src[i+1+yuvOrder]; + ushort Cb = src[i+2-yuvOrder]; int b = Y + CV_DESCALE((Cb - delta)*C3, yuv_shift); int g = Y + CV_DESCALE((Cb - delta)*C2 + (Cr - delta)*C1, yuv_shift); @@ -3002,6 +3090,7 @@ struct YCrCb2RGB_i } } int dstcn, blueIdx; + bool isCrCb; int coeffs[4]; int32x4_t v_c0, v_c1, v_c2, v_c3, v_delta2, v_delta; @@ -3016,11 +3105,12 @@ struct YCrCb2RGB_i { typedef uchar channel_type; - YCrCb2RGB_i(int _dstcn, int _blueIdx, const int* _coeffs) - : dstcn(_dstcn), blueIdx(_blueIdx) + YCrCb2RGB_i(int _dstcn, int _blueIdx, bool _isCrCb) + : dstcn(_dstcn), blueIdx(_blueIdx), isCrCb(_isCrCb) { - static const int coeffs0[] = {22987, -11698, -5636, 29049}; - memcpy(coeffs, _coeffs ? _coeffs : coeffs0, 4*sizeof(coeffs[0])); + static const int coeffs_crb[] = { CR2RI, CR2GI, CB2GI, CB2BI}; + static const int coeffs_yuv[] = { V2RI, V2GI, U2GI, U2BI }; + memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 4*sizeof(coeffs[0])); v_c0 = _mm_set1_epi16((short)coeffs[0]); v_c1 = _mm_set1_epi16((short)coeffs[1]); @@ -3033,7 +3123,9 @@ struct YCrCb2RGB_i uchar alpha = ColorChannel::max(); v_alpha = _mm_set1_epi8(*(char *)&alpha); - useSSE = coeffs[0] <= std::numeric_limits::max(); + // when using YUV, one of coefficients is bigger than std::numeric_limits::max(), + //which is not appropriate for SSE + useSSE = isCrCb; haveSIMD = checkHardwareSupport(CV_CPU_SSE2); } @@ -3152,6 +3244,7 @@ struct YCrCb2RGB_i void operator()(const uchar* src, uchar* dst, int n) const { int dcn = dstcn, bidx = blueIdx, i = 0; + int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb const uchar delta = ColorChannel::half(), alpha = ColorChannel::max(); int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3]; n *= 3; @@ -3316,8 +3409,8 @@ struct YCrCb2RGB_i for ( ; i < n; i += 3, dst += dcn) { uchar Y = src[i]; - uchar Cr = src[i+1]; - uchar Cb = src[i+2]; + uchar Cr = src[i+1+yuvOrder]; + uchar Cb = src[i+2-yuvOrder]; int b = Y + CV_DESCALE((Cb - delta)*C3, yuv_shift); int g = Y + CV_DESCALE((Cb - delta)*C2 + (Cr - delta)*C1, yuv_shift); @@ -3332,6 +3425,7 @@ struct YCrCb2RGB_i } int dstcn, blueIdx; int coeffs[4]; + bool isCrCb; bool useSSE, haveSIMD; __m128i v_c0, v_c1, v_c2, v_c3, v_delta2; @@ -7992,7 +8086,7 @@ static bool ocl_cvtColor( InputArray _src, OutputArray _dst, int code, int dcn ) case COLOR_RGB2YUV: { CV_Assert(scn == 3 || scn == 4); - bidx = code == COLOR_RGB2YUV ? 0 : 2; + bidx = code == COLOR_RGB2YUV ? 2 : 0; dcn = 3; k.create("RGB2YUV", ocl::imgproc::cvtcolor_oclsrc, opts + format("-D dcn=3 -D bidx=%d", bidx)); @@ -8001,9 +8095,9 @@ static bool ocl_cvtColor( InputArray _src, OutputArray _dst, int code, int dcn ) case COLOR_YUV2BGR: case COLOR_YUV2RGB: { - if(dcn < 0) dcn = 3; + if(dcn <= 0) dcn = 3; CV_Assert(dcn == 3 || dcn == 4); - bidx = code == COLOR_YUV2RGB ? 0 : 2; + bidx = code == COLOR_YUV2RGB ? 2 : 0; k.create("YUV2RGB", ocl::imgproc::cvtcolor_oclsrc, opts + format("-D dcn=%d -D bidx=%d", dcn, bidx)); break; @@ -8858,17 +8952,13 @@ void cvtBGRtoYUV(const uchar * src_data, size_t src_step, } #endif - static const float yuv_f[] = { 0.114f, 0.587f, 0.299f, 0.492f, 0.877f }; - static const int yuv_i[] = { B2Y, G2Y, R2Y, 8061, 14369 }; - const float* coeffs_f = isCbCr ? 0 : yuv_f; - const int* coeffs_i = isCbCr ? 0 : yuv_i; int blueIdx = swapBlue ? 2 : 0; if( depth == CV_8U ) - CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_i(scn, blueIdx, coeffs_i)); + CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_i(scn, blueIdx, isCbCr)); else if( depth == CV_16U ) - CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_i(scn, blueIdx, coeffs_i)); + CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_i(scn, blueIdx, isCbCr)); else - CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_f(scn, blueIdx, coeffs_f)); + CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_f(scn, blueIdx, isCbCr)); } void cvtYUVtoBGR(const uchar * src_data, size_t src_step, @@ -8914,17 +9004,13 @@ void cvtYUVtoBGR(const uchar * src_data, size_t src_step, } #endif - static const float yuv_f[] = { 2.032f, -0.395f, -0.581f, 1.140f }; - static const int yuv_i[] = { 33292, -6472, -9519, 18678 }; - const float* coeffs_f = isCbCr ? 0 : yuv_f; - const int* coeffs_i = isCbCr ? 0 : yuv_i; int blueIdx = swapBlue ? 2 : 0; if( depth == CV_8U ) - CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, YCrCb2RGB_i(dcn, blueIdx, coeffs_i)); + CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, YCrCb2RGB_i(dcn, blueIdx, isCbCr)); else if( depth == CV_16U ) - CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, YCrCb2RGB_i(dcn, blueIdx, coeffs_i)); + CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, YCrCb2RGB_i(dcn, blueIdx, isCbCr)); else - CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, YCrCb2RGB_f(dcn, blueIdx, coeffs_f)); + CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, YCrCb2RGB_f(dcn, blueIdx, isCbCr)); } void cvtBGRtoXYZ(const uchar * src_data, size_t src_step, diff --git a/modules/imgproc/src/opencl/cvtcolor.cl b/modules/imgproc/src/opencl/cvtcolor.cl index 743e3ced8a..9ceafd71aa 100644 --- a/modules/imgproc/src/opencl/cvtcolor.cl +++ b/modules/imgproc/src/opencl/cvtcolor.cl @@ -84,6 +84,39 @@ enum BLOCK_SIZE = 256 }; +//constants for conversion from/to RGB and Gray, YUV, YCrCb according to BT.601 +#define B2YF 0.114f +#define G2YF 0.587f +#define R2YF 0.299f +//to YCbCr +#define YCBF 0.564f +#define YCRF 0.713f +#define YCBI 9241 +#define YCRI 11682 +//to YUV +#define B2UF 0.492f +#define R2VF 0.877f +#define B2UI 8061 +#define R2VI 14369 +//from YUV +#define U2BF 2.032f +#define U2GF -0.395f +#define V2GF -0.581f +#define V2RF 1.140f +#define U2BI 33292 +#define U2GI -6472 +#define V2GI -9519 +#define V2RI 18678 +//from YCrCb +#define CR2RF 1.403f +#define CB2GF -0.344f +#define CR2GF -0.714f +#define CB2BF 1.773f +#define CR2RI 22987 +#define CB2GI -5636 +#define CR2GI -11698 +#define CB2BI 29049 + #define scnbytes ((int)sizeof(DATA_TYPE)*scn) #define dcnbytes ((int)sizeof(DATA_TYPE)*dcn) @@ -151,7 +184,7 @@ __kernel void RGB2Gray(__global const uchar * srcptr, int src_step, int src_offs __global DATA_TYPE* dst = (__global DATA_TYPE*)(dstptr + dst_index); DATA_TYPE_4 src_pix = vload4(0, src); #ifdef DEPTH_5 - dst[0] = fma(src_pix.B_COMP, 0.114f, fma(src_pix.G_COMP, 0.587f, src_pix.R_COMP * 0.299f)); + dst[0] = fma(src_pix.B_COMP, B2YF, fma(src_pix.G_COMP, G2YF, src_pix.R_COMP * R2YF)); #else dst[0] = (DATA_TYPE)CV_DESCALE(mad24(src_pix.B_COMP, B2Y, mad24(src_pix.G_COMP, G2Y, mul24(src_pix.R_COMP, R2Y))), yuv_shift); #endif @@ -201,8 +234,8 @@ __kernel void Gray2RGB(__global const uchar * srcptr, int src_step, int src_offs ///////////////////////////////////// RGB <-> YUV ////////////////////////////////////// -__constant float c_RGB2YUVCoeffs_f[5] = { 0.114f, 0.587f, 0.299f, 0.492f, 0.877f }; -__constant int c_RGB2YUVCoeffs_i[5] = { B2Y, G2Y, R2Y, 8061, 14369 }; +__constant float c_RGB2YUVCoeffs_f[5] = { B2YF, G2YF, R2YF, B2UF, R2VF }; +__constant int c_RGB2YUVCoeffs_i[5] = { B2Y, G2Y, R2Y, B2UI, R2VI }; __kernel void RGB2YUV(__global const uchar* srcptr, int src_step, int src_offset, __global uchar* dstptr, int dst_step, int dt_offset, @@ -251,8 +284,8 @@ __kernel void RGB2YUV(__global const uchar* srcptr, int src_step, int src_offset } } -__constant float c_YUV2RGBCoeffs_f[4] = { 2.032f, -0.395f, -0.581f, 1.140f }; -__constant int c_YUV2RGBCoeffs_i[4] = { 33292, -6472, -9519, 18678 }; +__constant float c_YUV2RGBCoeffs_f[4] = { U2BF, U2GF, V2GF, V2RF }; +__constant int c_YUV2RGBCoeffs_i[4] = { U2BI, U2GI, V2GI, V2RI }; __kernel void YUV2RGB(__global const uchar* srcptr, int src_step, int src_offset, __global uchar* dstptr, int dst_step, int dt_offset, @@ -624,8 +657,8 @@ __kernel void YUV2RGB_422(__global const uchar* srcptr, int src_step, int src_of ///////////////////////////////////// RGB <-> YCrCb ////////////////////////////////////// -__constant float c_RGB2YCrCbCoeffs_f[5] = {0.299f, 0.587f, 0.114f, 0.713f, 0.564f}; -__constant int c_RGB2YCrCbCoeffs_i[5] = {R2Y, G2Y, B2Y, 11682, 9241}; +__constant float c_RGB2YCrCbCoeffs_f[5] = {R2YF, G2YF, B2YF, YCRF, YCBF}; +__constant int c_RGB2YCrCbCoeffs_i[5] = {R2Y, G2Y, B2Y, YCRI, YCBI}; __kernel void RGB2YCrCb(__global const uchar* srcptr, int src_step, int src_offset, __global uchar* dstptr, int dst_step, int dt_offset, @@ -674,8 +707,8 @@ __kernel void RGB2YCrCb(__global const uchar* srcptr, int src_step, int src_offs } } -__constant float c_YCrCb2RGBCoeffs_f[4] = { 1.403f, -0.714f, -0.344f, 1.773f }; -__constant int c_YCrCb2RGBCoeffs_i[4] = { 22987, -11698, -5636, 29049 }; +__constant float c_YCrCb2RGBCoeffs_f[4] = { CR2RF, CR2GF, CB2GF, CB2BF }; +__constant int c_YCrCb2RGBCoeffs_i[4] = { CR2RI, CR2GI, CB2GI, CB2BI }; __kernel void YCrCb2RGB(__global const uchar* src, int src_step, int src_offset, __global uchar* dst, int dst_step, int dst_offset, From e69253a4fb613ddbc5797c9f4828cd9200f64777 Mon Sep 17 00:00:00 2001 From: guoxuedong Date: Thu, 24 Nov 2016 11:08:11 +0800 Subject: [PATCH 112/409] Update exif.cpp ExifReader::getExif may enter infinite loop with jpeg image which have no EOI. For example, bytesToSkip may be set to 0 and fseek seems like fseek(f, -2 , SEEK_CUR) for image that end with RST7(FF D7) instead of EOI. --- modules/imgcodecs/src/exif.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/imgcodecs/src/exif.cpp b/modules/imgcodecs/src/exif.cpp index 270848180a..8a4f3f4b86 100644 --- a/modules/imgcodecs/src/exif.cpp +++ b/modules/imgcodecs/src/exif.cpp @@ -160,6 +160,10 @@ std::map ExifReader::getExif() case APP9: case APP10: case APP11: case APP12: case APP13: case APP14: case APP15: case COM: bytesToSkip = getFieldSize( f ); + if (bytesToSkip < markerSize) { + fclose(f); + throw ExifParsingError(); + } fseek( f, static_cast( bytesToSkip - markerSize ), SEEK_CUR ); break; From 12e0bc0fe78eb762d05db7afbbe24f8f4dabfd7c Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Thu, 24 Nov 2016 15:02:12 +0300 Subject: [PATCH 113/409] Added wrappers for vx_matrix and vx_convolution (#7651) --- 3rdparty/openvx/include/ivx.hpp | 342 ++++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index 2d3275dada..b813ac9cf5 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -199,6 +199,27 @@ template<> struct TypeToEnum { static const vx_enum value = VX_TYPE //template<> struct TypeToEnum { static const vx_enum val = VX_TYPE_SIZE; }; //template<> struct TypeToEnum { static const vx_enum val = VX_TYPE_DF_IMAGE; }; +#ifdef IVX_USE_OPENCV +inline int enumToCVType(vx_enum type) +{ + switch (type) + { + case VX_TYPE_CHAR: return CV_8UC1;//While OpenCV support 8S as well, 8U is supported wider + case VX_TYPE_INT8: return CV_8SC1; + case VX_TYPE_UINT8: return CV_8UC1; + case VX_TYPE_INT16: return CV_16SC1; + case VX_TYPE_UINT16: return CV_16UC1; + case VX_TYPE_INT32: return CV_32SC1; + case VX_TYPE_UINT32: return CV_32SC1;//That's not the best option but there is CV_32S type only + case VX_TYPE_FLOAT32: return CV_32FC1; + case VX_TYPE_FLOAT64: return CV_64FC1; + case VX_TYPE_ENUM: return CV_32SC1; + case VX_TYPE_BOOL: return CV_32SC1; + default: throw WrapperError(std::string(__func__) + ": unsupported type enum"); + } +} +#endif + /// Helper type, provides info for OpenVX 'objects' (vx_reference extending) types template struct RefTypeTraits {}; @@ -283,6 +304,24 @@ template <> struct RefTypeTraits static vx_status release(vxType& ref) { return vxReleaseThreshold(&ref); } }; +class Convolution; +template <> struct RefTypeTraits +{ + typedef vx_convolution vxType; + typedef Convolution wrapperType; + static const vx_enum vxTypeEnum = VX_TYPE_CONVOLUTION; + static vx_status release(vxType& ref) { return vxReleaseConvolution(&ref); } +}; + +class Matrix; +template <> struct RefTypeTraits +{ + typedef vx_matrix vxType; + typedef Matrix wrapperType; + static const vx_enum vxTypeEnum = VX_TYPE_MATRIX; + static vx_status release(vxType& ref) { return vxReleaseMatrix(&ref); } +}; + #ifdef IVX_USE_CXX98 /// Casting to vx_reference with compile-time check @@ -1656,6 +1695,309 @@ public: { return Array(vxCreateVirtualArray(g, type, capacity)); } }; +/* +* Convolution +*/ +class Convolution : public RefWrapper +{ +public: + IVX_REF_STD_CTORS_AND_ASSIGNMENT(Convolution); + + static Convolution create(vx_context context, vx_size columns, vx_size rows) + { return Convolution(vxCreateConvolution(context, columns, rows)); } + +#ifndef VX_VERSION_1_1 + static const vx_enum + VX_MEMORY_TYPE_HOST = VX_IMPORT_TYPE_HOST, + VX_CONVOLUTION_ROWS = VX_CONVOLUTION_ATTRIBUTE_ROWS, + VX_CONVOLUTION_COLUMNS = VX_CONVOLUTION_ATTRIBUTE_COLUMNS, + VX_CONVOLUTION_SCALE = VX_CONVOLUTION_ATTRIBUTE_SCALE, + VX_CONVOLUTION_SIZE = VX_CONVOLUTION_ATTRIBUTE_SIZE; +#endif + + template + void query(vx_enum att, T& value) const + { IVX_CHECK_STATUS( vxQueryConvolution(ref, att, &value, sizeof(value)) ); } + + vx_size columns() const + { + vx_size v; + query(VX_CONVOLUTION_COLUMNS, v); + return v; + } + + vx_size rows() const + { + vx_size v; + query(VX_CONVOLUTION_ROWS, v); + return v; + } + + vx_uint32 scale() const + { + vx_uint32 v; + query(VX_CONVOLUTION_SCALE, v); + return v; + } + + vx_size size() const + { + vx_size v; + query(VX_CONVOLUTION_SIZE, v); + return v; + } + + vx_enum dataType() + { + return VX_TYPE_INT16; + } + + void copyTo(void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): output pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyConvolutionCoefficients(ref, data, VX_READ_ONLY, VX_MEMORY_TYPE_HOST)); +#else + IVX_CHECK_STATUS(vxReadConvolutionCoefficients(ref, (vx_int16 *)data)); +#endif + } + + void copyFrom(const void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): input pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyConvolutionCoefficients(ref, const_cast(data), VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST)); +#else + IVX_CHECK_STATUS(vxWriteConvolutionCoefficients(ref, (const vx_int16 *)data)); +#endif + } + + void copy(void* data, vx_enum usage, vx_enum memType = VX_MEMORY_TYPE_HOST) + { + if (!data) throw WrapperError(std::string(__func__) + "(): data pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyConvolutionCoefficients(ref, data, usage, memType)); +#else + if (usage == VX_READ_ONLY) + IVX_CHECK_STATUS(vxReadConvolutionCoefficients(ref, (vx_int16 *)data)); + else if (usage == VX_WRITE_ONLY) + IVX_CHECK_STATUS(vxWriteConvolutionCoefficients(ref, (const vx_int16 *)data)); + else + throw WrapperError(std::string(__func__) + "(): unknown copy direction"); + (void)memType; +#endif + } + + template void copyTo(std::vector& data) + { + if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (data.size() != size()) + { + if (data.size() == 0) + data.resize(size()); + else + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + } + copyTo(&data[0]); + } + + template void copyFrom(const std::vector& data) + { + if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + if (data.size() != size()) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + copyFrom(&data[0]); + } + +#ifdef IVX_USE_OPENCV + void copyTo(cv::Mat& m) + { + if (m.type() != enumToCVType(dataType())) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (((vx_size)(m.rows) != rows() || (vx_size)(m.cols) != columns()) && !m.empty()) + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + + if (m.isContinuous() && (vx_size)(m.rows) == rows() && (vx_size)(m.cols) == columns()) + { + copyTo(m.ptr()); + } + else + { + cv::Mat tmp((int)rows(), (int)columns(), enumToCVType(dataType())); + copyTo(tmp.ptr()); + if (m.empty()) + m = tmp; + else + tmp.copyTo(m); + } + } + + void copyFrom(const cv::Mat& m) + { + if ((vx_size)(m.rows) != rows() || (vx_size)(m.cols) != columns()) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + if (m.type() != enumToCVType(dataType())) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + copyFrom(m.isContinuous() ? m.ptr() : m.clone().ptr()); + } +#endif //IVX_USE_OPENCV +}; + +/* +* Matrix +*/ +class Matrix : public RefWrapper +{ +public: + IVX_REF_STD_CTORS_AND_ASSIGNMENT(Matrix); + + static Matrix create(vx_context context, vx_enum dataType, vx_size columns, vx_size rows) + { return Matrix(vxCreateMatrix(context, dataType, columns, rows)); } + +#ifdef VX_VERSION_1_1 + static Matrix createFromPattern(vx_context context, vx_enum pattern, vx_size columns, vx_size rows) + { return Matrix(vxCreateMatrixFromPattern(context, pattern, columns, rows)); } +#endif + +#ifndef VX_VERSION_1_1 + static const vx_enum + VX_MEMORY_TYPE_HOST = VX_IMPORT_TYPE_HOST, + VX_MATRIX_TYPE = VX_MATRIX_ATTRIBUTE_TYPE, + VX_MATRIX_ROWS = VX_MATRIX_ATTRIBUTE_ROWS, + VX_MATRIX_COLUMNS = VX_MATRIX_ATTRIBUTE_COLUMNS, + VX_MATRIX_SIZE = VX_MATRIX_ATTRIBUTE_SIZE; +#endif + + template + void query(vx_enum att, T& value) const + { IVX_CHECK_STATUS( vxQueryMatrix(ref, att, &value, sizeof(value)) ); } + + vx_enum dataType() const + { + vx_enum v; + query(VX_MATRIX_TYPE, v); + return v; + } + + vx_size columns() const + { + vx_size v; + query(VX_MATRIX_COLUMNS, v); + return v; + } + + vx_size rows() const + { + vx_size v; + query(VX_MATRIX_ROWS, v); + return v; + } + + vx_size size() const + { + vx_size v; + query(VX_MATRIX_SIZE, v); + return v; + } + +#ifdef VX_VERSION_1_1 + vx_coordinates2d_t origin() const + { + vx_coordinates2d_t v; + query(VX_MATRIX_ORIGIN, v); + return v; + } + + vx_enum pattern() const + { + vx_enum v; + query(VX_MATRIX_PATTERN, v); + return v; + } +#endif // VX_VERSION_1_1 + + void copyTo(void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): output pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyMatrix(ref, data, VX_READ_ONLY, VX_MEMORY_TYPE_HOST)); +#else + IVX_CHECK_STATUS(vxReadMatrix(ref, data)); +#endif + } + + void copyFrom(const void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): input pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyMatrix(ref, const_cast(data), VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST)); +#else + IVX_CHECK_STATUS(vxWriteMatrix(ref, data)); +#endif + } + + void copy(void* data, vx_enum usage, vx_enum memType = VX_MEMORY_TYPE_HOST) + { + if (!data) throw WrapperError(std::string(__func__) + "(): data pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyMatrix(ref, data, usage, memType)); +#else + if (usage == VX_READ_ONLY) + IVX_CHECK_STATUS(vxReadMatrix(ref, data)); + else if (usage == VX_WRITE_ONLY) + IVX_CHECK_STATUS(vxWriteMatrix(ref, data)); + else + throw WrapperError(std::string(__func__) + "(): unknown copy direction"); + (void)memType; +#endif + } + + template void copyTo(std::vector& data) + { + if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (data.size() != size()) + { + if (data.size() == 0) + data.resize(size()); + else + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + } + copyTo(&data[0]); + } + + template void copyFrom(const std::vector& data) + { + if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + if (data.size() != size()) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + copyFrom(&data[0]); + } + +#ifdef IVX_USE_OPENCV + void copyTo(cv::Mat& m) + { + if (m.type() != enumToCVType(dataType())) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (((vx_size)(m.rows) != rows() || (vx_size)(m.cols) != columns()) && !m.empty()) + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + + if (m.isContinuous() && (vx_size)(m.rows) == rows() && (vx_size)(m.cols) == columns()) + { + copyTo(m.ptr()); + } + else + { + cv::Mat tmp((int)rows(), (int)columns(), enumToCVType(dataType())); + copyTo(tmp.ptr()); + if (m.empty()) + m = tmp; + else + tmp.copyTo(m); + } + } + + void copyFrom(const cv::Mat& m) + { + if ((vx_size)(m.rows) != rows() || (vx_size)(m.cols) != columns()) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + if (m.type() != enumToCVType(dataType())) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + copyFrom(m.isContinuous() ? m.ptr() : m.clone().ptr()); + } +#endif //IVX_USE_OPENCV +}; /// Standard nodes namespace nodes { From e1503cc29efc49bfe483c715bc1b29b66b56a3df Mon Sep 17 00:00:00 2001 From: Pavel Rojtberg Date: Thu, 24 Nov 2016 13:56:08 +0100 Subject: [PATCH 114/409] calibrateCamera: specifiy what kind of re-projection error is returned --- modules/calib3d/include/opencv2/calib3d.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/calib3d/include/opencv2/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d.hpp index 6c61e6a854..023fe103e0 100644 --- a/modules/calib3d/include/opencv2/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d.hpp @@ -784,7 +784,7 @@ space, that is, a real position of the calibration pattern in the k-th pattern v @param stdDeviationsExtrinsics Output vector of standard deviations estimated for extrinsic parameters. Order of deviations values: \f$(R_1, T_1, \dotsc , R_M, T_M)\f$ where M is number of pattern views, \f$R_i, T_i\f$ are concatenated 1x3 vectors. - @param perViewErrors Output vector of average re-projection errors estimated for each pattern view. + @param perViewErrors Output vector of the RMS re-projection error estimated for each pattern view. @param flags Different flags that may be zero or a combination of the following values: - **CV_CALIB_USE_INTRINSIC_GUESS** cameraMatrix contains valid initial values of fx, fy, cx, cy that are optimized further. Otherwise, (cx, cy) is initially set to the image @@ -823,6 +823,8 @@ the optimization. If CV_CALIB_USE_INTRINSIC_GUESS is set, the coefficient from t supplied distCoeffs matrix is used. Otherwise, it is set to 0. @param criteria Termination criteria for the iterative optimization algorithm. +@return the overall RMS re-projection error. + The function estimates the intrinsic camera parameters and extrinsic parameters for each of the views. The algorithm is based on @cite Zhang2000 and @cite BouguetMCT . The coordinates of 3D object points and their corresponding 2D projections in each view must be specified. That may be achieved @@ -847,8 +849,6 @@ The algorithm performs the following steps: the projected (using the current estimates for camera parameters and the poses) object points objectPoints. See projectPoints for details. -The function returns the final re-projection error. - @note If you use a non-square (=non-NxN) grid and findChessboardCorners for calibration, and calibrateCamera returns bad values (zero distortion coefficients, an image center very far from From f5f91ed7f6dae882c6179a063567b4e6f11323be Mon Sep 17 00:00:00 2001 From: apavlenko Date: Thu, 24 Nov 2016 18:48:51 +0300 Subject: [PATCH 115/409] eliminating conflict with ivx.hpp --- 3rdparty/openvx/include/openvx_hal.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/include/openvx_hal.hpp index b027ff0e11..f067e40882 100644 --- a/3rdparty/openvx/include/openvx_hal.hpp +++ b/3rdparty/openvx/include/openvx_hal.hpp @@ -21,16 +21,16 @@ #if VX_VERSION == VX_VERSION_1_0 -#define VX_MEMORY_TYPE_HOST VX_IMPORT_TYPE_HOST -#define VX_INTERPOLATION_BILINEAR VX_INTERPOLATION_TYPE_BILINEAR -#define VX_INTERPOLATION_AREA VX_INTERPOLATION_TYPE_AREA -#define VX_INTERPOLATION_NEAREST_NEIGHBOR VX_INTERPOLATION_TYPE_NEAREST_NEIGHBOR -#define VX_IMAGE_RANGE VX_IMAGE_ATTRIBUTE_RANGE -#define VX_IMAGE_SPACE VX_IMAGE_ATTRIBUTE_SPACE -#define vx_border_t vx_border_mode_t -#define VX_BORDER_CONSTANT VX_BORDER_MODE_CONSTANT -#define VX_BORDER_REPLICATE VX_BORDER_MODE_REPLICATE -#define VX_CONTEXT_IMMEDIATE_BORDER VX_CONTEXT_ATTRIBUTE_IMMEDIATE_BORDER_MODE +static const vx_enum VX_MEMORY_TYPE_HOST = VX_IMPORT_TYPE_HOST; +static const vx_enum VX_INTERPOLATION_BILINEAR = VX_INTERPOLATION_TYPE_BILINEAR; +static const vx_enum VX_INTERPOLATION_AREA = VX_INTERPOLATION_TYPE_AREA; +static const vx_enum VX_INTERPOLATION_NEAREST_NEIGHBOR = VX_INTERPOLATION_TYPE_NEAREST_NEIGHBOR; +static const vx_enum VX_IMAGE_RANGE = VX_IMAGE_ATTRIBUTE_RANGE; +static const vx_enum VX_IMAGE_SPACE = VX_IMAGE_ATTRIBUTE_SPACE; +typedef vx_border_mode_t vx_border_t; +static const vx_enum VX_BORDER_CONSTANT = VX_BORDER_MODE_CONSTANT; +static const vx_enum VX_BORDER_REPLICATE = VX_BORDER_MODE_REPLICATE; +static const vx_enum VX_CONTEXT_IMMEDIATE_BORDER = VX_CONTEXT_ATTRIBUTE_IMMEDIATE_BORDER_MODE; #endif From 4a759abe393f85bbcfcc79171c73a3c98fdfb810 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 21 Oct 2016 17:57:33 +0300 Subject: [PATCH 116/409] Revert "Fix for VTK6.2 issue." This reverts commit f45da9866a94dbf52f5fb955bea67fce913ca515. --- modules/viz/src/vtk/vtkCocoaInteractorFix.mm | 237 ++++++------------- 1 file changed, 69 insertions(+), 168 deletions(-) diff --git a/modules/viz/src/vtk/vtkCocoaInteractorFix.mm b/modules/viz/src/vtk/vtkCocoaInteractorFix.mm index 481baf96ba..dad41b073e 100644 --- a/modules/viz/src/vtk/vtkCocoaInteractorFix.mm +++ b/modules/viz/src/vtk/vtkCocoaInteractorFix.mm @@ -1,54 +1,48 @@ /*M/////////////////////////////////////////////////////////////////////////////////////// - // - // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. - // - // By downloading, copying, installing or using the software you agree to this license. - // If you do not agree to this license, do not download, install, - // copy or use the software. - // - // - // License Agreement - // For Open Source Computer Vision Library - // - // Copyright (C) 2013, OpenCV Foundation, all rights reserved. - // Third party copyrights are property of their respective owners. - // - // Redistribution and use in source and binary forms, with or without modification, - // are permitted provided that the following conditions are met: - // - // * Redistribution's of source code must retain the above copyright notice, - // this list of conditions and the following disclaimer. - // - // * Redistribution's in binary form must reproduce the above copyright notice, - // this list of conditions and the following disclaimer in the documentation - // and/or other materials provided with the distribution. - // - // * The name of the copyright holders may not be used to endorse or promote products - // derived from this software without specific prior written permission. - // - // This software is provided by the copyright holders and contributors "as is" and - // any express or implied warranties, including, but not limited to, the implied - // warranties of merchantability and fitness for a particular purpose are disclaimed. - // In no event shall the Intel Corporation or contributors be liable for any direct, - // indirect, incidental, special, exemplary, or consequential damages - // (including, but not limited to, procurement of substitute goods or services; - // loss of use, data, or profits; or business interruption) however caused - // and on any theory of liability, whether in contract, strict liability, - // or tort (including negligence or otherwise) arising in any way out of - // the use of this software, even if advised of the possibility of such damage. - // - // Authors: - // * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com - // - // This workaround code was taken from PCL library(www.pointclouds.org) - // - // Modified by Jasper Shemilt to work with VTK 6.2 - // The fix was needed because GetCocoaServer has been moved from - // vtkCocoaRenderWindowInteractor to vtkCocoaRenderWindow in VTK 6.2. - // This alteration to VTK happened almost a year ago according to the gitHub - // commit a3e9fc9. - // - //M*/ +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +// This workaround code was taken from PCL library(www.pointclouds.org) +// +//M*/ #import #include @@ -124,14 +118,14 @@ [application stop:application]; NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined - location:NSMakePoint(0.0,0.0) - modifierFlags:0 - timestamp:0 - windowNumber:-1 - context:nil - subtype:0 - data1:0 - data2:0]; + location:NSMakePoint(0.0,0.0) + modifierFlags:0 + timestamp:0 + windowNumber:-1 + context:nil + subtype:0 + data1:0 + data2:0]; [application postEvent:event atStart:YES]; } @@ -160,121 +154,30 @@ //---------------------------------------------------------------------------- -#if VTK_MAJOR_VERSION >= 6 && VTK_MINOR_VERSION >=2 - namespace cv { namespace viz +{ + class vtkCocoaRenderWindowInteractorFix : public vtkCocoaRenderWindowInteractor { - class vtkCocoaRenderWindowInteractorFix : public vtkCocoaRenderWindowInteractor - { - public: - static vtkCocoaRenderWindowInteractorFix *New (); - vtkTypeMacro (vtkCocoaRenderWindowInteractorFix, vtkCocoaRenderWindowInteractor) + public: + static vtkCocoaRenderWindowInteractorFix *New (); + vtkTypeMacro (vtkCocoaRenderWindowInteractorFix, vtkCocoaRenderWindowInteractor) - virtual void Start (); - virtual void TerminateApp (); + virtual void Start (); + virtual void TerminateApp (); - protected: - vtkCocoaRenderWindowInteractorFix () {} - ~vtkCocoaRenderWindowInteractorFix () {} + protected: + vtkCocoaRenderWindowInteractorFix () {} + ~vtkCocoaRenderWindowInteractorFix () {} - private: - vtkCocoaRenderWindowInteractorFix (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. - void operator = (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. - }; + private: + vtkCocoaRenderWindowInteractorFix (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. + void operator = (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. + }; - vtkStandardNewMacro (vtkCocoaRenderWindowInteractorFix) + vtkStandardNewMacro (vtkCocoaRenderWindowInteractorFix) - vtkSmartPointer vtkCocoaRenderWindowInteractorNew(); - - class vtkCocoaRenderWindowFix : public vtkCocoaRenderWindow - { - public: - static vtkCocoaRenderWindowFix *New (); - vtkTypeMacro ( vtkCocoaRenderWindowFix, vtkCocoaRenderWindow) - - virtual vtkCocoaServerFix * GetCocoaServer (); - virtual void SetCocoaServer (void* ); - - protected: - vtkCocoaRenderWindowFix () {} - ~vtkCocoaRenderWindowFix () {} - - private: - vtkCocoaRenderWindowFix (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. - void operator = (const vtkCocoaRenderWindowFix&); // Not implemented. - }; - - vtkStandardNewMacro (vtkCocoaRenderWindowFix) - - vtkSmartPointer vtkCocoaRenderWindowNew(); - }} - -vtkCocoaServerFix * cv::viz::vtkCocoaRenderWindowFix::GetCocoaServer () -{ - return reinterpret_cast (this->GetCocoaServer ()); -} - -void cv::viz::vtkCocoaRenderWindowFix::SetCocoaServer (void* server) -{ - this->SetCocoaServer (server); -} - -void cv::viz::vtkCocoaRenderWindowInteractorFix::Start () -{ - vtkCocoaRenderWindowFix* renWin = vtkCocoaRenderWindowFix::SafeDownCast(this->GetRenderWindow ()); - if (renWin != NULL) - { - vtkCocoaServerFix *server = reinterpret_cast (renWin->GetCocoaServer ()); - if (!renWin->GetCocoaServer ()) - { - server = [vtkCocoaServerFix cocoaServerWithRenderWindow:renWin]; - renWin->SetCocoaServer (reinterpret_cast (server)); - } - - [server start]; - } -} - -void cv::viz::vtkCocoaRenderWindowInteractorFix::TerminateApp () -{ - vtkCocoaRenderWindowFix *renWin = vtkCocoaRenderWindowFix::SafeDownCast (this->RenderWindow); - if (renWin) - { - vtkCocoaServerFix *server = reinterpret_cast (renWin->GetCocoaServer ()); - [server stop]; - } -} - -vtkSmartPointer cv::viz::vtkCocoaRenderWindowInteractorNew() -{ - return vtkSmartPointer::New(); -} - -#else -namespace cv { namespace viz - { - class vtkCocoaRenderWindowInteractorFix : public vtkCocoaRenderWindowInteractor - { - public: - static vtkCocoaRenderWindowInteractorFix *New (); - vtkTypeMacro (vtkCocoaRenderWindowInteractorFix, vtkCocoaRenderWindowInteractor) - - virtual void Start (); - virtual void TerminateApp (); - - protected: - vtkCocoaRenderWindowInteractorFix () {} - ~vtkCocoaRenderWindowInteractorFix () {} - - private: - vtkCocoaRenderWindowInteractorFix (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. - void operator = (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. - }; - - vtkStandardNewMacro (vtkCocoaRenderWindowInteractorFix) - - vtkSmartPointer vtkCocoaRenderWindowInteractorNew(); - }} + vtkSmartPointer vtkCocoaRenderWindowInteractorNew(); +}} void cv::viz::vtkCocoaRenderWindowInteractorFix::Start () { @@ -306,5 +209,3 @@ vtkSmartPointer cv::viz::vtkCocoaRenderWindowInteract { return vtkSmartPointer::New(); } - -#endif From 6fb75c677b9ed1c1e2907c418224c9fdfa06a94d Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 21 Oct 2016 18:19:15 +0300 Subject: [PATCH 117/409] viz: update for VTK6.2+ PCL PR 1205 --- modules/viz/src/vtk/vtkCocoaInteractorFix.mm | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/viz/src/vtk/vtkCocoaInteractorFix.mm b/modules/viz/src/vtk/vtkCocoaInteractorFix.mm index dad41b073e..99e3c0d1a7 100644 --- a/modules/viz/src/vtk/vtkCocoaInteractorFix.mm +++ b/modules/viz/src/vtk/vtkCocoaInteractorFix.mm @@ -50,6 +50,13 @@ #include #include +namespace cv { namespace viz { + vtkSmartPointer vtkCocoaRenderWindowInteractorNew(); +}} // namespace + +#if ((VTK_MAJOR_VERSION < 6) || ((VTK_MAJOR_VERSION == 6) && (VTK_MINOR_VERSION < 2))) + + //---------------------------------------------------------------------------- @interface vtkCocoaServerFix : NSObject { @@ -175,8 +182,6 @@ namespace cv { namespace viz }; vtkStandardNewMacro (vtkCocoaRenderWindowInteractorFix) - - vtkSmartPointer vtkCocoaRenderWindowInteractorNew(); }} void cv::viz::vtkCocoaRenderWindowInteractorFix::Start () @@ -209,3 +214,13 @@ vtkSmartPointer cv::viz::vtkCocoaRenderWindowInteract { return vtkSmartPointer::New(); } + + +#else + +vtkSmartPointer cv::viz::vtkCocoaRenderWindowInteractorNew() +{ + return vtkSmartPointer::New(); +} + +#endif From 11b642fb8cc342f9f4e33ab0cf8f1383dba5da5d Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 21 Oct 2016 18:22:07 +0300 Subject: [PATCH 118/409] viz: eliminate warnings from tutorial code --- samples/cpp/tutorial_code/viz/creating_widgets.cpp | 2 +- samples/cpp/tutorial_code/viz/launching_viz.cpp | 2 +- samples/cpp/tutorial_code/viz/transformations.cpp | 4 ++-- samples/cpp/tutorial_code/viz/widget_pose.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/cpp/tutorial_code/viz/creating_widgets.cpp b/samples/cpp/tutorial_code/viz/creating_widgets.cpp index 9ee6c54364..db6fb489a0 100644 --- a/samples/cpp/tutorial_code/viz/creating_widgets.cpp +++ b/samples/cpp/tutorial_code/viz/creating_widgets.cpp @@ -24,7 +24,7 @@ using namespace std; * @function help * @brief Display instructions to use this tutorial program */ -void help() +static void help() { cout << "--------------------------------------------------------------------------" << endl diff --git a/samples/cpp/tutorial_code/viz/launching_viz.cpp b/samples/cpp/tutorial_code/viz/launching_viz.cpp index 55a3ddee2c..4cc90febfb 100644 --- a/samples/cpp/tutorial_code/viz/launching_viz.cpp +++ b/samples/cpp/tutorial_code/viz/launching_viz.cpp @@ -14,7 +14,7 @@ using namespace std; * @function help * @brief Display instructions to use this tutorial program */ -void help() +static void help() { cout << "--------------------------------------------------------------------------" << endl diff --git a/samples/cpp/tutorial_code/viz/transformations.cpp b/samples/cpp/tutorial_code/viz/transformations.cpp index d8713fddc1..fe26c4ebee 100644 --- a/samples/cpp/tutorial_code/viz/transformations.cpp +++ b/samples/cpp/tutorial_code/viz/transformations.cpp @@ -15,7 +15,7 @@ using namespace std; * @function help * @brief Display instructions to use this tutorial program */ -void help() +static void help() { cout << "--------------------------------------------------------------------------" << endl @@ -31,7 +31,7 @@ void help() * @function cvcloud_load * @brief load bunny.ply */ -Mat cvcloud_load() +static Mat cvcloud_load() { Mat cloud(1, 1889, CV_32FC3); ifstream ifs("bunny.ply"); diff --git a/samples/cpp/tutorial_code/viz/widget_pose.cpp b/samples/cpp/tutorial_code/viz/widget_pose.cpp index 3ecead8d93..d36f4fe376 100644 --- a/samples/cpp/tutorial_code/viz/widget_pose.cpp +++ b/samples/cpp/tutorial_code/viz/widget_pose.cpp @@ -15,7 +15,7 @@ using namespace std; * @function help * @brief Display instructions to use this tutorial program */ -void help() +static void help() { cout << "--------------------------------------------------------------------------" << endl From 7dc673fd56aa753e870d7e0d3b3f6236e215cba0 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 25 Nov 2016 18:19:43 +0300 Subject: [PATCH 119/409] imgproc: fix findContours (submatrix input) --- modules/imgproc/src/contours.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/src/contours.cpp b/modules/imgproc/src/contours.cpp index 0adc01b1a7..baa27c4ac3 100644 --- a/modules/imgproc/src/contours.cpp +++ b/modules/imgproc/src/contours.cpp @@ -1904,7 +1904,7 @@ void cv::findContours( InputOutputArray _image, OutputArrayOfArrays _contours, CV_Assert(_contours.empty() || (_contours.channels() == 2 && _contours.depth() == CV_32S)); Mat image; - copyMakeBorder(_image, image, 1, 1, 1, 1, BORDER_CONSTANT, Scalar(0)); + copyMakeBorder(_image, image, 1, 1, 1, 1, BORDER_CONSTANT | BORDER_ISOLATED, Scalar(0)); MemStorage storage(cvCreateMemStorage()); CvMat _cimage = image; CvSeq* _ccontours = 0; From 8098e5b04570c60b66eb4791b8ac7e11feeef25e Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Tue, 18 Oct 2016 01:02:01 +0300 Subject: [PATCH 120/409] Added OpenVX based implementation for Gray2BGR conversion HAL API --- 3rdparty/openvx/include/openvx_hal.hpp | 52 +++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/include/openvx_hal.hpp index f067e40882..4adf695976 100644 --- a/3rdparty/openvx/include/openvx_hal.hpp +++ b/3rdparty/openvx/include/openvx_hal.hpp @@ -225,6 +225,26 @@ struct vxImage void *ptrs[] = { (void*)data }; img = vxCreateImageFromHandle(ctx.ctx, VX_Traits::ImgType, &addr, ptrs, VX_MEMORY_TYPE_HOST); vxErr::check(img); + swapMemory = true; + } + template + vxImage(vxContext &ctx, T value, int w, int h) + { +#if VX_VERSION > VX_VERSION_1_0 + vx_pixel_value_t pixel; + switch ((int)(VX_Traits::DataType)) + { + case VX_TYPE_UINT8:pixel.U8 = value; break; + case VX_TYPE_UINT16:pixel.U16 = value; break; + case VX_TYPE_INT16:pixel.S16 = value; break; + default:vxErr(VX_ERROR_INVALID_PARAMETERS, "uniform image creation").check(); + } + img = vxCreateUniformImage(ctx.ctx, w, h, VX_Traits::ImgType, &pixel); +#else + img = vxCreateUniformImage(ctx.ctx, w, h, VX_Traits::ImgType, &value); +#endif + vxErr::check(img); + swapMemory = false; } vxImage(vxContext &ctx, int imgType, const uchar *data, size_t step, int w, int h) { @@ -295,14 +315,18 @@ struct vxImage } img = vxCreateImageFromHandle(ctx.ctx, imgType, addr, ptrs, VX_MEMORY_TYPE_HOST); vxErr::check(img); + swapMemory = true; } ~vxImage() { #if VX_VERSION > VX_VERSION_1_0 - vxErr::check(vxSwapImageHandle(img, NULL, NULL, 1)); + if (swapMemory) + vxErr::check(vxSwapImageHandle(img, NULL, NULL, 1)); #endif vxReleaseImage(&img); } +private: + bool swapMemory; }; struct vxMatrix @@ -999,6 +1023,30 @@ inline int ovx_hal_cvtBGRtoBGR(const uchar * a, size_t astep, uchar * b, size_t return CV_HAL_ERROR_OK; } +inline int ovx_hal_cvtGraytoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int depth, int bcn) +{ + if(dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + if (depth != CV_8U || (bcn != 3 && bcn != 4)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + try + { + vxContext * ctx = vxContext::getContext(); + vxImage ia(*ctx, a, astep, w, h); + vxImage ib(*ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, b, bstep, w, h); + vxErr::check(vxuChannelCombine(ctx->ctx, ia.img, ia.img, ia.img, + bcn == 4 ? vxImage(*ctx, uchar(255), w, h).img : NULL, + ib.img)); + } + catch (vxErr & e) + { + e.print(); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + inline int ovx_hal_cvtTwoPlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx) { if(dimTooBig(w) || dimTooBig(h)) @@ -1183,6 +1231,8 @@ inline int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, #undef cv_hal_cvtBGRtoBGR #define cv_hal_cvtBGRtoBGR ovx_hal_cvtBGRtoBGR +#undef cv_hal_cvtGraytoBGR +#define cv_hal_cvtGraytoBGR ovx_hal_cvtGraytoBGR #undef cv_hal_cvtTwoPlaneYUVtoBGR #define cv_hal_cvtTwoPlaneYUVtoBGR ovx_hal_cvtTwoPlaneYUVtoBGR #undef cv_hal_cvtThreePlaneYUVtoBGR From e878b60dbcd3116b7ab6bd456d29228655bcdb00 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 15 Nov 2016 18:55:23 +0300 Subject: [PATCH 121/409] core: drop type/dims/rows/cols information in Mat::release() --- modules/core/include/opencv2/core/mat.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 631c698081..bebe2262b2 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -368,6 +368,16 @@ inline void Mat::release() data = datastart = dataend = datalimit = 0; for(int i = 0; i < dims; i++) size.p[i] = 0; +#ifdef _DEBUG + flags = MAGIC_VAL; + dims = rows = cols = 0; + if (step.p != step.buf) + { + fastFree(step.p); + step.p = step.buf; + size.p = &rows; + } +#endif refcount = 0; } From 56ed8b172137cd737c393cd0d5978922c822a310 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 15 Nov 2016 22:25:42 +0300 Subject: [PATCH 122/409] calib3d: fix fisheye for test fisheyeTest.stereoCalibrateFixIntrinsic --- modules/calib3d/src/fisheye.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 9882cd8792..a2f117b028 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -1028,8 +1028,10 @@ double cv::fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO int a = cv::countNonZero(intrinsicLeft.isEstimate); int b = cv::countNonZero(intrinsicRight.isEstimate); cv::Mat deltas = J2_inv * J.t() * e; - intrinsicLeft = intrinsicLeft + deltas.rowRange(0, a); - intrinsicRight = intrinsicRight + deltas.rowRange(a, a + b); + if (a > 0) + intrinsicLeft = intrinsicLeft + deltas.rowRange(0, a); + if (b > 0) + intrinsicRight = intrinsicRight + deltas.rowRange(a, a + b); omcur = omcur + cv::Vec3d(deltas.rowRange(a + b, a + b + 3)); Tcur = Tcur + cv::Vec3d(deltas.rowRange(a + b + 3, a + b + 6)); for (int image_idx = 0; image_idx < n_images; ++image_idx) From 3f37e34e76a1031e075f6c8a1703a9085feda50e Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 25 Nov 2016 16:16:25 +0300 Subject: [PATCH 123/409] highgui: g_thread_create deprecated warning --- modules/highgui/src/window_gtk.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/highgui/src/window_gtk.cpp b/modules/highgui/src/window_gtk.cpp index 8ead11d6ee..6e4da8b320 100644 --- a/modules/highgui/src/window_gtk.cpp +++ b/modules/highgui/src/window_gtk.cpp @@ -619,9 +619,13 @@ CV_IMPL int cvStartWindowThread(){ // conditional that indicates a key has been pressed cond_have_key = g_cond_new(); +#if !GLIB_CHECK_VERSION(2, 32, 0) // this is the window update thread window_thread = g_thread_create((GThreadFunc) icvWindowThreadLoop, NULL, TRUE, NULL); +#else + window_thread = g_thread_new("OpenCV window update", (GThreadFunc)icvWindowThreadLoop, NULL); +#endif } thread_started = window_thread!=NULL; return thread_started; From 1a3f1a31b2545de9a7666aed785feec710cd1896 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 25 Nov 2016 14:56:00 +0300 Subject: [PATCH 124/409] ts: CV_ENUM fix static initialization order fiasco --- modules/ts/include/opencv2/ts/ts_perf.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/ts/include/opencv2/ts/ts_perf.hpp b/modules/ts/include/opencv2/ts/ts_perf.hpp index 5b15a9eb01..bfa181195d 100644 --- a/modules/ts/include/opencv2/ts/ts_perf.hpp +++ b/modules/ts/include/opencv2/ts/ts_perf.hpp @@ -95,11 +95,11 @@ private: #define CV_ENUM(class_name, ...) \ namespace { \ + using namespace cv;using namespace cv::cuda; using namespace cv::ocl; \ struct class_name { \ class_name(int val = 0) : val_(val) {} \ operator int() const { return val_; } \ void PrintTo(std::ostream* os) const { \ - using namespace cv;using namespace cv::cuda; using namespace cv::ocl; \ const int vals[] = { __VA_ARGS__ }; \ const char* svals = #__VA_ARGS__; \ for(int i = 0, pos = 0; i < (int)(sizeof(vals)/sizeof(int)); ++i) { \ @@ -115,8 +115,7 @@ private: *os << "UNKNOWN"; \ } \ static ::testing::internal::ParamGenerator all() { \ - using namespace cv;using namespace cv::cuda; using namespace cv::ocl; \ - static class_name vals[] = { __VA_ARGS__ }; \ + const class_name vals[] = { __VA_ARGS__ }; \ return ::testing::ValuesIn(vals); \ } \ private: int val_; \ From 90da5933165c4bc1b5cc91547bb5123a01478cfd Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 25 Nov 2016 16:16:25 +0300 Subject: [PATCH 125/409] highgui: g_thread_create deprecated warning --- modules/highgui/src/window_gtk.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/highgui/src/window_gtk.cpp b/modules/highgui/src/window_gtk.cpp index bb692249c0..cfdf3e82a9 100644 --- a/modules/highgui/src/window_gtk.cpp +++ b/modules/highgui/src/window_gtk.cpp @@ -480,9 +480,13 @@ CV_IMPL int cvStartWindowThread(){ // conditional that indicates a key has been pressed cond_have_key = g_cond_new(); +#if !GLIB_CHECK_VERSION(2, 32, 0) // this is the window update thread window_thread = g_thread_create((GThreadFunc) icvWindowThreadLoop, NULL, TRUE, NULL); +#else + window_thread = g_thread_new("OpenCV window update", (GThreadFunc)icvWindowThreadLoop, NULL); +#endif } thread_started = window_thread!=NULL; return thread_started; From e5041abe550c3c4c6dcb3208b8b6bb987089fd39 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sun, 27 Nov 2016 20:42:39 +0300 Subject: [PATCH 126/409] cmake: remove outdated pkg-config module --- CMakeLists.txt | 4 - cmake/OpenCVFindPkgConfig.cmake | 365 -------------------------------- cmake/OpenCVUtils.cmake | 7 + 3 files changed, 7 insertions(+), 369 deletions(-) delete mode 100644 cmake/OpenCVFindPkgConfig.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 91a6462012..2dd3668348 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -445,10 +445,6 @@ endif(WIN32 AND NOT MINGW) # CHECK FOR SYSTEM LIBRARIES, OPTIONS, ETC.. # ---------------------------------------------------------------------------- if(UNIX) - include(cmake/OpenCVFindPkgConfig.cmake OPTIONAL) - include(CheckFunctionExists) - include(CheckIncludeFile) - if(NOT APPLE) CHECK_INCLUDE_FILE(pthread.h HAVE_LIBPTHREAD) if(ANDROID) diff --git a/cmake/OpenCVFindPkgConfig.cmake b/cmake/OpenCVFindPkgConfig.cmake deleted file mode 100644 index 7e439fd8dc..0000000000 --- a/cmake/OpenCVFindPkgConfig.cmake +++ /dev/null @@ -1,365 +0,0 @@ -# -# OpenCV note: the file has been extracted from CMake 2.6.2 distribution. -# It is used to build OpenCV with CMake 2.4.x -# - -# - a pkg-config module for CMake -# -# Usage: -# pkg_check_modules( [REQUIRED] []*) -# checks for all the given modules -# -# pkg_search_module( [REQUIRED] []*) -# checks for given modules and uses the first working one -# -# When the 'REQUIRED' argument was set, macros will fail with an error -# when module(s) could not be found -# -# It sets the following variables: -# PKG_CONFIG_FOUND ... true if pkg-config works on the system -# PKG_CONFIG_EXECUTABLE ... pathname of the pkg-config program -# _FOUND ... set to 1 if module(s) exist -# -# For the following variables two sets of values exist; first one is the -# common one and has the given PREFIX. The second set contains flags -# which are given out when pkgconfig was called with the '--static' -# option. -# _LIBRARIES ... only the libraries (w/o the '-l') -# _LIBRARY_DIRS ... the paths of the libraries (w/o the '-L') -# _LDFLAGS ... all required linker flags -# _LDFLAGS_OTHER ... all other linker flags -# _INCLUDE_DIRS ... the '-I' preprocessor flags (w/o the '-I') -# _CFLAGS ... all required cflags -# _CFLAGS_OTHER ... the other compiler flags -# -# = for common case -# = _STATIC for static linking -# -# There are some special variables whose prefix depends on the count -# of given modules. When there is only one module, stays -# unchanged. When there are multiple modules, the prefix will be -# changed to _: -# _VERSION ... version of the module -# _PREFIX ... prefix-directory of the module -# _INCLUDEDIR ... include-dir of the module -# _LIBDIR ... lib-dir of the module -# -# = when |MODULES| == 1, else -# = _ -# -# A parameter can have the following formats: -# {MODNAME} ... matches any version -# {MODNAME}>={VERSION} ... at least version is required -# {MODNAME}={VERSION} ... exactly version is required -# {MODNAME}<={VERSION} ... modules must not be newer than -# -# Examples -# pkg_check_modules (GLIB2 glib-2.0) -# -# pkg_check_modules (GLIB2 glib-2.0>=2.10) -# requires at least version 2.10 of glib2 and defines e.g. -# GLIB2_VERSION=2.10.3 -# -# pkg_check_modules (FOO glib-2.0>=2.10 gtk+-2.0) -# requires both glib2 and gtk2, and defines e.g. -# FOO_glib-2.0_VERSION=2.10.3 -# FOO_gtk+-2.0_VERSION=2.8.20 -# -# pkg_check_modules (XRENDER REQUIRED xrender) -# defines e.g.: -# XRENDER_LIBRARIES=Xrender;X11 -# XRENDER_STATIC_LIBRARIES=Xrender;X11;pthread;Xau;Xdmcp -# -# pkg_search_module (BAR libxml-2.0 libxml2 libxml>=2) - - -# Copyright (C) 2006 Enrico Scholz -# -# Redistribution and use, with or without modification, are permitted -# provided that the following conditions are met: -# -# 1. Redistributions must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# 2. The name of the author may not be used to endorse or promote -# products derived from this software without specific prior -# written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -### Common stuff #### -set(PKG_CONFIG_VERSION 1) -set(PKG_CONFIG_FOUND 0) - -find_program(PKG_CONFIG_EXECUTABLE NAMES pkg-config DOC "pkg-config executable") -mark_as_advanced(PKG_CONFIG_EXECUTABLE) - -if(PKG_CONFIG_EXECUTABLE) - set(PKG_CONFIG_FOUND 1) -endif(PKG_CONFIG_EXECUTABLE) - - -# Unsets the given variables -macro(_pkgconfig_unset var) - set(${var} "" CACHE INTERNAL "") -endmacro(_pkgconfig_unset) - -macro(_pkgconfig_set var value) - set(${var} ${value} CACHE INTERNAL "") -endmacro(_pkgconfig_set) - -# Invokes pkgconfig, cleans up the result and sets variables -macro(_pkgconfig_invoke _pkglist _prefix _varname _regexp) - set(_pkgconfig_invoke_result) - - execute_process( - COMMAND ${PKG_CONFIG_EXECUTABLE} ${ARGN} ${_pkglist} - OUTPUT_VARIABLE _pkgconfig_invoke_result - RESULT_VARIABLE _pkgconfig_failed) - - if (_pkgconfig_failed) - set(_pkgconfig_${_varname} "") - _pkgconfig_unset(${_prefix}_${_varname}) - else(_pkgconfig_failed) - string(REGEX REPLACE "[\r\n]" " " _pkgconfig_invoke_result "${_pkgconfig_invoke_result}") - string(REGEX REPLACE " +$" "" _pkgconfig_invoke_result "${_pkgconfig_invoke_result}") - - if (NOT ${_regexp} STREQUAL "") - string(REGEX REPLACE "${_regexp}" " " _pkgconfig_invoke_result "${_pkgconfig_invoke_result}") - endif(NOT ${_regexp} STREQUAL "") - - separate_arguments(_pkgconfig_invoke_result) - - #message(STATUS " ${_varname} ... ${_pkgconfig_invoke_result}") - set(_pkgconfig_${_varname} ${_pkgconfig_invoke_result}) - _pkgconfig_set(${_prefix}_${_varname} "${_pkgconfig_invoke_result}") - endif(_pkgconfig_failed) -endmacro(_pkgconfig_invoke) - -# Invokes pkgconfig two times; once without '--static' and once with -# '--static' -macro(_pkgconfig_invoke_dyn _pkglist _prefix _varname cleanup_regexp) - _pkgconfig_invoke("${_pkglist}" ${_prefix} ${_varname} "${cleanup_regexp}" ${ARGN}) - _pkgconfig_invoke("${_pkglist}" ${_prefix} STATIC_${_varname} "${cleanup_regexp}" --static ${ARGN}) -endmacro(_pkgconfig_invoke_dyn) - -# Splits given arguments into options and a package list -macro(_pkgconfig_parse_options _result _is_req) - set(${_is_req} 0) - - foreach(_pkg ${ARGN}) - if (_pkg STREQUAL "REQUIRED") - set(${_is_req} 1) - endif (_pkg STREQUAL "REQUIRED") - endforeach(_pkg ${ARGN}) - - set(${_result} ${ARGN}) - list(REMOVE_ITEM ${_result} "REQUIRED") -endmacro(_pkgconfig_parse_options) - -### -macro(_pkg_check_modules_internal _is_required _is_silent _prefix) - _pkgconfig_unset(${_prefix}_FOUND) - _pkgconfig_unset(${_prefix}_VERSION) - _pkgconfig_unset(${_prefix}_PREFIX) - _pkgconfig_unset(${_prefix}_INCLUDEDIR) - _pkgconfig_unset(${_prefix}_LIBDIR) - _pkgconfig_unset(${_prefix}_LIBS) - _pkgconfig_unset(${_prefix}_LIBS_L) - _pkgconfig_unset(${_prefix}_LIBS_PATHS) - _pkgconfig_unset(${_prefix}_LIBS_OTHER) - _pkgconfig_unset(${_prefix}_CFLAGS) - _pkgconfig_unset(${_prefix}_CFLAGS_I) - _pkgconfig_unset(${_prefix}_CFLAGS_OTHER) - _pkgconfig_unset(${_prefix}_STATIC_LIBDIR) - _pkgconfig_unset(${_prefix}_STATIC_LIBS) - _pkgconfig_unset(${_prefix}_STATIC_LIBS_L) - _pkgconfig_unset(${_prefix}_STATIC_LIBS_PATHS) - _pkgconfig_unset(${_prefix}_STATIC_LIBS_OTHER) - _pkgconfig_unset(${_prefix}_STATIC_CFLAGS) - _pkgconfig_unset(${_prefix}_STATIC_CFLAGS_I) - _pkgconfig_unset(${_prefix}_STATIC_CFLAGS_OTHER) - - # create a better addressable variable of the modules and calculate its size - set(_pkg_check_modules_list ${ARGN}) - list(LENGTH _pkg_check_modules_list _pkg_check_modules_cnt) - - if(PKG_CONFIG_EXECUTABLE) - # give out status message telling checked module - if (NOT ${_is_silent}) - if (_pkg_check_modules_cnt EQUAL 1) - message(STATUS "checking for module '${_pkg_check_modules_list}'") - else(_pkg_check_modules_cnt EQUAL 1) - message(STATUS "checking for modules '${_pkg_check_modules_list}'") - endif(_pkg_check_modules_cnt EQUAL 1) - endif(NOT ${_is_silent}) - - set(_pkg_check_modules_packages) - set(_pkg_check_modules_failed) - - # iterate through module list and check whether they exist and match the required version - foreach (_pkg_check_modules_pkg ${_pkg_check_modules_list}) - set(_pkg_check_modules_exist_query) - - # check whether version is given - if (_pkg_check_modules_pkg MATCHES ".*(>=|=|<=).*") - string(REGEX REPLACE "(.*[^><])(>=|=|<=)(.*)" "\\1" _pkg_check_modules_pkg_name "${_pkg_check_modules_pkg}") - string(REGEX REPLACE "(.*[^><])(>=|=|<=)(.*)" "\\2" _pkg_check_modules_pkg_op "${_pkg_check_modules_pkg}") - string(REGEX REPLACE "(.*[^><])(>=|=|<=)(.*)" "\\3" _pkg_check_modules_pkg_ver "${_pkg_check_modules_pkg}") - else(_pkg_check_modules_pkg MATCHES ".*(>=|=|<=).*") - set(_pkg_check_modules_pkg_name "${_pkg_check_modules_pkg}") - set(_pkg_check_modules_pkg_op) - set(_pkg_check_modules_pkg_ver) - endif(_pkg_check_modules_pkg MATCHES ".*(>=|=|<=).*") - - # handle the operands - if (_pkg_check_modules_pkg_op STREQUAL ">=") - list(APPEND _pkg_check_modules_exist_query --atleast-version) - endif(_pkg_check_modules_pkg_op STREQUAL ">=") - - if (_pkg_check_modules_pkg_op STREQUAL "=") - list(APPEND _pkg_check_modules_exist_query --exact-version) - endif(_pkg_check_modules_pkg_op STREQUAL "=") - - if (_pkg_check_modules_pkg_op STREQUAL "<=") - list(APPEND _pkg_check_modules_exist_query --max-version) - endif(_pkg_check_modules_pkg_op STREQUAL "<=") - - # create the final query which is of the format: - # * --atleast-version - # * --exact-version - # * --max-version - # * --exists - if (_pkg_check_modules_pkg_op) - list(APPEND _pkg_check_modules_exist_query "${_pkg_check_modules_pkg_ver}") - else(_pkg_check_modules_pkg_op) - list(APPEND _pkg_check_modules_exist_query --exists) - endif(_pkg_check_modules_pkg_op) - - _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_VERSION) - _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_PREFIX) - _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_INCLUDEDIR) - _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_LIBDIR) - - list(APPEND _pkg_check_modules_exist_query "${_pkg_check_modules_pkg_name}") - list(APPEND _pkg_check_modules_packages "${_pkg_check_modules_pkg_name}") - - # execute the query - execute_process( - COMMAND ${PKG_CONFIG_EXECUTABLE} ${_pkg_check_modules_exist_query} - RESULT_VARIABLE _pkgconfig_retval) - - # evaluate result and tell failures - if (_pkgconfig_retval) - if(NOT ${_is_silent}) - message(STATUS " package '${_pkg_check_modules_pkg}' not found") - endif(NOT ${_is_silent}) - - set(_pkg_check_modules_failed 1) - endif(_pkgconfig_retval) - endforeach(_pkg_check_modules_pkg) - - if(_pkg_check_modules_failed) - # fail when requested - if (${_is_required}) - message(SEND_ERROR "A required package was not found") - endif (${_is_required}) - else(_pkg_check_modules_failed) - # when we are here, we checked whether requested modules - # exist. Now, go through them and set variables - - _pkgconfig_set(${_prefix}_FOUND 1) - list(LENGTH _pkg_check_modules_packages pkg_count) - - # iterate through all modules again and set individual variables - foreach (_pkg_check_modules_pkg ${_pkg_check_modules_packages}) - # handle case when there is only one package required - if (pkg_count EQUAL 1) - set(_pkg_check_prefix "${_prefix}") - else(pkg_count EQUAL 1) - set(_pkg_check_prefix "${_prefix}_${_pkg_check_modules_pkg}") - endif(pkg_count EQUAL 1) - - _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" VERSION "" --modversion ) - _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" PREFIX "" --variable=prefix ) - _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" INCLUDEDIR "" --variable=includedir ) - _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" LIBDIR "" --variable=libdir ) - - message(STATUS " found ${_pkg_check_modules_pkg}, version ${_pkgconfig_VERSION}") - endforeach(_pkg_check_modules_pkg) - - # set variables which are combined for multiple modules - _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LIBRARIES "(^| )-l" --libs-only-l ) - _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LIBRARY_DIRS "(^| )-L" --libs-only-L ) - _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LDFLAGS "" --libs ) - _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LDFLAGS_OTHER "" --libs-only-other ) - - _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" INCLUDE_DIRS "(^| )-I" --cflags-only-I ) - _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" CFLAGS "" --cflags ) - _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" CFLAGS_OTHER "" --cflags-only-other ) - endif(_pkg_check_modules_failed) - else(PKG_CONFIG_EXECUTABLE) - if (${_is_required}) - message(SEND_ERROR "pkg-config tool not found") - endif (${_is_required}) - endif(PKG_CONFIG_EXECUTABLE) -endmacro(_pkg_check_modules_internal) - -### -### User visible macros start here -### - -### -macro(pkg_check_modules _prefix _module0) - # check cached value - if (NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION} OR NOT ${_prefix}_FOUND) - _pkgconfig_parse_options (_pkg_modules _pkg_is_required "${_module0}" ${ARGN}) - _pkg_check_modules_internal("${_pkg_is_required}" 0 "${_prefix}" ${_pkg_modules}) - - _pkgconfig_set(__pkg_config_checked_${_prefix} ${PKG_CONFIG_VERSION}) - endif(NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION} OR NOT ${_prefix}_FOUND) -endmacro(pkg_check_modules) - -### -macro(pkg_search_module _prefix _module0) - # check cached value - if (NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION} OR NOT ${_prefix}_FOUND) - set(_pkg_modules_found 0) - _pkgconfig_parse_options(_pkg_modules_alt _pkg_is_required "${_module0}" ${ARGN}) - - message(STATUS "checking for one of the modules '${_pkg_modules_alt}'") - - # iterate through all modules and stop at the first working one. - foreach(_pkg_alt ${_pkg_modules_alt}) - if(NOT _pkg_modules_found) - _pkg_check_modules_internal(0 1 "${_prefix}" "${_pkg_alt}") - endif(NOT _pkg_modules_found) - - if (${_prefix}_FOUND) - set(_pkg_modules_found 1) - endif(${_prefix}_FOUND) - endforeach(_pkg_alt) - - if (NOT ${_prefix}_FOUND) - if(${_pkg_is_required}) - message(SEND_ERROR "None of the required '${_pkg_modules_alt}' found") - endif(${_pkg_is_required}) - endif(NOT ${_prefix}_FOUND) - - _pkgconfig_set(__pkg_config_checked_${_prefix} ${PKG_CONFIG_VERSION}) - endif(NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION} OR NOT ${_prefix}_FOUND) -endmacro(pkg_search_module) - -### Local Variables: -### mode: cmake -### End: diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 0241c43c1c..9797ff3ec0 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -1,3 +1,10 @@ +include(CheckFunctionExists) +include(CheckIncludeFile) + +if(UNIX) + find_package(PkgConfig) +endif() + # Search packages for host system instead of packages for target system # in case of cross compilation thess macro should be defined by toolchain file if(NOT COMMAND find_host_package) From 36d7712c2c4906af43be108c8581648ceed1ec04 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sun, 27 Nov 2016 21:15:40 +0300 Subject: [PATCH 127/409] cmake: CMakeVars.txt (for debugging purpose) --- CMakeLists.txt | 3 +++ cmake/OpenCVUtils.cmake | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91a6462012..41f1d25bb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1107,3 +1107,6 @@ endif() # ---------------------------------------------------------------------------- include(cmake/OpenCVPackaging.cmake) + +# This should be the last command +ocv_cmake_dump_vars("" TOFILE "CMakeVars.txt") diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 0241c43c1c..ce2cfaea8a 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -11,6 +11,37 @@ if(NOT COMMAND find_host_program) endmacro() endif() +if(NOT COMMAND cmake_parse_arguments) + include(CMakeParseArguments OPTIONAL) # CMake 2.8.3+ +endif() + +# Debugging function +function(ocv_cmake_dump_vars) + set(VARS "") + get_cmake_property(_variableNames VARIABLES) + if(COMMAND cmake_parse_arguments222) + cmake_parse_arguments(DUMP "" "TOFILE" "" ${ARGN}) + set(regex "${DUMP_UNPARSED_ARGUMENTS}") + else() + set(regex "${ARGV0}") + if(ARGV1 STREQUAL "TOFILE") + set(DUMP_TOFILE "${ARGV2}") + endif() + endif() + string(TOLOWER "${regex}" regex_lower) + foreach(_variableName ${_variableNames}) + string(TOLOWER "${_variableName}" _variableName_lower) + if(_variableName MATCHES "${regex}" OR _variableName_lower MATCHES "${regex_lower}") + set(VARS "${VARS}${_variableName}=${${_variableName}}\n") + endif() + endforeach() + if(DUMP_TOFILE) + file(WRITE ${CMAKE_BINARY_DIR}/${DUMP_TOFILE} "${VARS}") + else() + message(AUTHOR_WARNING "${VARS}") + endif() +endfunction() + # assert macro # Note: it doesn't support lists in arguments # Usage samples: From 5ac15f09ed96c57cfecbfc1b7b5ca3102f5b58a2 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 15 Jan 2016 14:03:48 +0300 Subject: [PATCH 128/409] cmake: add DEBUG_PRE/DEBUG_POST commands handling Useful for debug purposes: cmake -DDEBUG_POST="ocv_cmake_dump_vars(CUDA)" . cmake -DCMAKE_PRE="ocv_cmake_dump_vars(\"OPENCL|opencl\")" . --- CMakeLists.txt | 3 +++ cmake/OpenCVUtils.cmake | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 41f1d25bb4..fe43ce03b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,8 @@ endif() include(cmake/OpenCVUtils.cmake) +ocv_cmake_eval(DEBUG_PRE ONCE) + ocv_clear_vars(OpenCVModules_TARGETS) # ---------------------------------------------------------------------------- @@ -1110,3 +1112,4 @@ include(cmake/OpenCVPackaging.cmake) # This should be the last command ocv_cmake_dump_vars("" TOFILE "CMakeVars.txt") +ocv_cmake_eval(DEBUG_POST ONCE) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index ce2cfaea8a..093e8b7858 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -42,6 +42,16 @@ function(ocv_cmake_dump_vars) endif() endfunction() +function(ocv_cmake_eval var_name) + if(DEFINED ${var_name}) + file(WRITE "${CMAKE_BINARY_DIR}/CMakeCommand-${var_name}.cmake" ${${var_name}}) + include("${CMAKE_BINARY_DIR}/CMakeCommand-${var_name}.cmake") + endif() + if(";${ARGN};" MATCHES ";ONCE;") + unset(${var_name} CACHE) + endif() +endfunction() + # assert macro # Note: it doesn't support lists in arguments # Usage samples: From 32f568308a9022419aa6e1c4cb0c6989b9ab559e Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sun, 27 Nov 2016 23:31:04 +0300 Subject: [PATCH 129/409] cmake: cleanup ffmpeg detection --- 3rdparty/ffmpeg/ffmpeg_version.cmake | 22 ++++---- CMakeLists.txt | 15 +++--- cmake/OpenCVFindLibsVideo.cmake | 72 ++++++++----------------- cmake/OpenCVUtils.cmake | 38 +++++++++++++ cmake/checks/ffmpeg_test.cpp | 24 +++++++++ cmake/templates/cvconfig.h.in | 6 --- modules/highgui/src/cap_ffmpeg_impl.hpp | 3 ++ 7 files changed, 104 insertions(+), 76 deletions(-) create mode 100644 cmake/checks/ffmpeg_test.cpp diff --git a/3rdparty/ffmpeg/ffmpeg_version.cmake b/3rdparty/ffmpeg/ffmpeg_version.cmake index 48fba2b913..56fb31c80c 100644 --- a/3rdparty/ffmpeg/ffmpeg_version.cmake +++ b/3rdparty/ffmpeg/ffmpeg_version.cmake @@ -1,13 +1,11 @@ -set(HAVE_FFMPEG 1) -set(HAVE_FFMPEG_CODEC 1) -set(HAVE_FFMPEG_FORMAT 1) -set(HAVE_FFMPEG_UTIL 1) -set(HAVE_FFMPEG_SWSCALE 1) -set(HAVE_FFMPEG_RESAMPLE 0) -set(HAVE_GENTOO_FFMPEG 1) +set(FFMPEG_libavcodec_FOUND 1) +set(FFMPEG_libavformat_FOUND 1) +set(FFMPEG_libavutil_FOUND 1) +set(FFMPEG_libswscale_FOUND 1) +set(FFMPEG_libavresample_FOUND 1) -set(ALIASOF_libavcodec_VERSION 55.18.102) -set(ALIASOF_libavformat_VERSION 55.12.100) -set(ALIASOF_libavutil_VERSION 52.38.100) -set(ALIASOF_libswscale_VERSION 2.3.100) -set(ALIASOF_libavresample_VERSION 1.0.1) \ No newline at end of file +set(FFMPEG_libavcodec_VERSION 55.18.102) +set(FFMPEG_libavformat_VERSION 55.12.100) +set(FFMPEG_libavutil_VERSION 52.38.100) +set(FFMPEG_libswscale_VERSION 2.3.100) +set(FFMPEG_libavresample_VERSION 1.0.1) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91a6462012..c58e520791 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -882,19 +882,18 @@ if(ANDROID) endif() endif() -if(DEFINED WITH_FFMPEG) +if(DEFINED WITH_FFMPEG OR HAVE_FFMPEG) if(WIN32) status(" FFMPEG:" WITH_FFMPEG THEN "YES (prebuilt binaries)" ELSE NO) else() status(" FFMPEG:" HAVE_FFMPEG THEN YES ELSE NO) endif() - status(" codec:" HAVE_FFMPEG_CODEC THEN "YES (ver ${ALIASOF_libavcodec_VERSION})" ELSE NO) - status(" format:" HAVE_FFMPEG_FORMAT THEN "YES (ver ${ALIASOF_libavformat_VERSION})" ELSE NO) - status(" util:" HAVE_FFMPEG_UTIL THEN "YES (ver ${ALIASOF_libavutil_VERSION})" ELSE NO) - status(" swscale:" HAVE_FFMPEG_SWSCALE THEN "YES (ver ${ALIASOF_libswscale_VERSION})" ELSE NO) - status(" resample:" HAVE_FFMPEG_RESAMPLE THEN "YES (ver ${ALIASOF_libavresample_VERSION})" ELSE NO) - status(" gentoo-style:" HAVE_GENTOO_FFMPEG THEN YES ELSE NO) -endif(DEFINED WITH_FFMPEG) + status(" avcodec:" FFMPEG_libavcodec_FOUND THEN "YES (ver ${FFMPEG_libavcodec_VERSION})" ELSE NO) + status(" avformat:" FFMPEG_libavformat_FOUND THEN "YES (ver ${FFMPEG_libavformat_VERSION})" ELSE NO) + status(" avutil:" FFMPEG_libavutil_FOUND THEN "YES (ver ${FFMPEG_libavutil_VERSION})" ELSE NO) + status(" swscale:" FFMPEG_libswscale_FOUND THEN "YES (ver ${FFMPEG_libswscale_VERSION})" ELSE NO) + status(" avresample:" FFMPEG_libavresample_FOUND THEN "YES (ver ${FFMPEG_libavresample_VERSION})" ELSE NO) +endif() if(DEFINED WITH_GSTREAMER) status(" GStreamer:" HAVE_GSTREAMER THEN "" ELSE NO) diff --git a/cmake/OpenCVFindLibsVideo.cmake b/cmake/OpenCVFindLibsVideo.cmake index 5430759f39..cadc74eb74 100644 --- a/cmake/OpenCVFindLibsVideo.cmake +++ b/cmake/OpenCVFindLibsVideo.cmake @@ -190,63 +190,35 @@ if(WITH_XIMEA) endif(WITH_XIMEA) # --- FFMPEG --- -ocv_clear_vars(HAVE_FFMPEG HAVE_FFMPEG_CODEC HAVE_FFMPEG_FORMAT HAVE_FFMPEG_UTIL HAVE_FFMPEG_SWSCALE HAVE_FFMPEG_RESAMPLE HAVE_GENTOO_FFMPEG HAVE_FFMPEG_FFMPEG) +ocv_clear_vars(HAVE_FFMPEG) if(WITH_FFMPEG) if(WIN32 AND NOT ARM) include("${OpenCV_SOURCE_DIR}/3rdparty/ffmpeg/ffmpeg_version.cmake") - elseif(UNIX) - CHECK_MODULE(libavcodec HAVE_FFMPEG_CODEC) - CHECK_MODULE(libavformat HAVE_FFMPEG_FORMAT) - CHECK_MODULE(libavutil HAVE_FFMPEG_UTIL) - CHECK_MODULE(libswscale HAVE_FFMPEG_SWSCALE) - CHECK_MODULE(libavresample HAVE_FFMPEG_RESAMPLE) - - CHECK_INCLUDE_FILE(libavformat/avformat.h HAVE_GENTOO_FFMPEG) - CHECK_INCLUDE_FILE(ffmpeg/avformat.h HAVE_FFMPEG_FFMPEG) - if(NOT HAVE_GENTOO_FFMPEG AND NOT HAVE_FFMPEG_FFMPEG) - if(EXISTS /usr/include/ffmpeg/libavformat/avformat.h OR HAVE_FFMPEG_SWSCALE) - set(HAVE_GENTOO_FFMPEG TRUE) - endif() + set(HAVE_FFMPEG TRUE) + elseif(PKG_CONFIG_FOUND) + ocv_check_modules(FFMPEG libavcodec libavformat libavutil libswscale) + ocv_check_modules(FFMPEG_libavresample libavresample) + if(FFMPEG_libavresample_FOUND) + ocv_append_build_options(FFMPEG FFMPEG_libavresample) endif() - if(HAVE_FFMPEG_CODEC AND HAVE_FFMPEG_FORMAT AND HAVE_FFMPEG_UTIL AND HAVE_FFMPEG_SWSCALE) - set(HAVE_FFMPEG TRUE) - endif() - if(HAVE_FFMPEG) - # Find the bzip2 library because it is required on some systems - FIND_LIBRARY(BZIP2_LIBRARIES NAMES bz2 bzip2) - if(NOT BZIP2_LIBRARIES) - # Do an other trial - FIND_FILE(BZIP2_LIBRARIES NAMES libbz2.so.1 PATHS /lib) + try_compile(__VALID_FFMPEG + "${OpenCV_BINARY_DIR}" + "${OpenCV_SOURCE_DIR}/cmake/checks/ffmpeg_test.cpp" + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${FFMPEG_INCLUDE_DIRS}" + "-DLINK_DIRECTORIES:STRING=${FFMPEG_LIBRARY_DIRS}" + "-DLINK_LIBRARIES:STRING=${FFMPEG_LIBRARIES}" + OUTPUT_VARIABLE TRY_OUT + ) + if(NOT __VALID_FFMPEG) + #message(FATAL_ERROR "FFMPEG: test check build log:\n${TRY_OUT}") + message(STATUS "WARNING: Can't build ffmpeg test code") + else() + ocv_append_build_options(HIGHGUI FFMPEG) endif() - else() - find_path(FFMPEG_INCLUDE_DIR "libavformat/avformat.h" - PATHS /usr/local /usr /opt - PATH_SUFFIXES include - DOC "The path to FFMPEG headers") - if(FFMPEG_INCLUDE_DIR) - set(HAVE_GENTOO_FFMPEG TRUE) - set(FFMPEG_LIB_DIR "${FFMPEG_INCLUDE_DIR}/../lib" CACHE PATH "Full path of FFMPEG library directory") - find_library(FFMPEG_CODEC_LIB "avcodec" HINTS "${FFMPEG_LIB_DIR}") - find_library(FFMPEG_FORMAT_LIB "avformat" HINTS "${FFMPEG_LIB_DIR}") - find_library(FFMPEG_UTIL_LIB "avutil" HINTS "${FFMPEG_LIB_DIR}") - find_library(FFMPEG_SWSCALE_LIB "swscale" HINTS "${FFMPEG_LIB_DIR}") - if(FFMPEG_CODEC_LIB AND FFMPEG_FORMAT_LIB AND - FFMPEG_UTIL_LIB AND FFMPEG_SWSCALE_LIB) - set(ALIASOF_libavcodec_VERSION "Unknown") - set(ALIASOF_libavformat_VERSION "Unknown") - set(ALIASOF_libavutil_VERSION "Unknown") - set(ALIASOF_libswscale_VERSION "Unknown") - set(HAVE_FFMPEG 1) - endif() - endif(FFMPEG_INCLUDE_DIR) - if(HAVE_FFMPEG) - set(HIGHGUI_LIBRARIES ${HIGHGUI_LIBRARIES} - ${FFMPEG_CODEC_LIB} ${FFMPEG_FORMAT_LIB} - ${FFMPEG_UTIL_LIB} ${FFMPEG_SWSCALE_LIB}) - ocv_include_directories(${FFMPEG_INCLUDE_DIR}) - endif(HAVE_FFMPEG) endif() + else() + message(STATUS "Can't find ffmpeg - 'pkg-config' utility is missing") endif() endif(WITH_FFMPEG) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 0241c43c1c..bf62628df7 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -230,6 +230,44 @@ macro(OCV_OPTION variable description value) unset(__value) endmacro() +# Usage: ocv_append_build_options(HIGHGUI FFMPEG) +macro(ocv_append_build_options var_prefix pkg_prefix) + foreach(suffix INCLUDE_DIRS LIBRARIES LIBRARY_DIRS) + if(${pkg_prefix}_${suffix}) + list(APPEND ${var_prefix}_${suffix} ${${pkg_prefix}_${suffix}}) + list(REMOVE_DUPLICATES ${var_prefix}_${suffix}) + endif() + endforeach() +endmacro() + +# Usage is similar to CMake 'pkg_check_modules' command +# It additionally controls HAVE_${define} and ${define}_${modname}_FOUND variables +macro(ocv_check_modules define) + unset(HAVE_${define}) + foreach(m ${ARGN}) + if (m MATCHES "(.*[^><])(>=|=|<=)(.*)") + set(__modname "${CMAKE_MATCH_1}") + else() + set(__modname "${m}") + endif() + unset(${define}_${__modname}_FOUND) + endforeach() + pkg_check_modules(${define} ${ARGN}) + if(${define}_FOUND) + set(HAVE_${define} 1) + endif() + foreach(m ${ARGN}) + if (m MATCHES "(.*[^><])(>=|=|<=)(.*)") + set(__modname "${CMAKE_MATCH_1}") + else() + set(__modname "${m}") + endif() + if(NOT DEFINED ${define}_${__modname}_FOUND AND ${define}_FOUND) + set(${define}_${__modname}_FOUND 1) + endif() + endforeach() +endmacro() + # Macros that checks if module have been installed. # After it adds module to build and define diff --git a/cmake/checks/ffmpeg_test.cpp b/cmake/checks/ffmpeg_test.cpp new file mode 100644 index 0000000000..7b49c38c7a --- /dev/null +++ b/cmake/checks/ffmpeg_test.cpp @@ -0,0 +1,24 @@ +#include + +extern "C" { +#include +#include +#include +} + +#define CALC_FFMPEG_VERSION(a,b,c) ( a<<16 | b<<8 | c ) + +static void test() +{ + AVFormatContext* c = 0; + AVCodec* avcodec = 0; + AVFrame* frame = 0; + +#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(52, 111, 0) + int err = avformat_open_input(&c, "", NULL, NULL); +#else + int err = av_open_input_file(&c, "", NULL, 0, NULL); +#endif +} + +int main() { test(); return 0; } diff --git a/cmake/templates/cvconfig.h.in b/cmake/templates/cvconfig.h.in index caea7da3e7..c2277babb6 100644 --- a/cmake/templates/cvconfig.h.in +++ b/cmake/templates/cvconfig.h.in @@ -61,12 +61,6 @@ /* FFMpeg video library */ #cmakedefine HAVE_FFMPEG -/* ffmpeg's libswscale */ -#cmakedefine HAVE_FFMPEG_SWSCALE - -/* ffmpeg in Gentoo */ -#cmakedefine HAVE_GENTOO_FFMPEG - /* GStreamer multimedia framework */ #cmakedefine HAVE_GSTREAMER diff --git a/modules/highgui/src/cap_ffmpeg_impl.hpp b/modules/highgui/src/cap_ffmpeg_impl.hpp index a934faaa91..24b51c7d28 100644 --- a/modules/highgui/src/cap_ffmpeg_impl.hpp +++ b/modules/highgui/src/cap_ffmpeg_impl.hpp @@ -40,6 +40,9 @@ // //M*/ +#define HAVE_FFMPEG_SWSCALE +#define HAVE_GENTOO_FFMPEG + #include "cap_ffmpeg_api.hpp" #include #include From e8f591143ca30433981c1c72db0df1acca054444 Mon Sep 17 00:00:00 2001 From: Colin Finck Date: Mon, 28 Nov 2016 10:15:16 +0100 Subject: [PATCH 130/409] objdetect: Properly check for C++11 as it's done in the matching .cpp file. Fixes #7623 --- .../include/opencv2/objdetect/detection_based_tracker.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/objdetect/include/opencv2/objdetect/detection_based_tracker.hpp b/modules/objdetect/include/opencv2/objdetect/detection_based_tracker.hpp index 44213a818a..b93c8f59ea 100644 --- a/modules/objdetect/include/opencv2/objdetect/detection_based_tracker.hpp +++ b/modules/objdetect/include/opencv2/objdetect/detection_based_tracker.hpp @@ -46,7 +46,7 @@ // After this condition removal update blacklist for bindings: modules/python/common.cmake #if defined(__linux__) || defined(LINUX) || defined(__APPLE__) || defined(__ANDROID__) || \ - (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_MSC_VER) && _MSC_VER >= 1700) + (defined(__cplusplus) && __cplusplus > 199711L) || (defined(_MSC_VER) && _MSC_VER >= 1700) #include From ff2747dff5f0e2a22bf60d45f5d7cfd6fa023c59 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Fri, 25 Nov 2016 19:13:27 +0300 Subject: [PATCH 131/409] Added wrappers for OpenVX context attributes and setter for convolution scale attribute --- 3rdparty/openvx/include/ivx.hpp | 148 ++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index b813ac9cf5..f47831d1c8 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -696,6 +696,11 @@ 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; +#endif + /// vx_context wrapper class Context : public RefWrapper { @@ -719,6 +724,146 @@ public: /// vxLoadKernels() wrapper void loadKernels(const std::string& module) { IVX_CHECK_STATUS( vxLoadKernels(ref, module.c_str()) ); } + + /// vxQueryContext() wrapper + template + void query(vx_enum att, T& value) const + { IVX_CHECK_STATUS(vxQueryContext(ref, att, &value, sizeof(value))); } + +#ifndef VX_VERSION_1_1 + static const vx_enum + VX_CONTEXT_VENDOR_ID = VX_CONTEXT_ATTRIBUTE_VENDOR_ID, + VX_CONTEXT_VERSION = VX_CONTEXT_ATTRIBUTE_VERSION, + VX_CONTEXT_UNIQUE_KERNELS = VX_CONTEXT_ATTRIBUTE_UNIQUE_KERNELS, + VX_CONTEXT_MODULES = VX_CONTEXT_ATTRIBUTE_MODULES, + VX_CONTEXT_REFERENCES = VX_CONTEXT_ATTRIBUTE_REFERENCES, + VX_CONTEXT_IMPLEMENTATION = VX_CONTEXT_ATTRIBUTE_IMPLEMENTATION, + VX_CONTEXT_EXTENSIONS_SIZE = VX_CONTEXT_ATTRIBUTE_EXTENSIONS_SIZE, + VX_CONTEXT_EXTENSIONS = VX_CONTEXT_ATTRIBUTE_EXTENSIONS, + VX_CONTEXT_CONVOLUTION_MAX_DIMENSION = VX_CONTEXT_ATTRIBUTE_CONVOLUTION_MAXIMUM_DIMENSION, + VX_CONTEXT_OPTICAL_FLOW_MAX_WINDOW_DIMENSION = VX_CONTEXT_ATTRIBUTE_OPTICAL_FLOW_WINDOW_MAXIMUM_DIMENSION, + VX_CONTEXT_IMMEDIATE_BORDER = VX_CONTEXT_ATTRIBUTE_IMMEDIATE_BORDER_MODE, + VX_CONTEXT_UNIQUE_KERNEL_TABLE = VX_CONTEXT_ATTRIBUTE_UNIQUE_KERNEL_TABLE; +#endif + + /// vxQueryContext(VX_CONTEXT_VENDOR_ID) wrapper + vx_uint16 vendorID() const + { + vx_uint16 v; + query(VX_CONTEXT_VENDOR_ID, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_VERSION) wrapper + vx_uint16 version() const + { + vx_uint16 v; + query(VX_CONTEXT_VERSION, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_UNIQUE_KERNELS) wrapper + vx_uint32 uniqueKernels() const + { + vx_uint32 v; + query(VX_CONTEXT_UNIQUE_KERNELS, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_MODULES) wrapper + vx_uint32 modules() const + { + vx_uint32 v; + query(VX_CONTEXT_MODULES, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_REFERENCES) wrapper + vx_uint32 references() const + { + vx_uint32 v; + query(VX_CONTEXT_REFERENCES, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_EXTENSIONS_SIZE) wrapper + vx_size extensionsSize() const + { + vx_size v; + query(VX_CONTEXT_EXTENSIONS_SIZE, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_CONVOLUTION_MAX_DIMENSION) wrapper + vx_size convolutionMaxDimension() const + { + vx_size v; + query(VX_CONTEXT_CONVOLUTION_MAX_DIMENSION, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_OPTICAL_FLOW_MAX_WINDOW_DIMENSION) wrapper + vx_size opticalFlowMaxWindowSize() const + { + vx_size v; + query(VX_CONTEXT_OPTICAL_FLOW_MAX_WINDOW_DIMENSION, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_IMMEDIATE_BORDER) wrapper + vx_border_t borderMode() const + { + vx_border_t v; + query(VX_CONTEXT_IMMEDIATE_BORDER, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_IMPLEMENTATION) wrapper + std::string implementation() const + { + std::vector v(VX_MAX_IMPLEMENTATION_NAME); + IVX_CHECK_STATUS(vxQueryContext(ref, VX_CONTEXT_IMPLEMENTATION, &v[0], v.size() * sizeof(vx_char))); + return std::string(v.data()); + } + + /// vxQueryContext(VX_CONTEXT_EXTENSIONS) wrapper + std::string extensions() const + { + std::vector v(extensionsSize()); + IVX_CHECK_STATUS(vxQueryContext(ref, VX_CONTEXT_EXTENSIONS, &v[0], v.size() * sizeof(vx_char))); + return std::string(v.data()); + } + + /// vxQueryContext(VX_CONTEXT_UNIQUE_KERNEL_TABLE) wrapper + std::vector kernelTable() const + { + std::vector v(uniqueKernels()); + 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 v; + query(VX_CONTEXT_IMMEDIATE_BORDER_POLICY, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_NONLINEAR_MAX_DIMENSION) wrapper + vx_size nonlinearMaxDimension() const + { + vx_size v; + query(VX_CONTEXT_NONLINEAR_MAX_DIMENSION, v); + return v; + } +#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))); } + }; /// vx_graph wrapper @@ -1752,6 +1897,9 @@ public: return VX_TYPE_INT16; } + void setScale(vx_uint32 newScale) + { IVX_CHECK_STATUS( vxSetConvolutionAttribute(ref, VX_CONVOLUTION_SCALE, &newScale, sizeof(newScale)) ); } + void copyTo(void* data) { if (!data) throw WrapperError(std::string(__func__) + "(): output pointer is 0"); From 2c03c8d71f7f70d38c6b1ad7d925c8c54001cec5 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 28 Nov 2016 19:10:12 +0300 Subject: [PATCH 132/409] Update googletests 1.8.0 (20160822) (#7738) * restore Google Test 1.7.0 (get patch) * ts: update Google Test to 1.8.0 release https://github.com/google/googletest * ts: re-apply OpenCV patch for gtest * ts: fixes for gtest 1.8.0 * ts: workaround MSVS2015 problem in gtest --- modules/ts/include/opencv2/ts/cuda_test.hpp | 15 +- modules/ts/include/opencv2/ts/ts_ext.hpp | 17 +- modules/ts/include/opencv2/ts/ts_gtest.h | 2291 ++++++++++++++----- modules/ts/src/ts_gtest.cpp | 1493 +++++++++--- 4 files changed, 2895 insertions(+), 921 deletions(-) diff --git a/modules/ts/include/opencv2/ts/cuda_test.hpp b/modules/ts/include/opencv2/ts/cuda_test.hpp index b7470a49fb..b459bb358f 100644 --- a/modules/ts/include/opencv2/ts/cuda_test.hpp +++ b/modules/ts/include/opencv2/ts/cuda_test.hpp @@ -180,14 +180,17 @@ namespace cvtest static int AddToRegistry() { \ ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ GetTestCasePatternHolder(\ - #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ - #test_case_name, \ - #test_name, \ - new ::testing::internal::TestMetaFactory< \ - GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + #test_case_name, \ + ::testing::internal::CodeLocation(\ + __FILE__, __LINE__))->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(\ + test_case_name, test_name)>()); \ return 0; \ } \ - static int gtest_registering_dummy_; \ + static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; \ GTEST_DISALLOW_COPY_AND_ASSIGN_(\ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ }; \ diff --git a/modules/ts/include/opencv2/ts/ts_ext.hpp b/modules/ts/include/opencv2/ts/ts_ext.hpp index 7bf5e50553..ed6009ccd8 100644 --- a/modules/ts/include/opencv2/ts/ts_ext.hpp +++ b/modules/ts/include/opencv2/ts/ts_ext.hpp @@ -27,6 +27,7 @@ void checkIppStatus(); ::test_info_ =\ ::testing::internal::MakeAndRegisterTestInfo(\ #test_case_name, #test_name, NULL, NULL, \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), \ (::testing::internal::GetTestTypeId()), \ ::testing::Test::SetUpTestCase, \ ::testing::Test::TearDownTestCase, \ @@ -52,6 +53,7 @@ void checkIppStatus(); ::test_info_ =\ ::testing::internal::MakeAndRegisterTestInfo(\ #test_fixture, #test_name, NULL, NULL, \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), \ (::testing::internal::GetTypeId()), \ test_fixture::SetUpTestCase, \ test_fixture::TearDownTestCase, \ @@ -72,14 +74,17 @@ void checkIppStatus(); static int AddToRegistry() { \ ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ GetTestCasePatternHolder(\ - #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ - #test_case_name, \ - #test_name, \ - new ::testing::internal::TestMetaFactory< \ - GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + #test_case_name, \ + ::testing::internal::CodeLocation(\ + __FILE__, __LINE__))->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(\ + test_case_name, test_name)>()); \ return 0; \ } \ - static int gtest_registering_dummy_; \ + static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; \ GTEST_DISALLOW_COPY_AND_ASSIGN_(\ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ }; \ diff --git a/modules/ts/include/opencv2/ts/ts_gtest.h b/modules/ts/include/opencv2/ts/ts_gtest.h index cec926a08f..d886c97b6b 100644 --- a/modules/ts/include/opencv2/ts/ts_gtest.h +++ b/modules/ts/include/opencv2/ts/ts_gtest.h @@ -126,8 +126,11 @@ // Authors: wan@google.com (Zhanyong Wan) // // Low-level types and utilities for porting Google Test to various -// platforms. They are subject to change without notice. DO NOT USE -// THEM IN USER CODE. +// platforms. All macros ending with _ and symbols defined in an +// internal namespace are subject to change without notice. Code +// outside Google Test MUST NOT USE THEM DIRECTLY. Macros that don't +// end with _ are part of Google Test's public API and can be used by +// code outside Google Test. // // This file is fundamental to Google Test. All other Google Test source // files are expected to #include this. Therefore, it cannot #include @@ -136,9 +139,30 @@ #ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ #define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ -// The user can define the following macros in the build script to -// control Google Test's behavior. If the user doesn't define a macro -// in this list, Google Test will define it. +// Environment-describing macros +// ----------------------------- +// +// Google Test can be used in many different environments. Macros in +// this section tell Google Test what kind of environment it is being +// used in, such that Google Test can provide environment-specific +// features and implementations. +// +// Google Test tries to automatically detect the properties of its +// environment, so users usually don't need to worry about these +// macros. However, the automatic detection is not perfect. +// Sometimes it's necessary for a user to define some of the following +// macros in the build script to override Google Test's decisions. +// +// If the user doesn't define a macro in the list, Google Test will +// provide a default definition. After this header is #included, all +// macros in this list will be defined to either 1 or 0. +// +// Notes to maintainers: +// - Each macro here is a user-tweakable knob; do not grow the list +// lightly. +// - Use #if to key off these macros. Don't use #ifdef or "#if +// defined(...)", which will not work as these macros are ALWAYS +// defined. // // GTEST_HAS_CLONE - Define it to 1/0 to indicate that clone(2) // is/isn't available. @@ -182,18 +206,23 @@ // - Define to 1 when compiling Google Test itself // as a shared library. -// This header defines the following utilities: +// Platform-indicating macros +// -------------------------- +// +// Macros indicating the platform on which Google Test is being used +// (a macro is defined to 1 if compiled on the given platform; +// otherwise UNDEFINED -- it's never defined to 0.). Google Test +// defines these macros automatically. Code outside Google Test MUST +// NOT define them. // -// Macros indicating the current platform (defined to 1 if compiled on -// the given platform; otherwise undefined): // GTEST_OS_AIX - IBM AIX // GTEST_OS_CYGWIN - Cygwin +// GTEST_OS_FREEBSD - FreeBSD // GTEST_OS_HPUX - HP-UX // GTEST_OS_LINUX - Linux // GTEST_OS_LINUX_ANDROID - Google Android // GTEST_OS_MAC - Mac OS X // GTEST_OS_IOS - iOS -// GTEST_OS_IOS_SIMULATOR - iOS simulator // GTEST_OS_NACL - Google Native Client (NaCl) // GTEST_OS_OPENBSD - OpenBSD // GTEST_OS_QNX - QNX @@ -203,6 +232,8 @@ // GTEST_OS_WINDOWS_DESKTOP - Windows Desktop // GTEST_OS_WINDOWS_MINGW - MinGW // GTEST_OS_WINDOWS_MOBILE - Windows Mobile +// GTEST_OS_WINDOWS_PHONE - Windows Phone +// GTEST_OS_WINDOWS_RT - Windows Store App/WinRT // GTEST_OS_ZOS - z/OS // // Among the platforms, Cygwin, Linux, Max OS X, and Windows have the @@ -212,22 +243,50 @@ // googletestframework@googlegroups.com (patches for fixing them are // even more welcome!). // -// Note that it is possible that none of the GTEST_OS_* macros are defined. +// It is possible that none of the GTEST_OS_* macros are defined. + +// Feature-indicating macros +// ------------------------- +// +// Macros indicating which Google Test features are available (a macro +// is defined to 1 if the corresponding feature is supported; +// otherwise UNDEFINED -- it's never defined to 0.). Google Test +// defines these macros automatically. Code outside Google Test MUST +// NOT define them. +// +// These macros are public so that portable tests can be written. +// Such tests typically surround code using a feature with an #if +// which controls that code. For example: +// +// #if GTEST_HAS_DEATH_TEST +// EXPECT_DEATH(DoSomethingDeadly()); +// #endif // -// Macros indicating available Google Test features (defined to 1 if -// the corresponding feature is supported; otherwise undefined): // GTEST_HAS_COMBINE - the Combine() function (for value-parameterized // tests) // GTEST_HAS_DEATH_TEST - death tests // GTEST_HAS_PARAM_TEST - value-parameterized tests // GTEST_HAS_TYPED_TEST - typed tests // GTEST_HAS_TYPED_TEST_P - type-parameterized tests +// GTEST_IS_THREADSAFE - Google Test is thread-safe. // GTEST_USES_POSIX_RE - enhanced POSIX regex is used. Do not confuse with // GTEST_HAS_POSIX_RE (see above) which users can // define themselves. // GTEST_USES_SIMPLE_RE - our own simple regex is used; // the above two are mutually exclusive. // GTEST_CAN_COMPARE_NULL - accepts untyped NULL in EXPECT_EQ(). + +// Misc public macros +// ------------------ +// +// GTEST_FLAG(flag_name) - references the variable corresponding to +// the given Google Test flag. + +// Internal utilities +// ------------------ +// +// The following macros and utilities are for Google Test's INTERNAL +// use only. Code outside Google Test MUST NOT USE THEM DIRECTLY. // // Macros for basic C++ coding: // GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning. @@ -236,13 +295,18 @@ // GTEST_DISALLOW_ASSIGN_ - disables operator=. // GTEST_DISALLOW_COPY_AND_ASSIGN_ - disables copy ctor and operator=. // GTEST_MUST_USE_RESULT_ - declares that a function's result must be used. +// GTEST_INTENTIONAL_CONST_COND_PUSH_ - start code section where MSVC C4127 is +// suppressed (constant conditional). +// GTEST_INTENTIONAL_CONST_COND_POP_ - finish code section where MSVC C4127 +// is suppressed. +// +// C++11 feature wrappers: +// +// testing::internal::move - portability wrapper for std::move. // // Synchronization: // Mutex, MutexLock, ThreadLocal, GetThreadCount() -// - synchronization primitives. -// GTEST_IS_THREADSAFE - defined to 1 to indicate that the above -// synchronization primitives have real implementations -// and Google Test is thread-safe; or 0 otherwise. +// - synchronization primitives. // // Template meta programming: // is_pointer - as in TR1; needed on Symbian and IBM XL C/C++ only. @@ -278,7 +342,6 @@ // BiggestInt - the biggest signed integer type. // // Command-line utilities: -// GTEST_FLAG() - references a flag. // GTEST_DECLARE_*() - declares a flag. // GTEST_DEFINE_*() - defines a flag. // GetInjectableArgvs() - returns the command line as a vector of strings. @@ -304,23 +367,49 @@ # include #endif +#include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT +#include +#include // NOLINT -#define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com" -#define GTEST_FLAG_PREFIX_ "gtest_" -#define GTEST_FLAG_PREFIX_DASH_ "gtest-" -#define GTEST_FLAG_PREFIX_UPPER_ "GTEST_" -#define GTEST_NAME_ "Google Test" -#define GTEST_PROJECT_URL_ "http://code.google.com/p/googletest/" +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the GTEST_OS_* macro. +// It is separate from gtest-port.h so that custom/gtest-port.h can include it. -// Determines the version of gcc that is used to compile this. -#ifdef __GNUC__ -// 40302 means version 4.3.2. -# define GTEST_GCC_VER_ \ - (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) -#endif // __GNUC__ +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ // Determines the platform on which Google Test is compiled. #define GTEST_OS_CYGWIN 0 @@ -329,6 +418,8 @@ #define GTEST_OS_WINDOWS_MOBILE 0 #define GTEST_OS_WINDOWS_MINGW 0 #define GTEST_OS_WINDOWS_DESKTOP 0 +#define GTEST_OS_WINDOWS_PHONE 0 +#define GTEST_OS_WINDOWS_RT 0 #define GTEST_OS_MAC 0 #define GTEST_OS_LINUX 0 #define GTEST_OS_LINUX_ANDROID 0 @@ -341,6 +432,7 @@ #define GTEST_OS_QNX 0 #define GTEST_OS_IOS 0 #define GTEST_OS_IOS_SIMULATOR 0 +#define GTEST_OS_FREEBSD 0 #ifdef __CYGWIN__ # undef GTEST_OS_CYGWIN @@ -357,6 +449,23 @@ # elif defined(__MINGW__) || defined(__MINGW32__) # undef GTEST_OS_WINDOWS_MINGW # define GTEST_OS_WINDOWS_MINGW 1 +# elif defined(WINAPI_FAMILY) +# include +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# undef GTEST_OS_WINDOWS_DESKTOP +# define GTEST_OS_WINDOWS_DESKTOP 1 +# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) +# undef GTEST_OS_WINDOWS_PHONE +# define GTEST_OS_WINDOWS_PHONE 1 +# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) +# undef GTEST_OS_WINDOWS_RT +# define GTEST_OS_WINDOWS_RT 1 +# else + // WINAPI_FAMILY defined but no known partition matched. + // Default to desktop. +# undef GTEST_OS_WINDOWS_DESKTOP +# define GTEST_OS_WINDOWS_DESKTOP 1 +# endif # else # undef GTEST_OS_WINDOWS_DESKTOP # define GTEST_OS_WINDOWS_DESKTOP 1 @@ -372,6 +481,9 @@ # define GTEST_OS_IOS_SIMULATOR 1 # endif # endif +#elif defined __FreeBSD__ +# undef GTEST_OS_FREEBSD +# define GTEST_OS_FREEBSD 1 #elif defined __linux__ # undef GTEST_OS_LINUX # define GTEST_OS_LINUX 1 @@ -402,6 +514,121 @@ # define GTEST_OS_QNX 1 #endif // __CYGWIN__ +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Injection point for custom user configurations. +// The following macros can be defined: +// +// Flag related macros: +// GTEST_FLAG(flag_name) +// GTEST_USE_OWN_FLAGFILE_FLAG_ - Define to 0 when the system provides its +// own flagfile flag parsing. +// GTEST_DECLARE_bool_(name) +// GTEST_DECLARE_int32_(name) +// GTEST_DECLARE_string_(name) +// GTEST_DEFINE_bool_(name, default_val, doc) +// GTEST_DEFINE_int32_(name, default_val, doc) +// GTEST_DEFINE_string_(name, default_val, doc) +// +// Test filtering: +// GTEST_TEST_FILTER_ENV_VAR_ - The name of an environment variable that +// will be used if --GTEST_FLAG(test_filter) +// is not provided. +// +// Logging: +// GTEST_LOG_(severity) +// GTEST_CHECK_(condition) +// Functions LogToStderr() and FlushInfoLog() have to be provided too. +// +// Threading: +// GTEST_HAS_NOTIFICATION_ - Enabled if Notification is already provided. +// GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ - Enabled if Mutex and ThreadLocal are +// already provided. +// Must also provide GTEST_DECLARE_STATIC_MUTEX_(mutex) and +// GTEST_DEFINE_STATIC_MUTEX_(mutex) +// +// GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks) +// GTEST_LOCK_EXCLUDED_(locks) +// +// ** Custom implementation starts here ** + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ + +#ifndef GTEST_HAS_NOTIFICATION_ +#define GTEST_HAS_NOTIFICATION_ 0 +#endif +#ifndef GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ +#define GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ 0 +#endif + +#if !defined(GTEST_DEV_EMAIL_) +# define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com" +# define GTEST_FLAG_PREFIX_ "gtest_" +# define GTEST_FLAG_PREFIX_DASH_ "gtest-" +# define GTEST_FLAG_PREFIX_UPPER_ "GTEST_" +# define GTEST_NAME_ "Google Test" +# define GTEST_PROJECT_URL_ "https://github.com/google/googletest/" +#endif // !defined(GTEST_DEV_EMAIL_) + +#if !defined(GTEST_INIT_GOOGLE_TEST_NAME_) +# define GTEST_INIT_GOOGLE_TEST_NAME_ "testing::InitGoogleTest" +#endif // !defined(GTEST_INIT_GOOGLE_TEST_NAME_) + +// Determines the version of gcc that is used to compile this. +#ifdef __GNUC__ +// 40302 means version 4.3.2. +# define GTEST_GCC_VER_ \ + (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + +// Macros for disabling Microsoft Visual C++ warnings. +// +// GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 4385) +// /* code that triggers warnings C4800 and C4385 */ +// GTEST_DISABLE_MSC_WARNINGS_POP_() +#if defined(_MSC_VER) && _MSC_VER >= 1500 +# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) \ + __pragma(warning(push)) \ + __pragma(warning(disable: warnings)) +# define GTEST_DISABLE_MSC_WARNINGS_POP_() \ + __pragma(warning(pop)) +#else +// Older versions of MSVC don't have __pragma. +# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) +# define GTEST_DISABLE_MSC_WARNINGS_POP_() +#endif + #ifndef GTEST_LANG_CXX11 // gcc and clang define __GXX_EXPERIMENTAL_CXX0X__ when // -std={c,gnu}++{0x,11} is passed. The C++11 standard specifies a @@ -415,19 +642,95 @@ # endif #endif +// Distinct from C++11 language support, some environments don't provide +// proper C++11 library support. Notably, it's possible to build in +// C++11 mode when targeting Mac OS X 10.6, which has an old libstdc++ +// with no C++11 support. +// +// libstdc++ has sufficient C++11 support as of GCC 4.6.0, __GLIBCXX__ +// 20110325, but maintenance releases in the 4.4 and 4.5 series followed +// this date, so check for those versions by their date stamps. +// https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html#abi.versioning +#if GTEST_LANG_CXX11 && \ + (!defined(__GLIBCXX__) || ( \ + __GLIBCXX__ >= 20110325ul && /* GCC >= 4.6.0 */ \ + /* Blacklist of patch releases of older branches: */ \ + __GLIBCXX__ != 20110416ul && /* GCC 4.4.6 */ \ + __GLIBCXX__ != 20120313ul && /* GCC 4.4.7 */ \ + __GLIBCXX__ != 20110428ul && /* GCC 4.5.3 */ \ + __GLIBCXX__ != 20120702ul)) /* GCC 4.5.4 */ +# define GTEST_STDLIB_CXX11 1 +#else +# define GTEST_STDLIB_CXX11 0 +#endif + +// Only use C++11 library features if the library provides them. +#if GTEST_STDLIB_CXX11 +# define GTEST_HAS_STD_BEGIN_AND_END_ 1 +# define GTEST_HAS_STD_FORWARD_LIST_ 1 +# define GTEST_HAS_STD_FUNCTION_ 1 +# define GTEST_HAS_STD_INITIALIZER_LIST_ 1 +# define GTEST_HAS_STD_MOVE_ 1 +# define GTEST_HAS_STD_SHARED_PTR_ 1 +# define GTEST_HAS_STD_TYPE_TRAITS_ 1 +# define GTEST_HAS_STD_UNIQUE_PTR_ 1 +#else +# define GTEST_HAS_STD_BEGIN_AND_END_ 0 +# define GTEST_HAS_STD_FORWARD_LIST_ 0 +# define GTEST_HAS_STD_FUNCTION_ 0 +# define GTEST_HAS_STD_INITIALIZER_LIST_ 0 +# define GTEST_HAS_STD_MOVE_ 0 +# define GTEST_HAS_STD_SHARED_PTR_ 0 +# define GTEST_HAS_STD_TYPE_TRAITS_ 0 +# define GTEST_HAS_STD_UNIQUE_PTR_ 0 +#endif + +// C++11 specifies that provides std::tuple. +// Some platforms still might not have it, however. +#if GTEST_LANG_CXX11 +# define GTEST_HAS_STD_TUPLE_ 1 +# if defined(__clang__) +// Inspired by http://clang.llvm.org/docs/LanguageExtensions.html#__has_include +# if defined(__has_include) && !__has_include() +# undef GTEST_HAS_STD_TUPLE_ +# endif +# elif defined(_MSC_VER) +// Inspired by boost/config/stdlib/dinkumware.hpp +# if defined(_CPPLIB_VER) && _CPPLIB_VER < 520 +# undef GTEST_HAS_STD_TUPLE_ +# endif +# elif defined(__GLIBCXX__) +// Inspired by boost/config/stdlib/libstdcpp3.hpp, +// http://gcc.gnu.org/gcc-4.2/changes.html and +// http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt01ch01.html#manual.intro.status.standard.200x +# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 2) +# undef GTEST_HAS_STD_TUPLE_ +# endif +# endif +#else +# define GTEST_HAS_STD_TUPLE_ 0 +#endif + // Brings in definitions for functions used in the testing::internal::posix // namespace (read, write, close, chdir, isatty, stat). We do not currently // use them on Windows Mobile. -#if !GTEST_OS_WINDOWS +#if GTEST_OS_WINDOWS +# if !GTEST_OS_WINDOWS_MOBILE +# include +# include +# endif +// In order to avoid having to include , use forward declaration +// assuming CRITICAL_SECTION is a typedef of _RTL_CRITICAL_SECTION. +// This assumption is verified by +// WindowsTypesTest.CRITICAL_SECTIONIs_RTL_CRITICAL_SECTION. +struct _RTL_CRITICAL_SECTION; +#else // This assumes that non-Windows OSes provide unistd.h. For OSes where this // is not the case, we need to include headers that provide the functions // mentioned above. # include # include -#elif !GTEST_OS_WINDOWS_MOBILE -# include -# include -#endif +#endif // GTEST_OS_WINDOWS #if GTEST_OS_LINUX_ANDROID // Used to define __ANDROID_API__ matching the target NDK API level. @@ -444,7 +747,10 @@ #endif #endif -#if GTEST_HAS_POSIX_RE +#if defined(GTEST_USES_PCRE) && GTEST_USES_PCRE +// The appropriate headers have already been included. + +#elif GTEST_HAS_POSIX_RE // On some platforms, needs someone to define size_t, and // won't compile otherwise. We can #include it here as we already @@ -469,7 +775,7 @@ # define GTEST_USES_SIMPLE_RE 1 # define GTEST_USES_POSIX_RE 0 -#endif // GTEST_HAS_POSIX_RE +#endif // GTEST_USES_PCRE #ifndef GTEST_HAS_EXCEPTIONS // The user didn't tell us whether exceptions are enabled, so we need @@ -482,6 +788,15 @@ # define _HAS_EXCEPTIONS 1 # endif // _HAS_EXCEPTIONS # define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS +# elif defined(__clang__) +// clang defines __EXCEPTIONS iff exceptions are enabled before clang 220714, +// but iff cleanups are enabled after that. In Obj-C++ files, there can be +// cleanups for ObjC exceptions which also need cleanups, even if C++ exceptions +// are disabled. clang has __has_feature(cxx_exceptions) which checks for C++ +// exceptions starting at clang r206352, but which checked for cleanups prior to +// that. To reliably check for C++ exception availability with clang, check for +// __EXCEPTIONS && __has_feature(cxx_exceptions). +# define GTEST_HAS_EXCEPTIONS (__EXCEPTIONS && __has_feature(cxx_exceptions)) # elif defined(__GNUC__) && __EXCEPTIONS // gcc defines __EXCEPTIONS to 1 iff exceptions are enabled. # define GTEST_HAS_EXCEPTIONS 1 @@ -607,13 +922,13 @@ // Determines whether Google Test can use the pthreads library. #ifndef GTEST_HAS_PTHREAD -// The user didn't tell us explicitly, so we assume pthreads support is -// available on Linux and Mac. +// The user didn't tell us explicitly, so we make reasonable assumptions about +// which platforms have pthreads support. // // To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0 // to your compiler flags. # define GTEST_HAS_PTHREAD (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_HPUX \ - || GTEST_OS_QNX) + || GTEST_OS_QNX || GTEST_OS_FREEBSD || GTEST_OS_NACL) #endif // GTEST_HAS_PTHREAD #if GTEST_HAS_PTHREAD @@ -625,6 +940,15 @@ # include // NOLINT #endif +// Determines if hash_map/hash_set are available. +// Only used for testing against those containers. +#if !defined(GTEST_HAS_HASH_MAP_) +# ifdef _MSC_VER +# define GTEST_HAS_HASH_MAP_ 1 // Indicates that hash_map is available. +# define GTEST_HAS_HASH_SET_ 1 // Indicates that hash_set is available. +# endif // _MSC_VER +#endif // !defined(GTEST_HAS_HASH_MAP_) + // Determines whether Google Test can use tr1/tuple. You can define // this macro to 0 to prevent Google Test from using tuple (any // feature depending on tuple with be disabled in this mode). @@ -682,8 +1006,18 @@ // To avoid conditional compilation everywhere, we make it // gtest-port.h's responsibility to #include the header implementing -// tr1/tuple. +// tuple. +#if GTEST_HAS_STD_TUPLE_ +# include // IWYU pragma: export +# define GTEST_TUPLE_NAMESPACE_ ::std +#endif // GTEST_HAS_STD_TUPLE_ + +// We include tr1::tuple even if std::tuple is available to define printers for +// them. #if GTEST_HAS_TR1_TUPLE +# ifndef GTEST_TUPLE_NAMESPACE_ +# define GTEST_TUPLE_NAMESPACE_ ::std::tr1 +# endif // GTEST_TUPLE_NAMESPACE_ # if GTEST_USE_OWN_TR1_TUPLE // This file was GENERATED by command: @@ -741,6 +1075,14 @@ private: #endif +// Visual Studio 2010, 2012, and 2013 define symbols in std::tr1 that conflict +// with our own definitions. Therefore using our own tuple does not work on +// those compilers. +#if defined(_MSC_VER) && _MSC_VER >= 1600 /* 1600 is Visual Studio 2010 */ +# error "gtest's tuple doesn't compile on Visual Studio 2010 or later. \ +GTEST_USE_OWN_TR1_TUPLE must be set to 0 on those compilers." +#endif + // GTEST_n_TUPLE_(T) is the type of an n-tuple. #define GTEST_0_TUPLE_(T) tuple<> #define GTEST_1_TUPLE_(T) tuple, which defines // BOOST_HAS_TR1_TUPLE, from being #included by Boost's . # define BOOST_TR1_DETAIL_CONFIG_HPP_INCLUDED -# include +# include // IWYU pragma: export // NOLINT # elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40000) // GCC 4.0+ implements tr1/tuple in the header. This does @@ -1751,7 +2093,7 @@ using ::std::tuple_size; # else // If the compiler is not GCC 4.0+, we assume the user is using a // spec-conforming TR1 implementation. -# include // NOLINT +# include // IWYU pragma: export // NOLINT # endif // GTEST_USE_OWN_TR1_TUPLE #endif // GTEST_HAS_TR1_TUPLE @@ -1785,7 +2127,8 @@ using ::std::tuple_size; #ifndef GTEST_HAS_STREAM_REDIRECTION // By default, we assume that stream redirection is supported on all // platforms except known mobile ones. -# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN +# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || \ + GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT # define GTEST_HAS_STREAM_REDIRECTION 0 # else # define GTEST_HAS_STREAM_REDIRECTION 1 @@ -1797,10 +2140,10 @@ using ::std::tuple_size; // abort() in a VC 7.1 application compiled as GUI in debug config // pops up a dialog window that cannot be suppressed programmatically. #if (GTEST_OS_LINUX || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ - (GTEST_OS_MAC && !GTEST_OS_IOS) || GTEST_OS_IOS_SIMULATOR || \ + (GTEST_OS_MAC && !GTEST_OS_IOS) || \ (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER >= 1400) || \ GTEST_OS_WINDOWS_MINGW || GTEST_OS_AIX || GTEST_OS_HPUX || \ - GTEST_OS_OPENBSD || GTEST_OS_QNX) + GTEST_OS_OPENBSD || GTEST_OS_QNX || GTEST_OS_FREEBSD) # define GTEST_HAS_DEATH_TEST 1 # include // NOLINT #else @@ -1870,7 +2213,12 @@ using ::std::tuple_size; // compiler the variable/parameter does not have to be used. #if defined(__GNUC__) && !defined(COMPILER_ICC) # define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) -#else +#elif defined(__clang__) +# if __has_attribute(unused) +# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) +# endif +#endif +#ifndef GTEST_ATTRIBUTE_UNUSED_ # define GTEST_ATTRIBUTE_UNUSED_ #endif @@ -1896,6 +2244,19 @@ using ::std::tuple_size; # define GTEST_MUST_USE_RESULT_ #endif // __GNUC__ && (GTEST_GCC_VER_ >= 30400) && !COMPILER_ICC +// MS C++ compiler emits warning when a conditional expression is compile time +// constant. In some contexts this warning is false positive and needs to be +// suppressed. Use the following two macros in such cases: +// +// GTEST_INTENTIONAL_CONST_COND_PUSH_() +// while (true) { +// GTEST_INTENTIONAL_CONST_COND_POP_() +// } +# define GTEST_INTENTIONAL_CONST_COND_PUSH_() \ + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127) +# define GTEST_INTENTIONAL_CONST_COND_POP_() \ + GTEST_DISABLE_MSC_WARNINGS_POP_() + // Determine whether the compiler supports Microsoft's Structured Exception // Handling. This is supported by several Windows compilers but generally // does not exist on any other system. @@ -1910,17 +2271,22 @@ using ::std::tuple_size; # define GTEST_HAS_SEH 0 # endif +#define GTEST_IS_THREADSAFE \ + (GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ \ + || (GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT) \ + || GTEST_HAS_PTHREAD) + #endif // GTEST_HAS_SEH #ifdef _MSC_VER - # if GTEST_LINKED_AS_SHARED_LIBRARY # define GTEST_API_ __declspec(dllimport) # elif GTEST_CREATE_SHARED_LIBRARY # define GTEST_API_ __declspec(dllexport) # endif - -#endif // _MSC_VER +#elif __GNUC__ >= 4 || defined(__clang__) +# define GTEST_API_ __attribute__((visibility ("default"))) +#endif // _MSC_VER #ifndef GTEST_API_ # define GTEST_API_ CV_EXPORTS @@ -1940,10 +2306,58 @@ using ::std::tuple_size; # define GTEST_HAS_CXXABI_H_ 0 #endif +// A function level attribute to disable checking for use of uninitialized +// memory when built with MemorySanitizer. +#if defined(__clang__) +# if __has_feature(memory_sanitizer) +# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ \ + __attribute__((no_sanitize_memory)) +# else +# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +# endif // __has_feature(memory_sanitizer) +#else +# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +#endif // __clang__ + +// A function level attribute to disable AddressSanitizer instrumentation. +#if defined(__clang__) +# if __has_feature(address_sanitizer) +# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ \ + __attribute__((no_sanitize_address)) +# else +# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +# endif // __has_feature(address_sanitizer) +#else +# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +#endif // __clang__ + +// A function level attribute to disable ThreadSanitizer instrumentation. +#if defined(__clang__) +# if __has_feature(thread_sanitizer) +# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ \ + __attribute__((no_sanitize_thread)) +# else +# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ +# endif // __has_feature(thread_sanitizer) +#else +# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ +#endif // __clang__ + namespace testing { class Message; +#if defined(GTEST_TUPLE_NAMESPACE_) +// Import tuple and friends into the ::testing namespace. +// It is part of our interface, having them in ::testing allows us to change +// their types as needed. +using GTEST_TUPLE_NAMESPACE_::get; +using GTEST_TUPLE_NAMESPACE_::make_tuple; +using GTEST_TUPLE_NAMESPACE_::tuple; +using GTEST_TUPLE_NAMESPACE_::tuple_size; +using GTEST_TUPLE_NAMESPACE_::tuple_element; +#endif // defined(GTEST_TUPLE_NAMESPACE_) + namespace internal { // A secret type that Google Test users don't know about. It has no @@ -1955,8 +2369,8 @@ class Secret; // expression is true. For example, you could use it to verify the // size of a static array: // -// GTEST_COMPILE_ASSERT_(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, -// content_type_names_incorrect_size); +// GTEST_COMPILE_ASSERT_(GTEST_ARRAY_SIZE_(names) == NUM_NAMES, +// names_incorrect_size); // // or to make sure a struct is smaller than a certain size: // @@ -1966,16 +2380,22 @@ class Secret; // the expression is false, most compilers will issue a warning/error // containing the name of the variable. +#if GTEST_LANG_CXX11 +# define GTEST_COMPILE_ASSERT_(expr, msg) static_assert(expr, #msg) +#else // !GTEST_LANG_CXX11 template -struct CompileAssert { + struct CompileAssert { }; -#define GTEST_COMPILE_ASSERT_(expr, msg) \ +# define GTEST_COMPILE_ASSERT_(expr, msg) \ typedef ::testing::internal::CompileAssert<(static_cast(expr))> \ msg[static_cast(expr) ? 1 : -1] GTEST_ATTRIBUTE_UNUSED_ +#endif // !GTEST_LANG_CXX11 // Implementation details of GTEST_COMPILE_ASSERT_: // +// (In C++11, we simply use static_assert instead of the following) +// // - GTEST_COMPILE_ASSERT_ works by defining an array type that has -1 // elements (and thus is invalid) when the expression is false. // @@ -2022,7 +2442,12 @@ template struct StaticAssertTypeEqHelper; template -struct StaticAssertTypeEqHelper {}; +struct StaticAssertTypeEqHelper { + enum { value = true }; +}; + +// Evaluates to the number of elements in 'array'. +#define GTEST_ARRAY_SIZE_(array) (sizeof(array) / sizeof(array[0])) #if GTEST_HAS_GLOBAL_STRING typedef ::string string; @@ -2040,6 +2465,9 @@ typedef ::std::wstring wstring; // returns 'condition'. GTEST_API_ bool IsTrue(bool condition); +template class scoped_ptr; +template static void swap(scoped_ptr& a, scoped_ptr& b); + // Defines scoped_ptr. // This implementation of scoped_ptr is PARTIAL - it only contains @@ -2071,12 +2499,20 @@ class scoped_ptr { } } + friend void swap(scoped_ptr& a, scoped_ptr& b); + private: T* ptr_; GTEST_DISALLOW_COPY_AND_ASSIGN_(scoped_ptr); }; +template +static void swap(scoped_ptr& a, scoped_ptr& b) { + using std::swap; + swap(a.ptr_, b.ptr_); +} + // Defines RE. // A simple C++ wrapper for . It uses the POSIX Extended @@ -2194,13 +2630,18 @@ class GTEST_API_ GTestLog { GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestLog); }; -#define GTEST_LOG_(severity) \ +#if !defined(GTEST_LOG_) + +# define GTEST_LOG_(severity) \ ::testing::internal::GTestLog(::testing::internal::GTEST_##severity, \ __FILE__, __LINE__).GetStream() inline void LogToStderr() {} inline void FlushInfoLog() { fflush(NULL); } +#endif // !defined(GTEST_LOG_) + +#if !defined(GTEST_CHECK_) // INTERNAL IMPLEMENTATION - DO NOT USE. // // GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition @@ -2215,12 +2656,13 @@ inline void FlushInfoLog() { fflush(NULL); } // condition itself, plus additional message streamed into it, if any, // and then it aborts the program. It aborts the program irrespective of // whether it is built in the debug mode or not. -#define GTEST_CHECK_(condition) \ +# define GTEST_CHECK_(condition) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ if (::testing::internal::IsTrue(condition)) \ ; \ else \ GTEST_LOG_(FATAL) << "Condition " #condition " failed. " +#endif // !defined(GTEST_CHECK_) // An all-mode assert to verify that the given POSIX-style function // call returns 0 (indicating success). Known limitation: this @@ -2232,6 +2674,15 @@ inline void FlushInfoLog() { fflush(NULL); } GTEST_LOG_(FATAL) << #posix_call << "failed with error " \ << gtest_error +#if GTEST_HAS_STD_MOVE_ +using std::move; +#else // GTEST_HAS_STD_MOVE_ +template +const T& move(const T& t) { + return t; +} +#endif // GTEST_HAS_STD_MOVE_ + // INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. // // Use ImplicitCast_ as a safe version of static_cast for upcasting in @@ -2282,7 +2733,9 @@ inline To DownCast_(From* f) { // so we only accept pointers // for compile-time type checking, and has no overhead in an // optimized build at run-time, as it will be optimized away // completely. + GTEST_INTENTIONAL_CONST_COND_PUSH_() if (false) { + GTEST_INTENTIONAL_CONST_COND_POP_() const To to = NULL; ::testing::internal::ImplicitCast_(to); } @@ -2303,6 +2756,11 @@ template Derived* CheckedDowncastToActualType(Base* base) { #if GTEST_HAS_RTTI GTEST_CHECK_(typeid(*base) == typeid(Derived)); +#endif + +#if defined(GTEST_HAS_DOWNCAST_) && GTEST_HAS_DOWNCAST_ + return ::down_cast(base); +#elif GTEST_HAS_RTTI return dynamic_cast(base); // NOLINT #else return static_cast(base); // Poor man's downcast. @@ -2324,6 +2782,17 @@ GTEST_API_ std::string GetCapturedStderr(); #endif // GTEST_HAS_STREAM_REDIRECTION +// Returns a path to temporary directory. +GTEST_API_ std::string TempDir(); + +// Returns the size (in bytes) of a file. +GTEST_API_ size_t GetFileSize(FILE* file); + +// Reads the entire content of a file as a string. +GTEST_API_ std::string ReadEntireFile(FILE* file); + +// All command line arguments. +GTEST_API_ const ::std::vector& GetArgvs(); #if GTEST_HAS_DEATH_TEST @@ -2331,18 +2800,15 @@ const ::std::vector& GetInjectableArgvs(); void SetInjectableArgvs(const ::std::vector* new_argvs); -// A copy of all command line arguments. Set by InitGoogleTest(). -extern ::std::vector g_argvs; #endif // GTEST_HAS_DEATH_TEST // Defines synchronization primitives. - -#if GTEST_HAS_PTHREAD - -// Sleeps for (roughly) n milli-seconds. This function is only for -// testing Google Test's own constructs. Don't use it in user tests, -// either directly or indirectly. +#if GTEST_IS_THREADSAFE +# if GTEST_HAS_PTHREAD +// Sleeps for (roughly) n milliseconds. This function is only for testing +// Google Test's own constructs. Don't use it in user tests, either +// directly or indirectly. inline void SleepMilliseconds(int n) { const timespec time = { 0, // 0 seconds. @@ -2350,7 +2816,13 @@ inline void SleepMilliseconds(int n) { }; nanosleep(&time, NULL); } +# endif // GTEST_HAS_PTHREAD +# if GTEST_HAS_NOTIFICATION_ +// Notification has already been imported into the namespace. +// Nothing to do here. + +# elif GTEST_HAS_PTHREAD // Allows a controller thread to pause execution of newly created // threads until notified. Instances of this class must be created // and destroyed in the controller thread. @@ -2394,6 +2866,62 @@ class Notification { GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification); }; +# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT + +GTEST_API_ void SleepMilliseconds(int n); + +// Provides leak-safe Windows kernel handle ownership. +// Used in death tests and in threading support. +class GTEST_API_ AutoHandle { + public: + // Assume that Win32 HANDLE type is equivalent to void*. Doing so allows us to + // avoid including in this header file. Including is + // undesirable because it defines a lot of symbols and macros that tend to + // conflict with client code. This assumption is verified by + // WindowsTypesTest.HANDLEIsVoidStar. + typedef void* Handle; + AutoHandle(); + explicit AutoHandle(Handle handle); + + ~AutoHandle(); + + Handle Get() const; + void Reset(); + void Reset(Handle handle); + + private: + // Returns true iff the handle is a valid handle object that can be closed. + bool IsCloseable() const; + + Handle handle_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle); +}; + +// Allows a controller thread to pause execution of newly created +// threads until notified. Instances of this class must be created +// and destroyed in the controller thread. +// +// This class is only for testing Google Test's own constructs. Do not +// use it in user tests, either directly or indirectly. +class GTEST_API_ Notification { + public: + Notification(); + void Notify(); + void WaitForNotification(); + + private: + AutoHandle event_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification); +}; +# endif // GTEST_HAS_NOTIFICATION_ + +// On MinGW, we can have both GTEST_OS_WINDOWS and GTEST_HAS_PTHREAD +// defined, but we don't want to use MinGW's pthreads implementation, which +// has conformance problems with some versions of the POSIX standard. +# if GTEST_HAS_PTHREAD && !GTEST_OS_WINDOWS_MINGW + // As a C-function, ThreadFuncWithCLinkage cannot be templated itself. // Consequently, it cannot select a correct instantiation of ThreadWithParam // in order to call its Run(). Introducing ThreadWithParamBase as a @@ -2431,10 +2959,9 @@ extern "C" inline void* ThreadFuncWithCLinkage(void* thread) { template class ThreadWithParam : public ThreadWithParamBase { public: - typedef void (*UserThreadFunc)(T); + typedef void UserThreadFunc(T); - ThreadWithParam( - UserThreadFunc func, T param, Notification* thread_can_start) + ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start) : func_(func), param_(param), thread_can_start_(thread_can_start), @@ -2461,7 +2988,7 @@ class ThreadWithParam : public ThreadWithParamBase { } private: - const UserThreadFunc func_; // User-supplied thread function. + UserThreadFunc* const func_; // User-supplied thread function. const T param_; // User-supplied parameter to the thread function. // When non-NULL, used to block execution until the controller thread // notifies. @@ -2471,26 +2998,293 @@ class ThreadWithParam : public ThreadWithParamBase { GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); }; +# endif // !GTEST_OS_WINDOWS && GTEST_HAS_PTHREAD || + // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ -// MutexBase and Mutex implement mutex on pthreads-based platforms. They -// are used in conjunction with class MutexLock: +# if GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ +// Mutex and ThreadLocal have already been imported into the namespace. +// Nothing to do here. + +# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT + +// Mutex implements mutex on Windows platforms. It is used in conjunction +// with class MutexLock: // // Mutex mutex; // ... -// MutexLock lock(&mutex); // Acquires the mutex and releases it at the end -// // of the current scope. -// -// MutexBase implements behavior for both statically and dynamically -// allocated mutexes. Do not use MutexBase directly. Instead, write -// the following to define a static mutex: +// MutexLock lock(&mutex); // Acquires the mutex and releases it at the +// // end of the current scope. // +// A static Mutex *must* be defined or declared using one of the following +// macros: // GTEST_DEFINE_STATIC_MUTEX_(g_some_mutex); -// -// You can forward declare a static mutex like this: -// // GTEST_DECLARE_STATIC_MUTEX_(g_some_mutex); // -// To create a dynamic mutex, just define an object of type Mutex. +// (A non-static Mutex is defined/declared in the usual way). +class GTEST_API_ Mutex { + public: + enum MutexType { kStatic = 0, kDynamic = 1 }; + // We rely on kStaticMutex being 0 as it is to what the linker initializes + // type_ in static mutexes. critical_section_ will be initialized lazily + // in ThreadSafeLazyInit(). + enum StaticConstructorSelector { kStaticMutex = 0 }; + + // This constructor intentionally does nothing. It relies on type_ being + // statically initialized to 0 (effectively setting it to kStatic) and on + // ThreadSafeLazyInit() to lazily initialize the rest of the members. + explicit Mutex(StaticConstructorSelector /*dummy*/) {} + + Mutex(); + ~Mutex(); + + void Lock(); + + void Unlock(); + + // Does nothing if the current thread holds the mutex. Otherwise, crashes + // with high probability. + void AssertHeld(); + + private: + // Initializes owner_thread_id_ and critical_section_ in static mutexes. + void ThreadSafeLazyInit(); + + // Per http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx, + // we assume that 0 is an invalid value for thread IDs. + unsigned int owner_thread_id_; + + // For static mutexes, we rely on these members being initialized to zeros + // by the linker. + MutexType type_; + long critical_section_init_phase_; // NOLINT + _RTL_CRITICAL_SECTION* critical_section_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); +}; + +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::Mutex mutex + +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::Mutex mutex(::testing::internal::Mutex::kStaticMutex) + +// We cannot name this class MutexLock because the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex* mutex) + : mutex_(mutex) { mutex_->Lock(); } + + ~GTestMutexLock() { mutex_->Unlock(); } + + private: + Mutex* const mutex_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock); +}; + +typedef GTestMutexLock MutexLock; + +// Base class for ValueHolder. Allows a caller to hold and delete a value +// without knowing its type. +class ThreadLocalValueHolderBase { + public: + virtual ~ThreadLocalValueHolderBase() {} +}; + +// Provides a way for a thread to send notifications to a ThreadLocal +// regardless of its parameter type. +class ThreadLocalBase { + public: + // Creates a new ValueHolder object holding a default value passed to + // this ThreadLocal's constructor and returns it. It is the caller's + // responsibility not to call this when the ThreadLocal instance already + // has a value on the current thread. + virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const = 0; + + protected: + ThreadLocalBase() {} + virtual ~ThreadLocalBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocalBase); +}; + +// Maps a thread to a set of ThreadLocals that have values instantiated on that +// thread and notifies them when the thread exits. A ThreadLocal instance is +// expected to persist until all threads it has values on have terminated. +class GTEST_API_ ThreadLocalRegistry { + public: + // Registers thread_local_instance as having value on the current thread. + // Returns a value that can be used to identify the thread from other threads. + static ThreadLocalValueHolderBase* GetValueOnCurrentThread( + const ThreadLocalBase* thread_local_instance); + + // Invoked when a ThreadLocal instance is destroyed. + static void OnThreadLocalDestroyed( + const ThreadLocalBase* thread_local_instance); +}; + +class GTEST_API_ ThreadWithParamBase { + public: + void Join(); + + protected: + class Runnable { + public: + virtual ~Runnable() {} + virtual void Run() = 0; + }; + + ThreadWithParamBase(Runnable *runnable, Notification* thread_can_start); + virtual ~ThreadWithParamBase(); + + private: + AutoHandle thread_; +}; + +// Helper class for testing Google Test's multi-threading constructs. +template +class ThreadWithParam : public ThreadWithParamBase { + public: + typedef void UserThreadFunc(T); + + ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start) + : ThreadWithParamBase(new RunnableImpl(func, param), thread_can_start) { + } + virtual ~ThreadWithParam() {} + + private: + class RunnableImpl : public Runnable { + public: + RunnableImpl(UserThreadFunc* func, T param) + : func_(func), + param_(param) { + } + virtual ~RunnableImpl() {} + virtual void Run() { + func_(param_); + } + + private: + UserThreadFunc* const func_; + const T param_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(RunnableImpl); + }; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); +}; + +// Implements thread-local storage on Windows systems. +// +// // Thread 1 +// ThreadLocal tl(100); // 100 is the default value for each thread. +// +// // Thread 2 +// tl.set(150); // Changes the value for thread 2 only. +// EXPECT_EQ(150, tl.get()); +// +// // Thread 1 +// EXPECT_EQ(100, tl.get()); // In thread 1, tl has the original value. +// tl.set(200); +// EXPECT_EQ(200, tl.get()); +// +// The template type argument T must have a public copy constructor. +// In addition, the default ThreadLocal constructor requires T to have +// a public default constructor. +// +// The users of a TheadLocal instance have to make sure that all but one +// threads (including the main one) using that instance have exited before +// destroying it. Otherwise, the per-thread objects managed for them by the +// ThreadLocal instance are not guaranteed to be destroyed on all platforms. +// +// Google Test only uses global ThreadLocal objects. That means they +// will die after main() has returned. Therefore, no per-thread +// object managed by Google Test will be leaked as long as all threads +// using Google Test have exited when main() returns. +template +class ThreadLocal : public ThreadLocalBase { + public: + ThreadLocal() : default_factory_(new DefaultValueHolderFactory()) {} + explicit ThreadLocal(const T& value) + : default_factory_(new InstanceValueHolderFactory(value)) {} + + ~ThreadLocal() { ThreadLocalRegistry::OnThreadLocalDestroyed(this); } + + T* pointer() { return GetOrCreateValue(); } + const T* pointer() const { return GetOrCreateValue(); } + const T& get() const { return *pointer(); } + void set(const T& value) { *pointer() = value; } + + private: + // Holds a value of T. Can be deleted via its base class without the caller + // knowing the type of T. + class ValueHolder : public ThreadLocalValueHolderBase { + public: + ValueHolder() : value_() {} + explicit ValueHolder(const T& value) : value_(value) {} + + T* pointer() { return &value_; } + + private: + T value_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder); + }; + + + T* GetOrCreateValue() const { + return static_cast( + ThreadLocalRegistry::GetValueOnCurrentThread(this))->pointer(); + } + + virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const { + return default_factory_->MakeNewHolder(); + } + + class ValueHolderFactory { + public: + ValueHolderFactory() {} + virtual ~ValueHolderFactory() {} + virtual ValueHolder* MakeNewHolder() const = 0; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory); + }; + + class DefaultValueHolderFactory : public ValueHolderFactory { + public: + DefaultValueHolderFactory() {} + virtual ValueHolder* MakeNewHolder() const { return new ValueHolder(); } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory); + }; + + class InstanceValueHolderFactory : public ValueHolderFactory { + public: + explicit InstanceValueHolderFactory(const T& value) : value_(value) {} + virtual ValueHolder* MakeNewHolder() const { + return new ValueHolder(value_); + } + + private: + const T value_; // The value for each thread. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory); + }; + + scoped_ptr default_factory_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); +}; + +# elif GTEST_HAS_PTHREAD + +// MutexBase and Mutex implement mutex on pthreads-based platforms. class MutexBase { public: // Acquires this mutex. @@ -2535,17 +3329,12 @@ class MutexBase { }; // Forward-declares a static mutex. -# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ - extern ::testing::internal::MutexBase mutex +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::MutexBase mutex // Defines and statically (i.e. at link time) initializes a static mutex. -// The initialization list here does not explicitly initialize each field, -// instead relying on default initialization for the unspecified fields. In -// particular, the owner_ field (a pthread_t) is not explicitly initialized. -// This allows initialization to work whether pthread_t is a scalar or struct. -// The flag -Wmissing-field-initializers must not be specified for this to work. -# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ - ::testing::internal::MutexBase mutex = { PTHREAD_MUTEX_INITIALIZER, false } +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::MutexBase mutex = { PTHREAD_MUTEX_INITIALIZER, false, pthread_t() } // The Mutex class can only be used for mutexes created at runtime. It // shares its API with MutexBase otherwise. @@ -2563,9 +3352,11 @@ class Mutex : public MutexBase { GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); }; -// We cannot name this class MutexLock as the ctor declaration would +// We cannot name this class MutexLock because the ctor declaration would // conflict with a macro named MutexLock, which is defined on some -// platforms. Hence the typedef trick below. +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. class GTestMutexLock { public: explicit GTestMutexLock(MutexBase* mutex) @@ -2599,41 +3390,14 @@ extern "C" inline void DeleteThreadLocalValue(void* value_holder) { } // Implements thread-local storage on pthreads-based systems. -// -// // Thread 1 -// ThreadLocal tl(100); // 100 is the default value for each thread. -// -// // Thread 2 -// tl.set(150); // Changes the value for thread 2 only. -// EXPECT_EQ(150, tl.get()); -// -// // Thread 1 -// EXPECT_EQ(100, tl.get()); // In thread 1, tl has the original value. -// tl.set(200); -// EXPECT_EQ(200, tl.get()); -// -// The template type argument T must have a public copy constructor. -// In addition, the default ThreadLocal constructor requires T to have -// a public default constructor. -// -// An object managed for a thread by a ThreadLocal instance is deleted -// when the thread exits. Or, if the ThreadLocal instance dies in -// that thread, when the ThreadLocal dies. It's the user's -// responsibility to ensure that all other threads using a ThreadLocal -// have exited when it dies, or the per-thread objects for those -// threads will not be deleted. -// -// Google Test only uses global ThreadLocal objects. That means they -// will die after main() has returned. Therefore, no per-thread -// object managed by Google Test will be leaked as long as all threads -// using Google Test have exited when main() returns. template class ThreadLocal { public: - ThreadLocal() : key_(CreateKey()), - default_() {} - explicit ThreadLocal(const T& value) : key_(CreateKey()), - default_(value) {} + ThreadLocal() + : key_(CreateKey()), default_factory_(new DefaultValueHolderFactory()) {} + explicit ThreadLocal(const T& value) + : key_(CreateKey()), + default_factory_(new InstanceValueHolderFactory(value)) {} ~ThreadLocal() { // Destroys the managed object for the current thread, if any. @@ -2653,6 +3417,7 @@ class ThreadLocal { // Holds a value of type T. class ValueHolder : public ThreadLocalValueHolderBase { public: + ValueHolder() : value_() {} explicit ValueHolder(const T& value) : value_(value) {} T* pointer() { return &value_; } @@ -2678,22 +3443,54 @@ class ThreadLocal { return CheckedDowncastToActualType(holder)->pointer(); } - ValueHolder* const new_holder = new ValueHolder(default_); + ValueHolder* const new_holder = default_factory_->MakeNewHolder(); ThreadLocalValueHolderBase* const holder_base = new_holder; GTEST_CHECK_POSIX_SUCCESS_(pthread_setspecific(key_, holder_base)); return new_holder->pointer(); } + class ValueHolderFactory { + public: + ValueHolderFactory() {} + virtual ~ValueHolderFactory() {} + virtual ValueHolder* MakeNewHolder() const = 0; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory); + }; + + class DefaultValueHolderFactory : public ValueHolderFactory { + public: + DefaultValueHolderFactory() {} + virtual ValueHolder* MakeNewHolder() const { return new ValueHolder(); } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory); + }; + + class InstanceValueHolderFactory : public ValueHolderFactory { + public: + explicit InstanceValueHolderFactory(const T& value) : value_(value) {} + virtual ValueHolder* MakeNewHolder() const { + return new ValueHolder(value_); + } + + private: + const T value_; // The value for each thread. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory); + }; + // A key pthreads uses for looking up per-thread values. const pthread_key_t key_; - const T default_; // The default value for each thread. + scoped_ptr default_factory_; GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); }; -# define GTEST_IS_THREADSAFE 1 +# endif // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ -#else // GTEST_HAS_PTHREAD +#else // GTEST_IS_THREADSAFE // A dummy implementation of synchronization primitives (mutex, lock, // and thread-local variable). Necessary for compiling Google Test where @@ -2713,6 +3510,11 @@ class Mutex { # define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex +// We cannot name this class MutexLock because the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. class GTestMutexLock { public: explicit GTestMutexLock(Mutex*) {} // NOLINT @@ -2733,11 +3535,7 @@ class ThreadLocal { T value_; }; -// The above synchronization primitives have dummy implementations. -// Therefore Google Test is not thread-safe. -# define GTEST_IS_THREADSAFE 0 - -#endif // GTEST_HAS_PTHREAD +#endif // GTEST_IS_THREADSAFE // Returns the number of threads running in the process, or 0 to indicate that // we cannot detect it. @@ -2847,6 +3645,13 @@ inline char ToUpper(char ch) { return static_cast(toupper(static_cast(ch))); } +inline std::string StripTrailingSpaces(std::string str) { + std::string::iterator it = str.end(); + while (it != str.begin() && IsSpace(*--it)) + it = str.erase(it); + return str; +} + // The testing::internal::posix namespace holds wrappers for common // POSIX functions. These wrappers hide the differences between // Windows/MSVC and POSIX systems. Since some compilers define these @@ -2910,11 +3715,7 @@ inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } // Functions deprecated by MSVC 8.0. -#ifdef _MSC_VER -// Temporarily disable warning 4996 (deprecated function). -# pragma warning(push) -# pragma warning(disable:4996) -#endif +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4996 /* deprecated function */) inline const char* StrNCpy(char* dest, const char* src, size_t n) { return strncpy(dest, src, n); @@ -2924,7 +3725,7 @@ inline const char* StrNCpy(char* dest, const char* src, size_t n) { // StrError() aren't needed on Windows CE at this time and thus not // defined there. -#if !GTEST_OS_WINDOWS_MOBILE && !defined WINRT +#if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT inline int ChDir(const char* dir) { return chdir(dir); } #endif inline FILE* FOpen(const char* path, const char* mode) { @@ -2948,8 +3749,9 @@ inline int Close(int fd) { return close(fd); } inline const char* StrError(int errnum) { return strerror(errnum); } #endif inline const char* GetEnv(const char* name) { -#if GTEST_OS_WINDOWS_MOBILE || defined WINRT +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT // We are on Windows CE, which has no environment variables. + static_cast(name); // To prevent 'unused argument' warning. return NULL; #elif defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9) // Environment variables which we programmatically clear will be set to the @@ -2961,9 +3763,7 @@ inline const char* GetEnv(const char* name) { #endif } -#ifdef _MSC_VER -# pragma warning(pop) // Restores the warning state. -#endif +GTEST_DISABLE_MSC_WARNINGS_POP_() #if GTEST_OS_WINDOWS_MOBILE // Windows CE has no C library. The abort() function is used in @@ -3064,11 +3864,20 @@ typedef TypeWithSize<8>::Int TimeInMillis; // Represents time in milliseconds. // Utilities for command line flags and environment variables. // Macro for referencing flags. -#define GTEST_FLAG(name) FLAGS_gtest_##name +#if !defined(GTEST_FLAG) +# define GTEST_FLAG(name) FLAGS_gtest_##name +#endif // !defined(GTEST_FLAG) + +#if !defined(GTEST_USE_OWN_FLAGFILE_FLAG_) +# define GTEST_USE_OWN_FLAGFILE_FLAG_ 1 +#endif // !defined(GTEST_USE_OWN_FLAGFILE_FLAG_) + +#if !defined(GTEST_DECLARE_bool_) +# define GTEST_FLAG_SAVER_ ::testing::internal::GTestFlagSaver // Macros for declaring flags. -#define GTEST_DECLARE_bool_(name) GTEST_API_ extern bool GTEST_FLAG(name) -#define GTEST_DECLARE_int32_(name) \ +# define GTEST_DECLARE_bool_(name) GTEST_API_ extern bool GTEST_FLAG(name) +# define GTEST_DECLARE_int32_(name) \ GTEST_API_ extern ::testing::internal::Int32 GTEST_FLAG(name) #define GTEST_DECLARE_string_(name) \ GTEST_API_ extern ::std::string GTEST_FLAG(name) @@ -3081,9 +3890,13 @@ typedef TypeWithSize<8>::Int TimeInMillis; // Represents time in milliseconds. #define GTEST_DEFINE_string_(name, default_val, doc) \ GTEST_API_ ::std::string GTEST_FLAG(name) = (default_val) +#endif // !defined(GTEST_DECLARE_bool_) + // Thread annotations -#define GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks) -#define GTEST_LOCK_EXCLUDED_(locks) +#if !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_) +# define GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks) +# define GTEST_LOCK_EXCLUDED_(locks) +#endif // !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_) // Parses 'str' for a 32-bit signed integer. If successful, writes the result // to *value and returns true; otherwise leaves *value unchanged and returns @@ -3097,7 +3910,7 @@ bool ParseInt32(const Message& src_text, const char* str, Int32* value); // corresponding to the given Google Test flag. bool BoolFromGTestEnv(const char* flag, bool default_val); GTEST_API_ Int32 Int32FromGTestEnv(const char* flag, Int32 default_val); -const char* StringFromGTestEnv(const char* flag, const char* default_val); +std::string StringFromGTestEnv(const char* flag, const char* default_val); } // namespace internal } // namespace testing @@ -3120,7 +3933,10 @@ const char* StringFromGTestEnv(const char* flag, const char* default_val); #include #include #include +#include #include +#include +#include // Copyright 2005, Google Inc. // All rights reserved. @@ -7108,9 +7924,6 @@ class ScopedTrace; // Implements scoped trace. class TestInfoImpl; // Opaque implementation of TestInfo class UnitTestImpl; // Opaque implementation of UnitTest -// How many times InitGoogleTest() has been called. -GTEST_API_ extern int g_init_gtest_count; - // The text used in failure messages to indicate the start of the // stack trace. GTEST_API_ extern const char kStackTraceMarker[]; @@ -7192,6 +8005,36 @@ class GTEST_API_ ScopedTrace { // c'tor and d'tor. Therefore it doesn't // need to be used otherwise. +namespace edit_distance { +// Returns the optimal edits to go from 'left' to 'right'. +// All edits cost the same, with replace having lower priority than +// add/remove. +// Simple implementation of the Wagner–Fischer algorithm. +// See http://en.wikipedia.org/wiki/Wagner-Fischer_algorithm +enum EditType { kMatch, kAdd, kRemove, kReplace }; +GTEST_API_ std::vector CalculateOptimalEdits( + const std::vector& left, const std::vector& right); + +// Same as above, but the input is represented as strings. +GTEST_API_ std::vector CalculateOptimalEdits( + const std::vector& left, + const std::vector& right); + +// Create a diff of the input strings in Unified diff format. +GTEST_API_ std::string CreateUnifiedDiff(const std::vector& left, + const std::vector& right, + size_t context = 2); + +} // namespace edit_distance + +// Calculate the diff between 'left' and 'right' and return it in unified diff +// format. +// If not null, stores in 'total_line_count' the total number of lines found +// in left + right. +GTEST_API_ std::string DiffStrings(const std::string& left, + const std::string& right, + size_t* total_line_count); + // Constructs and returns the message for an equality assertion // (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. // @@ -7492,6 +8335,13 @@ GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr, typedef void (*SetUpTestCaseFunc)(); typedef void (*TearDownTestCaseFunc)(); +struct CodeLocation { + CodeLocation(const string& a_file, int a_line) : file(a_file), line(a_line) {} + + string file; + int line; +}; + // Creates a new TestInfo object and registers it with Google Test; // returns the created object. // @@ -7503,6 +8353,7 @@ typedef void (*TearDownTestCaseFunc)(); // this is not a typed or a type-parameterized test. // value_param text representation of the test's value parameter, // or NULL if this is not a type-parameterized test. +// code_location: code location where the test is defined // fixture_class_id: ID of the test fixture class // set_up_tc: pointer to the function that sets up the test case // tear_down_tc: pointer to the function that tears down the test case @@ -7514,6 +8365,7 @@ GTEST_API_ TestInfo* MakeAndRegisterTestInfo( const char* name, const char* type_param, const char* value_param, + CodeLocation code_location, TypeId fixture_class_id, SetUpTestCaseFunc set_up_tc, TearDownTestCaseFunc tear_down_tc, @@ -7543,10 +8395,21 @@ class GTEST_API_ TypedTestCasePState { fflush(stderr); posix::Abort(); } - defined_test_names_.insert(test_name); + registered_tests_.insert( + ::std::make_pair(test_name, CodeLocation(file, line))); return true; } + bool TestExists(const std::string& test_name) const { + return registered_tests_.count(test_name) > 0; + } + + const CodeLocation& GetCodeLocation(const std::string& test_name) const { + RegisteredTestsMap::const_iterator it = registered_tests_.find(test_name); + GTEST_CHECK_(it != registered_tests_.end()); + return it->second; + } + // Verifies that registered_tests match the test names in // defined_test_names_; returns registered_tests if successful, or // aborts the program otherwise. @@ -7554,8 +8417,10 @@ class GTEST_API_ TypedTestCasePState { const char* file, int line, const char* registered_tests); private: + typedef ::std::map RegisteredTestsMap; + bool registered_; - ::std::set defined_test_names_; + RegisteredTestsMap registered_tests_; }; // Skips to the first non-space char after the first comma in 'str'; @@ -7576,6 +8441,11 @@ inline std::string GetPrefixUntilComma(const char* str) { return comma == NULL ? str : std::string(str, comma); } +// Splits a given string on a given delimiter, populating a given +// vector with the fields. +void SplitString(const ::std::string& str, char delimiter, + ::std::vector< ::std::string>* dest); + // TypeParameterizedTest::Register() // registers a list of type-parameterized tests with Google Test. The // return value is insignificant - we just need to return something @@ -7590,8 +8460,10 @@ class TypeParameterizedTest { // specified in INSTANTIATE_TYPED_TEST_CASE_P(Prefix, TestCase, // Types). Valid values for 'index' are [0, N - 1] where N is the // length of Types. - static bool Register(const char* prefix, const char* case_name, - const char* test_names, int index) { + static bool Register(const char* prefix, + CodeLocation code_location, + const char* case_name, const char* test_names, + int index) { typedef typename Types::Head Type; typedef Fixture FixtureClass; typedef typename GTEST_BIND_(TestSel, Type) TestClass; @@ -7601,9 +8473,10 @@ class TypeParameterizedTest { MakeAndRegisterTestInfo( (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name + "/" + StreamableToString(index)).c_str(), - GetPrefixUntilComma(test_names).c_str(), + StripTrailingSpaces(GetPrefixUntilComma(test_names)).c_str(), GetTypeName().c_str(), NULL, // No value parameter. + code_location, GetTypeId(), TestClass::SetUpTestCase, TestClass::TearDownTestCase, @@ -7611,7 +8484,7 @@ class TypeParameterizedTest { // Next, recurses (at compile time) with the tail of the type list. return TypeParameterizedTest - ::Register(prefix, case_name, test_names, index + 1); + ::Register(prefix, code_location, case_name, test_names, index + 1); } }; @@ -7619,8 +8492,9 @@ class TypeParameterizedTest { template class TypeParameterizedTest { public: - static bool Register(const char* /*prefix*/, const char* /*case_name*/, - const char* /*test_names*/, int /*index*/) { + static bool Register(const char* /*prefix*/, CodeLocation, + const char* /*case_name*/, const char* /*test_names*/, + int /*index*/) { return true; } }; @@ -7632,17 +8506,31 @@ class TypeParameterizedTest { template class TypeParameterizedTestCase { public: - static bool Register(const char* prefix, const char* case_name, - const char* test_names) { + static bool Register(const char* prefix, CodeLocation code_location, + const TypedTestCasePState* state, + const char* case_name, const char* test_names) { + std::string test_name = StripTrailingSpaces( + GetPrefixUntilComma(test_names)); + if (!state->TestExists(test_name)) { + fprintf(stderr, "Failed to get code location for test %s.%s at %s.", + case_name, test_name.c_str(), + FormatFileLocation(code_location.file.c_str(), + code_location.line).c_str()); + fflush(stderr); + posix::Abort(); + } + const CodeLocation& test_location = state->GetCodeLocation(test_name); + typedef typename Tests::Head Head; // First, register the first test in 'Test' for each type in 'Types'. TypeParameterizedTest::Register( - prefix, case_name, test_names, 0); + prefix, test_location, case_name, test_names, 0); // Next, recurses (at compile time) with the tail of the test list. return TypeParameterizedTestCase - ::Register(prefix, case_name, SkipComma(test_names)); + ::Register(prefix, code_location, state, + case_name, SkipComma(test_names)); } }; @@ -7650,8 +8538,9 @@ class TypeParameterizedTestCase { template class TypeParameterizedTestCase { public: - static bool Register(const char* /*prefix*/, const char* /*case_name*/, - const char* /*test_names*/) { + static bool Register(const char* /*prefix*/, CodeLocation, + const TypedTestCasePState* /*state*/, + const char* /*case_name*/, const char* /*test_names*/) { return true; } }; @@ -7805,7 +8694,7 @@ class ImplicitlyConvertible { // MakeFrom() is an expression whose type is From. We cannot simply // use From(), as the type From may not have a public default // constructor. - static From MakeFrom(); + static typename AddReference::type MakeFrom(); // These two functions are overloaded. Given an expression // Helper(x), the compiler will pick the first version if x can be @@ -7823,25 +8712,20 @@ class ImplicitlyConvertible { // We have to put the 'public' section after the 'private' section, // or MSVC refuses to compile the code. public: - // MSVC warns about implicitly converting from double to int for - // possible loss of data, so we need to temporarily disable the - // warning. -#ifdef _MSC_VER -# pragma warning(push) // Saves the current warning state. -# pragma warning(disable:4244) // Temporarily disables warning 4244. - - static const bool value = - sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; -# pragma warning(pop) // Restores the warning state. -#elif defined(__BORLANDC__) +#if defined(__BORLANDC__) // C++Builder cannot use member overload resolution during template // instantiation. The simplest workaround is to use its C++0x type traits // functions (C++Builder 2009 and above only). static const bool value = __is_convertible(From, To); #else + // MSVC warns about implicitly converting from double to int for + // possible loss of data, so we need to temporarily disable the + // warning. + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4244) static const bool value = sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; -#endif // _MSV_VER + GTEST_DISABLE_MSC_WARNINGS_POP_() +#endif // __BORLANDC__ }; template const bool ImplicitlyConvertible::value; @@ -7967,11 +8851,10 @@ void CopyArray(const T* from, size_t size, U* to) { // The relation between an NativeArray object (see below) and the // native array it represents. -enum RelationToSource { - kReference, // The NativeArray references the native array. - kCopy // The NativeArray makes a copy of the native array and - // owns the copy. -}; +// We use 2 different structs to allow non-copyable types to be used, as long +// as RelationToSourceReference() is passed. +struct RelationToSourceReference {}; +struct RelationToSourceCopy {}; // Adapts a native array to a read-only STL-style container. Instead // of the complete STL container concept, this adaptor only implements @@ -7989,22 +8872,23 @@ class NativeArray { typedef Element* iterator; typedef const Element* const_iterator; - // Constructs from a native array. - NativeArray(const Element* array, size_t count, RelationToSource relation) { - Init(array, count, relation); + // Constructs from a native array. References the source. + NativeArray(const Element* array, size_t count, RelationToSourceReference) { + InitRef(array, count); + } + + // Constructs from a native array. Copies the source. + NativeArray(const Element* array, size_t count, RelationToSourceCopy) { + InitCopy(array, count); } // Copy constructor. NativeArray(const NativeArray& rhs) { - Init(rhs.array_, rhs.size_, rhs.relation_to_source_); + (this->*rhs.clone_)(rhs.array_, rhs.size_); } ~NativeArray() { - // Ensures that the user doesn't instantiate NativeArray with a - // const or reference type. - static_cast(StaticAssertTypeEqHelper()); - if (relation_to_source_ == kCopy) + if (clone_ != &NativeArray::InitRef) delete[] array_; } @@ -8018,23 +8902,30 @@ class NativeArray { } private: - // Initializes this object; makes a copy of the input array if - // 'relation' is kCopy. - void Init(const Element* array, size_t a_size, RelationToSource relation) { - if (relation == kReference) { - array_ = array; - } else { - Element* const copy = new Element[a_size]; - CopyArray(array, a_size, copy); - array_ = copy; - } + enum { + kCheckTypeIsNotConstOrAReference = StaticAssertTypeEqHelper< + Element, GTEST_REMOVE_REFERENCE_AND_CONST_(Element)>::value, + }; + + // Initializes this object with a copy of the input. + void InitCopy(const Element* array, size_t a_size) { + Element* const copy = new Element[a_size]; + CopyArray(array, a_size, copy); + array_ = copy; size_ = a_size; - relation_to_source_ = relation; + clone_ = &NativeArray::InitCopy; + } + + // Initializes this object with a reference of the input. + void InitRef(const Element* array, size_t a_size) { + array_ = array; + size_ = a_size; + clone_ = &NativeArray::InitRef; } const Element* array_; size_t size_; - RelationToSource relation_to_source_; + void (NativeArray::*clone_)(const Element*, size_t); GTEST_DISALLOW_ASSIGN_(NativeArray); }; @@ -8169,6 +9060,7 @@ class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ ::test_info_ =\ ::testing::internal::MakeAndRegisterTestInfo(\ #test_case_name, #test_name, NULL, NULL, \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), \ (parent_id), \ parent_class::SetUpTestCase, \ parent_class::TearDownTestCase, \ @@ -8177,6 +9069,7 @@ class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() #endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ + // Copyright 2005, Google Inc. // All rights reserved. // @@ -9013,7 +9906,10 @@ TEST_P(DerivedTest, DoesBlah) { #ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ #define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ +#include + #include +#include #include #include @@ -9131,7 +10027,12 @@ class linked_ptr_internal { MutexLock lock(&g_linked_ptr_mutex); linked_ptr_internal const* p = ptr; - while (p->next_ != ptr) p = p->next_; + while (p->next_ != ptr) { + assert(p->next_ != this && + "Trying to join() a linked ring we are already in. " + "Is GMock thread safety enabled?"); + p = p->next_; + } p->next_ = this; next_ = ptr; } @@ -9144,7 +10045,12 @@ class linked_ptr_internal { if (next_ == this) return true; linked_ptr_internal const* p = next_; - while (p->next_ != this) p = p->next_; + while (p->next_ != this) { + assert(p->next_ != next_ && + "Trying to depart() a linked ring we are not in. " + "Is GMock thread safety enabled?"); + p = p->next_; + } p->next_ = next_; return false; } @@ -9355,6 +10261,10 @@ linked_ptr make_linked_ptr(T* ptr) { #include #include +#if GTEST_HAS_STD_TUPLE_ +# include +#endif + namespace testing { // Definitions in the 'internal' and 'internal2' name spaces are @@ -9502,6 +10412,103 @@ void DefaultPrintNonContainerTo(const T& value, ::std::ostream* os) { namespace testing { namespace internal { +// FormatForComparison::Format(value) formats a +// value of type ToPrint that is an operand of a comparison assertion +// (e.g. ASSERT_EQ). OtherOperand is the type of the other operand in +// the comparison, and is used to help determine the best way to +// format the value. In particular, when the value is a C string +// (char pointer) and the other operand is an STL string object, we +// want to format the C string as a string, since we know it is +// compared by value with the string object. If the value is a char +// pointer but the other operand is not an STL string object, we don't +// know whether the pointer is supposed to point to a NUL-terminated +// string, and thus want to print it as a pointer to be safe. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// The default case. +template +class FormatForComparison { + public: + static ::std::string Format(const ToPrint& value) { + return ::testing::PrintToString(value); + } +}; + +// Array. +template +class FormatForComparison { + public: + static ::std::string Format(const ToPrint* value) { + return FormatForComparison::Format(value); + } +}; + +// By default, print C string as pointers to be safe, as we don't know +// whether they actually point to a NUL-terminated string. + +#define GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(CharType) \ + template \ + class FormatForComparison { \ + public: \ + static ::std::string Format(CharType* value) { \ + return ::testing::PrintToString(static_cast(value)); \ + } \ + } + +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(wchar_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const wchar_t); + +#undef GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_ + +// If a C string is compared with an STL string object, we know it's meant +// to point to a NUL-terminated string, and thus can print it as a string. + +#define GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(CharType, OtherStringType) \ + template <> \ + class FormatForComparison { \ + public: \ + static ::std::string Format(CharType* value) { \ + return ::testing::PrintToString(value); \ + } \ + } + +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::std::string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::std::string); + +#if GTEST_HAS_GLOBAL_STRING +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::string); +#endif + +#if GTEST_HAS_GLOBAL_WSTRING +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::wstring); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::wstring); +#endif + +#if GTEST_HAS_STD_WSTRING +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::std::wstring); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::std::wstring); +#endif + +#undef GTEST_IMPL_FORMAT_C_STRING_AS_STRING_ + +// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) +// operand to be used in a failure message. The type (but not value) +// of the other operand may affect the format. This allows us to +// print a char* as a raw pointer when it is compared against another +// char* or void*, and print it as a C string when it is compared +// against an std::string object, for example. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +std::string FormatForComparisonFailureMessage( + const T1& value, const T2& /* other_operand */) { + return FormatForComparison::Format(value); +} + // UniversalPrinter::Print(value, ostream_ptr) prints the given // value to the given ostream. The caller must ensure that // 'ostream_ptr' is not NULL, or the behavior is undefined. @@ -9732,14 +10739,16 @@ inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) { } #endif // GTEST_HAS_STD_WSTRING -#if GTEST_HAS_TR1_TUPLE -// Overload for ::std::tr1::tuple. Needed for printing function arguments, -// which are packed as tuples. - +#if GTEST_HAS_TR1_TUPLE || GTEST_HAS_STD_TUPLE_ // Helper function for printing a tuple. T must be instantiated with // a tuple type. template void PrintTupleTo(const T& t, ::std::ostream* os); +#endif // GTEST_HAS_TR1_TUPLE || GTEST_HAS_STD_TUPLE_ + +#if GTEST_HAS_TR1_TUPLE +// Overload for ::std::tr1::tuple. Needed for printing function arguments, +// which are packed as tuples. // Overloaded PrintTo() for tuples of various arities. We support // tuples of up-to 10 fields. The following implementation works @@ -9813,6 +10822,13 @@ void PrintTo( } #endif // GTEST_HAS_TR1_TUPLE +#if GTEST_HAS_STD_TUPLE_ +template +void PrintTo(const ::std::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} +#endif // GTEST_HAS_STD_TUPLE_ + // Overload for std::pair. template void PrintTo(const ::std::pair& value, ::std::ostream* os) { @@ -9832,10 +10848,7 @@ class UniversalPrinter { public: // MSVC warns about adding const to a function type, so we want to // disable the warning. -#ifdef _MSC_VER -# pragma warning(push) // Saves the current warning state. -# pragma warning(disable:4180) // Temporarily disables warning 4180. -#endif // _MSC_VER + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180) // Note: we deliberately don't call this PrintTo(), as that name // conflicts with ::testing::internal::PrintTo in the body of the @@ -9852,9 +10865,7 @@ class UniversalPrinter { PrintTo(value, os); } -#ifdef _MSC_VER -# pragma warning(pop) // Restores the warning state. -#endif // _MSC_VER + GTEST_DISABLE_MSC_WARNINGS_POP_() }; // UniversalPrintArray(begin, len, os) prints an array of 'len' @@ -9906,10 +10917,7 @@ class UniversalPrinter { public: // MSVC warns about adding const to a function type, so we want to // disable the warning. -#ifdef _MSC_VER -# pragma warning(push) // Saves the current warning state. -# pragma warning(disable:4180) // Temporarily disables warning 4180. -#endif // _MSC_VER + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180) static void Print(const T& value, ::std::ostream* os) { // Prints the address of the value. We use reinterpret_cast here @@ -9920,9 +10928,7 @@ class UniversalPrinter { UniversalPrint(value, os); } -#ifdef _MSC_VER -# pragma warning(pop) // Restores the warning state. -#endif // _MSC_VER + GTEST_DISABLE_MSC_WARNINGS_POP_() }; // Prints a value tersely: for a reference type, the referenced value @@ -10008,16 +11014,65 @@ void UniversalPrint(const T& value, ::std::ostream* os) { UniversalPrinter::Print(value, os); } -#if GTEST_HAS_TR1_TUPLE typedef ::std::vector Strings; +// TuplePolicy must provide: +// - tuple_size +// size of tuple TupleT. +// - get(const TupleT& t) +// static function extracting element I of tuple TupleT. +// - tuple_element::type +// type of element I of tuple TupleT. +template +struct TuplePolicy; + +#if GTEST_HAS_TR1_TUPLE +template +struct TuplePolicy { + typedef TupleT Tuple; + static const size_t tuple_size = ::std::tr1::tuple_size::value; + + template + struct tuple_element : ::std::tr1::tuple_element {}; + + template + static typename AddReference< + const typename ::std::tr1::tuple_element::type>::type get( + const Tuple& tuple) { + return ::std::tr1::get(tuple); + } +}; +template +const size_t TuplePolicy::tuple_size; +#endif // GTEST_HAS_TR1_TUPLE + +#if GTEST_HAS_STD_TUPLE_ +template +struct TuplePolicy< ::std::tuple > { + typedef ::std::tuple Tuple; + static const size_t tuple_size = ::std::tuple_size::value; + + template + struct tuple_element : ::std::tuple_element {}; + + template + static const typename ::std::tuple_element::type& get( + const Tuple& tuple) { + return ::std::get(tuple); + } +}; +template +const size_t TuplePolicy< ::std::tuple >::tuple_size; +#endif // GTEST_HAS_STD_TUPLE_ + +#if GTEST_HAS_TR1_TUPLE || GTEST_HAS_STD_TUPLE_ // This helper template allows PrintTo() for tuples and // UniversalTersePrintTupleFieldsToStrings() to be defined by // induction on the number of tuple fields. The idea is that // TuplePrefixPrinter::PrintPrefixTo(t, os) prints the first N // fields in tuple t, and can be defined in terms of // TuplePrefixPrinter. - +// // The inductive case. template struct TuplePrefixPrinter { @@ -10025,9 +11080,14 @@ struct TuplePrefixPrinter { template static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) { TuplePrefixPrinter::PrintPrefixTo(t, os); - *os << ", "; - UniversalPrinter::type> - ::Print(::std::tr1::get(t), os); + GTEST_INTENTIONAL_CONST_COND_PUSH_() + if (N > 1) { + GTEST_INTENTIONAL_CONST_COND_POP_() + *os << ", "; + } + UniversalPrinter< + typename TuplePolicy::template tuple_element::type> + ::Print(TuplePolicy::template get(t), os); } // Tersely prints the first N fields of a tuple to a string vector, @@ -10036,12 +11096,12 @@ struct TuplePrefixPrinter { static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) { TuplePrefixPrinter::TersePrintPrefixToStrings(t, strings); ::std::stringstream ss; - UniversalTersePrint(::std::tr1::get(t), &ss); + UniversalTersePrint(TuplePolicy::template get(t), &ss); strings->push_back(ss.str()); } }; -// Base cases. +// Base case. template <> struct TuplePrefixPrinter<0> { template @@ -10050,34 +11110,13 @@ struct TuplePrefixPrinter<0> { template static void TersePrintPrefixToStrings(const Tuple&, Strings*) {} }; -// We have to specialize the entire TuplePrefixPrinter<> class -// template here, even though the definition of -// TersePrintPrefixToStrings() is the same as the generic version, as -// Embarcadero (formerly CodeGear, formerly Borland) C++ doesn't -// support specializing a method template of a class template. -template <> -struct TuplePrefixPrinter<1> { - template - static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) { - UniversalPrinter::type>:: - Print(::std::tr1::get<0>(t), os); - } - template - static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) { - ::std::stringstream ss; - UniversalTersePrint(::std::tr1::get<0>(t), &ss); - strings->push_back(ss.str()); - } -}; - -// Helper function for printing a tuple. T must be instantiated with -// a tuple type. -template -void PrintTupleTo(const T& t, ::std::ostream* os) { +// Helper function for printing a tuple. +// Tuple must be either std::tr1::tuple or std::tuple type. +template +void PrintTupleTo(const Tuple& t, ::std::ostream* os) { *os << "("; - TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: - PrintPrefixTo(t, os); + TuplePrefixPrinter::tuple_size>::PrintPrefixTo(t, os); *os << ")"; } @@ -10087,11 +11126,11 @@ void PrintTupleTo(const T& t, ::std::ostream* os) { template Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) { Strings result; - TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: + TuplePrefixPrinter::tuple_size>:: TersePrintPrefixToStrings(value, &result); return result; } -#endif // GTEST_HAS_TR1_TUPLE +#endif // GTEST_HAS_TR1_TUPLE || GTEST_HAS_STD_TUPLE_ } // namespace internal @@ -10104,11 +11143,78 @@ template } // namespace testing +// Include any custom printer added by the local installation. +// We must include this header at the end to make sure it can use the +// declarations from this file. +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// This file provides an injection point for custom printers in a local +// installation of gTest. +// It will be included from gtest-printers.h and the overrides in this file +// will be visible to everyone. +// See documentation at gtest/gtest-printers.h for details on how to define a +// custom printer. +// +// ** Custom implementation starts here ** + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ + #endif // GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ #if GTEST_HAS_PARAM_TEST namespace testing { + +// Input to a parameterized test name generator, describing a test parameter. +// Consists of the parameter value and the integer parameter index. +template +struct TestParamInfo { + TestParamInfo(const ParamType& a_param, size_t an_index) : + param(a_param), + index(an_index) {} + ParamType param; + size_t index; +}; + +// A builtin parameterized test name generator which returns the result of +// testing::PrintToString. +struct PrintToStringParamName { + template + std::string operator()(const TestParamInfo& info) const { + return PrintToString(info.param); + } +}; + namespace internal { // INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. @@ -10118,7 +11224,7 @@ namespace internal { // TEST_P macro is used to define two tests with the same name // but in different namespaces. GTEST_API_ void ReportInvalidTestCaseType(const char* test_case_name, - const char* file, int line); + CodeLocation code_location); template class ParamGeneratorInterface; template class ParamGenerator; @@ -10266,7 +11372,7 @@ class RangeGenerator : public ParamGeneratorInterface { return base_; } virtual void Advance() { - value_ = value_ + step_; + value_ = static_cast(value_ + step_); index_++; } virtual ParamIteratorInterface* Clone() const { @@ -10303,7 +11409,7 @@ class RangeGenerator : public ParamGeneratorInterface { const T& end, const IncrementT& step) { int end_index = 0; - for (T i = begin; i < end; i = i + step) + for (T i = begin; i < end; i = static_cast(i + step)) end_index++; return end_index; } @@ -10405,6 +11511,37 @@ class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface { const ContainerType container_; }; // class ValuesInIteratorRangeGenerator +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Default parameterized test name generator, returns a string containing the +// integer test parameter index. +template +std::string DefaultParamName(const TestParamInfo& info) { + Message name_stream; + name_stream << info.index; + return name_stream.GetString(); +} + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Parameterized test name overload helpers, which help the +// INSTANTIATE_TEST_CASE_P macro choose between the default parameterized +// test name generator and user param name generator. +template +ParamNameGenFunctor GetParamNameGen(ParamNameGenFunctor func) { + return func; +} + +template +struct ParamNameGenFunc { + typedef std::string Type(const TestParamInfo&); +}; + +template +typename ParamNameGenFunc::Type *GetParamNameGen() { + return DefaultParamName; +} + // INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. // // Stores a parameter value and later creates tests parameterized with that @@ -10509,9 +11646,11 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { typedef typename TestCase::ParamType ParamType; // A function that returns an instance of appropriate generator type. typedef ParamGenerator(GeneratorCreationFunc)(); + typedef typename ParamNameGenFunc::Type ParamNameGeneratorFunc; - explicit ParameterizedTestCaseInfo(const char* name) - : test_case_name_(name) {} + explicit ParameterizedTestCaseInfo( + const char* name, CodeLocation code_location) + : test_case_name_(name), code_location_(code_location) {} // Test case base name for display purposes. virtual const string& GetTestCaseName() const { return test_case_name_; } @@ -10534,9 +11673,11 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { // about a generator. int AddTestCaseInstantiation(const string& instantiation_name, GeneratorCreationFunc* func, - const char* /* file */, - int /* line */) { - instantiations_.push_back(::std::make_pair(instantiation_name, func)); + ParamNameGeneratorFunc* name_func, + const char* file, + int line) { + instantiations_.push_back( + InstantiationInfo(instantiation_name, func, name_func, file, line)); return 0; // Return value used only to run this method in namespace scope. } // UnitTest class invokes this method to register tests in this test case @@ -10551,25 +11692,45 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { for (typename InstantiationContainer::iterator gen_it = instantiations_.begin(); gen_it != instantiations_.end(); ++gen_it) { - const string& instantiation_name = gen_it->first; - ParamGenerator generator((*gen_it->second)()); + const string& instantiation_name = gen_it->name; + ParamGenerator generator((*gen_it->generator)()); + ParamNameGeneratorFunc* name_func = gen_it->name_func; + const char* file = gen_it->file; + int line = gen_it->line; string test_case_name; if ( !instantiation_name.empty() ) test_case_name = instantiation_name + "/"; test_case_name += test_info->test_case_base_name; - int i = 0; + size_t i = 0; + std::set test_param_names; for (typename ParamGenerator::iterator param_it = generator.begin(); param_it != generator.end(); ++param_it, ++i) { Message test_name_stream; - test_name_stream << test_info->test_base_name << "/" << i; + + std::string param_name = name_func( + TestParamInfo(*param_it, i)); + + GTEST_CHECK_(IsValidParamName(param_name)) + << "Parameterized test name '" << param_name + << "' is invalid, in " << file + << " line " << line << std::endl; + + GTEST_CHECK_(test_param_names.count(param_name) == 0) + << "Duplicate parameterized test name '" << param_name + << "', in " << file << " line " << line << std::endl; + + test_param_names.insert(param_name); + + test_name_stream << test_info->test_base_name << "/" << param_name; MakeAndRegisterTestInfo( test_case_name.c_str(), test_name_stream.GetString().c_str(), NULL, // No type parameter. PrintToString(*param_it).c_str(), + code_location_, GetTestCaseTypeId(), TestCase::SetUpTestCase, TestCase::TearDownTestCase, @@ -10595,12 +11756,45 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { const scoped_ptr > test_meta_factory; }; typedef ::std::vector > TestInfoContainer; - // Keeps pairs of - // received from INSTANTIATE_TEST_CASE_P macros. - typedef ::std::vector > - InstantiationContainer; + // Records data received from INSTANTIATE_TEST_CASE_P macros: + // + struct InstantiationInfo { + InstantiationInfo(const std::string &name_in, + GeneratorCreationFunc* generator_in, + ParamNameGeneratorFunc* name_func_in, + const char* file_in, + int line_in) + : name(name_in), + generator(generator_in), + name_func(name_func_in), + file(file_in), + line(line_in) {} + + std::string name; + GeneratorCreationFunc* generator; + ParamNameGeneratorFunc* name_func; + const char* file; + int line; + }; + typedef ::std::vector InstantiationContainer; + + static bool IsValidParamName(const std::string& name) { + // Check for empty string + if (name.empty()) + return false; + + // Check for invalid characters + for (std::string::size_type index = 0; index < name.size(); ++index) { + if (!isalnum(name[index]) && name[index] != '_') + return false; + } + + return true; + } const string test_case_name_; + CodeLocation code_location_; TestInfoContainer tests_; InstantiationContainer instantiations_; @@ -10628,8 +11822,7 @@ class ParameterizedTestCaseRegistry { template ParameterizedTestCaseInfo* GetTestCasePatternHolder( const char* test_case_name, - const char* file, - int line) { + CodeLocation code_location) { ParameterizedTestCaseInfo* typed_test_info = NULL; for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); it != test_case_infos_.end(); ++it) { @@ -10638,7 +11831,7 @@ class ParameterizedTestCaseRegistry { // Complain about incorrect usage of Google Test facilities // and terminate the program since we cannot guaranty correct // test case setup and tear-down in this case. - ReportInvalidTestCaseType(test_case_name, file, line); + ReportInvalidTestCaseType(test_case_name, code_location); posix::Abort(); } else { // At this point we are sure that the object we found is of the same @@ -10651,7 +11844,8 @@ class ParameterizedTestCaseRegistry { } } if (typed_test_info == NULL) { - typed_test_info = new ParameterizedTestCaseInfo(test_case_name); + typed_test_info = new ParameterizedTestCaseInfo( + test_case_name, code_location); test_case_infos_.push_back(typed_test_info); } return typed_test_info; @@ -10719,7 +11913,7 @@ class ParameterizedTestCaseRegistry { // and at most 10 arguments in Combine. Please contact // googletestframework@googlegroups.com if you need more. // Please note that the number of arguments to Combine is limited -// by the maximum arity of the implementation of tr1::tuple which is +// by the maximum arity of the implementation of tuple which is // currently set at 10. #ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ @@ -10756,7 +11950,10 @@ class ValueArray1 { explicit ValueArray1(T1 v1) : v1_(v1) {} template - operator ParamGenerator() const { return ValuesIn(&v1_, &v1_ + 1); } + operator ParamGenerator() const { + const T array[] = {static_cast(v1_)}; + return ValuesIn(array); + } private: // No implementation - assignment is unsupported. @@ -13834,9 +15031,9 @@ class ValueArray50 { // template class CartesianProductGenerator2 - : public ParamGeneratorInterface< ::std::tr1::tuple > { + : public ParamGeneratorInterface< ::testing::tuple > { public: - typedef ::std::tr1::tuple ParamType; + typedef ::testing::tuple ParamType; CartesianProductGenerator2(const ParamGenerator& g1, const ParamGenerator& g2) @@ -13949,9 +15146,9 @@ class CartesianProductGenerator2 template class CartesianProductGenerator3 - : public ParamGeneratorInterface< ::std::tr1::tuple > { + : public ParamGeneratorInterface< ::testing::tuple > { public: - typedef ::std::tr1::tuple ParamType; + typedef ::testing::tuple ParamType; CartesianProductGenerator3(const ParamGenerator& g1, const ParamGenerator& g2, const ParamGenerator& g3) @@ -14081,9 +15278,9 @@ class CartesianProductGenerator3 template class CartesianProductGenerator4 - : public ParamGeneratorInterface< ::std::tr1::tuple > { + : public ParamGeneratorInterface< ::testing::tuple > { public: - typedef ::std::tr1::tuple ParamType; + typedef ::testing::tuple ParamType; CartesianProductGenerator4(const ParamGenerator& g1, const ParamGenerator& g2, const ParamGenerator& g3, @@ -14232,9 +15429,9 @@ class CartesianProductGenerator4 template class CartesianProductGenerator5 - : public ParamGeneratorInterface< ::std::tr1::tuple > { + : public ParamGeneratorInterface< ::testing::tuple > { public: - typedef ::std::tr1::tuple ParamType; + typedef ::testing::tuple ParamType; CartesianProductGenerator5(const ParamGenerator& g1, const ParamGenerator& g2, const ParamGenerator& g3, @@ -14400,10 +15597,10 @@ class CartesianProductGenerator5 template class CartesianProductGenerator6 - : public ParamGeneratorInterface< ::std::tr1::tuple > { public: - typedef ::std::tr1::tuple ParamType; + typedef ::testing::tuple ParamType; CartesianProductGenerator6(const ParamGenerator& g1, const ParamGenerator& g2, const ParamGenerator& g3, @@ -14586,10 +15783,10 @@ class CartesianProductGenerator6 template class CartesianProductGenerator7 - : public ParamGeneratorInterface< ::std::tr1::tuple > { public: - typedef ::std::tr1::tuple ParamType; + typedef ::testing::tuple ParamType; CartesianProductGenerator7(const ParamGenerator& g1, const ParamGenerator& g2, const ParamGenerator& g3, @@ -14789,10 +15986,10 @@ class CartesianProductGenerator7 template class CartesianProductGenerator8 - : public ParamGeneratorInterface< ::std::tr1::tuple > { public: - typedef ::std::tr1::tuple ParamType; + typedef ::testing::tuple ParamType; CartesianProductGenerator8(const ParamGenerator& g1, const ParamGenerator& g2, const ParamGenerator& g3, @@ -15011,10 +16208,10 @@ class CartesianProductGenerator8 template class CartesianProductGenerator9 - : public ParamGeneratorInterface< ::std::tr1::tuple > { public: - typedef ::std::tr1::tuple ParamType; + typedef ::testing::tuple ParamType; CartesianProductGenerator9(const ParamGenerator& g1, const ParamGenerator& g2, const ParamGenerator& g3, @@ -15250,10 +16447,10 @@ class CartesianProductGenerator9 template class CartesianProductGenerator10 - : public ParamGeneratorInterface< ::std::tr1::tuple > { public: - typedef ::std::tr1::tuple ParamType; + typedef ::testing::tuple ParamType; CartesianProductGenerator10(const ParamGenerator& g1, const ParamGenerator& g2, const ParamGenerator& g3, @@ -15515,8 +16712,8 @@ class CartesianProductHolder2 { CartesianProductHolder2(const Generator1& g1, const Generator2& g2) : g1_(g1), g2_(g2) {} template - operator ParamGenerator< ::std::tr1::tuple >() const { - return ParamGenerator< ::std::tr1::tuple >( + operator ParamGenerator< ::testing::tuple >() const { + return ParamGenerator< ::testing::tuple >( new CartesianProductGenerator2( static_cast >(g1_), static_cast >(g2_))); @@ -15537,8 +16734,8 @@ CartesianProductHolder3(const Generator1& g1, const Generator2& g2, const Generator3& g3) : g1_(g1), g2_(g2), g3_(g3) {} template - operator ParamGenerator< ::std::tr1::tuple >() const { - return ParamGenerator< ::std::tr1::tuple >( + operator ParamGenerator< ::testing::tuple >() const { + return ParamGenerator< ::testing::tuple >( new CartesianProductGenerator3( static_cast >(g1_), static_cast >(g2_), @@ -15562,8 +16759,8 @@ CartesianProductHolder4(const Generator1& g1, const Generator2& g2, const Generator3& g3, const Generator4& g4) : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {} template - operator ParamGenerator< ::std::tr1::tuple >() const { - return ParamGenerator< ::std::tr1::tuple >( + operator ParamGenerator< ::testing::tuple >() const { + return ParamGenerator< ::testing::tuple >( new CartesianProductGenerator4( static_cast >(g1_), static_cast >(g2_), @@ -15589,8 +16786,8 @@ CartesianProductHolder5(const Generator1& g1, const Generator2& g2, const Generator3& g3, const Generator4& g4, const Generator5& g5) : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {} template - operator ParamGenerator< ::std::tr1::tuple >() const { - return ParamGenerator< ::std::tr1::tuple >( + operator ParamGenerator< ::testing::tuple >() const { + return ParamGenerator< ::testing::tuple >( new CartesianProductGenerator5( static_cast >(g1_), static_cast >(g2_), @@ -15620,8 +16817,8 @@ CartesianProductHolder6(const Generator1& g1, const Generator2& g2, : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {} template - operator ParamGenerator< ::std::tr1::tuple >() const { - return ParamGenerator< ::std::tr1::tuple >( + operator ParamGenerator< ::testing::tuple >() const { + return ParamGenerator< ::testing::tuple >( new CartesianProductGenerator6( static_cast >(g1_), static_cast >(g2_), @@ -15653,9 +16850,9 @@ CartesianProductHolder7(const Generator1& g1, const Generator2& g2, : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {} template - operator ParamGenerator< ::std::tr1::tuple >() const { - return ParamGenerator< ::std::tr1::tuple >( + return ParamGenerator< ::testing::tuple >( new CartesianProductGenerator7( static_cast >(g1_), static_cast >(g2_), @@ -15691,9 +16888,9 @@ CartesianProductHolder8(const Generator1& g1, const Generator2& g2, g8_(g8) {} template - operator ParamGenerator< ::std::tr1::tuple >() const { - return ParamGenerator< ::std::tr1::tuple >( + return ParamGenerator< ::testing::tuple >( new CartesianProductGenerator8( static_cast >(g1_), static_cast >(g2_), @@ -15732,9 +16929,9 @@ CartesianProductHolder9(const Generator1& g1, const Generator2& g2, g9_(g9) {} template - operator ParamGenerator< ::std::tr1::tuple >() const { - return ParamGenerator< ::std::tr1::tuple >( new CartesianProductGenerator9( static_cast >(g1_), @@ -15776,10 +16973,10 @@ CartesianProductHolder10(const Generator1& g1, const Generator2& g2, g9_(g9), g10_(g10) {} template - operator ParamGenerator< ::std::tr1::tuple >() const { - return ParamGenerator< ::std::tr1::tuple >( + operator ParamGenerator< ::testing::tuple >() const { + return ParamGenerator< ::testing::tuple >( new CartesianProductGenerator10( static_cast >(g1_), @@ -17014,14 +18211,17 @@ internal::CartesianProductHolder10parameterized_test_registry(). \ GetTestCasePatternHolder(\ - #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ - #test_case_name, \ - #test_name, \ - new ::testing::internal::TestMetaFactory< \ - GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + #test_case_name, \ + ::testing::internal::CodeLocation(\ + __FILE__, __LINE__))->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(\ + test_case_name, test_name)>()); \ return 0; \ } \ - static int gtest_registering_dummy_; \ + static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; \ GTEST_DISALLOW_COPY_AND_ASSIGN_(\ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ }; \ @@ -17030,16 +18230,36 @@ internal::CartesianProductHolder10, and return std::string. +// +// testing::PrintToStringParamName is a builtin test suffix generator that +// returns the value of testing::PrintToString(GetParam()). It does not work +// for std::string or C strings. +// +// Note: test names must be non-empty, unique, and may only contain ASCII +// alphanumeric characters or underscore. + +# define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator, ...) \ ::testing::internal::ParamGenerator \ gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ - int gtest_##prefix##test_case_name##_dummy_ = \ + ::std::string gtest_##prefix##test_case_name##_EvalGenerateName_( \ + const ::testing::TestParamInfo& info) { \ + return ::testing::internal::GetParamNameGen \ + (__VA_ARGS__)(info); \ + } \ + int gtest_##prefix##test_case_name##_dummy_ GTEST_ATTRIBUTE_UNUSED_ = \ ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ GetTestCasePatternHolder(\ - #test_case_name, __FILE__, __LINE__)->AddTestCaseInstantiation(\ - #prefix, \ - >est_##prefix##test_case_name##_EvalGenerator_, \ - __FILE__, __LINE__) + #test_case_name, \ + ::testing::internal::CodeLocation(\ + __FILE__, __LINE__))->AddTestCaseInstantiation(\ + #prefix, \ + >est_##prefix##test_case_name##_EvalGenerator_, \ + >est_##prefix##test_case_name##_EvalGenerateName_, \ + __FILE__, __LINE__) } // namespace testing @@ -17462,7 +18682,8 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); ::testing::internal::TemplateSel< \ GTEST_TEST_CLASS_NAME_(CaseName, TestName)>, \ GTEST_TYPE_PARAMS_(CaseName)>::Register(\ - "", #CaseName, #TestName, 0); \ + "", ::testing::internal::CodeLocation(__FILE__, __LINE__), \ + #CaseName, #TestName, 0); \ template \ void GTEST_TEST_CLASS_NAME_(CaseName, TestName)::TestBody() @@ -17533,7 +18754,10 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); ::testing::internal::TypeParameterizedTestCase::type>::Register(\ - #Prefix, #CaseName, GTEST_REGISTERED_TEST_NAMES_(CaseName)) + #Prefix, \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), \ + >EST_TYPED_TEST_CASE_P_STATE_(CaseName), \ + #CaseName, GTEST_REGISTERED_TEST_NAMES_(CaseName)) #endif // GTEST_HAS_TYPED_TEST_P @@ -17544,14 +18768,14 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); // class ::string, which has the same interface as ::std::string, but // has a different implementation. // -// The user can define GTEST_HAS_GLOBAL_STRING to 1 to indicate that +// You can define GTEST_HAS_GLOBAL_STRING to 1 to indicate that // ::string is available AND is a distinct type to ::std::string, or // define it to 0 to indicate otherwise. // -// If the user's ::std::string and ::string are the same class due to -// aliasing, he should define GTEST_HAS_GLOBAL_STRING to 0. +// If ::std::string and ::string are the same class on your platform +// due to aliasing, you should define GTEST_HAS_GLOBAL_STRING to 0. // -// If the user doesn't define GTEST_HAS_GLOBAL_STRING, it is defined +// If you do not define GTEST_HAS_GLOBAL_STRING, it is defined // heuristically. namespace testing { @@ -17735,8 +18959,31 @@ class GTEST_API_ AssertionResult { // Copy constructor. // Used in EXPECT_TRUE/FALSE(assertion_result). AssertionResult(const AssertionResult& other); + + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 /* forcing value to bool */) + // Used in the EXPECT_TRUE/FALSE(bool_expression). - explicit AssertionResult(bool success) : success_(success) {} + // + // T must be contextually convertible to bool. + // + // The second parameter prevents this overload from being considered if + // the argument is implicitly convertible to AssertionResult. In that case + // we want AssertionResult's copy constructor to be used. + template + explicit AssertionResult( + const T& success, + typename internal::EnableIf< + !internal::ImplicitlyConvertible::value>::type* + /*enabler*/ = NULL) + : success_(success) {} + + GTEST_DISABLE_MSC_WARNINGS_POP_() + + // Assignment operator. + AssertionResult& operator=(AssertionResult other) { + swap(other); + return *this; + } // Returns true iff the assertion succeeded. operator bool() const { return success_; } // NOLINT @@ -17777,6 +19024,9 @@ class GTEST_API_ AssertionResult { message_->append(a_message.GetString().c_str()); } + // Swap the contents of this AssertionResult with other. + void swap(AssertionResult& other); + // Stores result of the assertion predicate. bool success_; // Stores the message describing the condition in case the expectation @@ -17784,8 +19034,6 @@ class GTEST_API_ AssertionResult { // Referenced via a pointer to avoid taking too much stack frame space // with test assertions. internal::scoped_ptr< ::std::string> message_; - - GTEST_DISALLOW_ASSIGN_(AssertionResult); }; // Makes a successful assertion result. @@ -17812,8 +19060,8 @@ GTEST_API_ AssertionResult AssertionFailure(const Message& msg); // // class FooTest : public testing::Test { // protected: -// virtual void SetUp() { ... } -// virtual void TearDown() { ... } +// void SetUp() override { ... } +// void TearDown() override { ... } // ... // }; // @@ -17905,20 +19153,19 @@ class GTEST_API_ Test { // internal method to avoid clashing with names used in user TESTs. void DeleteSelf_() { delete this; } - // Uses a GTestFlagSaver to save and restore all Google Test flags. - const internal::GTestFlagSaver* const gtest_flag_saver_; + const internal::scoped_ptr< GTEST_FLAG_SAVER_ > gtest_flag_saver_; - // Often a user mis-spells SetUp() as Setup() and spends a long time + // Often a user misspells SetUp() as Setup() and spends a long time // wondering why it is never called by Google Test. The declaration of // the following method is solely for catching such an error at // compile time: // // - The return type is deliberately chosen to be not void, so it - // will be a conflict if a user declares void Setup() in his test - // fixture. + // will be a conflict if void Setup() is declared in the user's + // test fixture. // // - This method is private, so it will be another compiler error - // if a user calls it from his test fixture. + // if the method is called from the user's test fixture. // // DO NOT OVERRIDE THIS FUNCTION. // @@ -18123,6 +19370,12 @@ class GTEST_API_ TestInfo { return NULL; } + // Returns the file name where this test is defined. + const char* file() const { return location_.file.c_str(); } + + // Returns the line where this test is defined. + int line() const { return location_.line; } + // Returns true if this test should run, that is if the test is not // disabled (or it is disabled but the also_run_disabled_tests flag has // been specified) and its full name matches the user-specified filter. @@ -18165,6 +19418,7 @@ class GTEST_API_ TestInfo { const char* name, const char* type_param, const char* value_param, + internal::CodeLocation code_location, internal::TypeId fixture_class_id, Test::SetUpTestCaseFunc set_up_tc, Test::TearDownTestCaseFunc tear_down_tc, @@ -18176,6 +19430,7 @@ class GTEST_API_ TestInfo { const std::string& name, const char* a_type_param, // NULL if not a type-parameterized test const char* a_value_param, // NULL if not a value-parameterized test + internal::CodeLocation a_code_location, internal::TypeId fixture_class_id, internal::TestFactoryBase* factory); @@ -18202,6 +19457,7 @@ class GTEST_API_ TestInfo { // Text representation of the value parameter, or NULL if this is not a // value-parameterized test. const internal::scoped_ptr value_param_; + internal::CodeLocation location_; const internal::TypeId fixture_class_id_; // ID of the test fixture class bool should_run_; // True iff this test should run bool is_disabled_; // True iff this test is disabled @@ -18401,7 +19657,7 @@ class GTEST_API_ TestCase { }; // An Environment object is capable of setting up and tearing down an -// environment. The user should subclass this to define his own +// environment. You should subclass this to define your own // environment(s). // // An Environment object does the set-up and tear-down in virtual @@ -18813,137 +20069,42 @@ GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv); namespace internal { -// FormatForComparison::Format(value) formats a -// value of type ToPrint that is an operand of a comparison assertion -// (e.g. ASSERT_EQ). OtherOperand is the type of the other operand in -// the comparison, and is used to help determine the best way to -// format the value. In particular, when the value is a C string -// (char pointer) and the other operand is an STL string object, we -// want to format the C string as a string, since we know it is -// compared by value with the string object. If the value is a char -// pointer but the other operand is not an STL string object, we don't -// know whether the pointer is supposed to point to a NUL-terminated -// string, and thus want to print it as a pointer to be safe. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. - -// The default case. -template -class FormatForComparison { - public: - static ::std::string Format(const ToPrint& value) { - return ::testing::PrintToString(value); - } -}; - -// Array. -template -class FormatForComparison { - public: - static ::std::string Format(const ToPrint* value) { - return FormatForComparison::Format(value); - } -}; - -// By default, print C string as pointers to be safe, as we don't know -// whether they actually point to a NUL-terminated string. - -#define GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(CharType) \ - template \ - class FormatForComparison { \ - public: \ - static ::std::string Format(CharType* value) { \ - return ::testing::PrintToString(static_cast(value)); \ - } \ - } - -GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char); -GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char); -GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(wchar_t); -GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const wchar_t); - -#undef GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_ - -// If a C string is compared with an STL string object, we know it's meant -// to point to a NUL-terminated string, and thus can print it as a string. - -#define GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(CharType, OtherStringType) \ - template <> \ - class FormatForComparison { \ - public: \ - static ::std::string Format(CharType* value) { \ - return ::testing::PrintToString(value); \ - } \ - } - -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::std::string); -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::std::string); - -#if GTEST_HAS_GLOBAL_STRING -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::string); -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::string); -#endif - -#if GTEST_HAS_GLOBAL_WSTRING -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::wstring); -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::wstring); -#endif - -#if GTEST_HAS_STD_WSTRING -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::std::wstring); -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::std::wstring); -#endif - -#undef GTEST_IMPL_FORMAT_C_STRING_AS_STRING_ - -// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) -// operand to be used in a failure message. The type (but not value) -// of the other operand may affect the format. This allows us to -// print a char* as a raw pointer when it is compared against another -// char* or void*, and print it as a C string when it is compared -// against an std::string object, for example. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// Separate the error generating code from the code path to reduce the stack +// frame size of CmpHelperEQ. This helps reduce the overhead of some sanitizers +// when calling EXPECT_* in a tight loop. template -std::string FormatForComparisonFailureMessage( - const T1& value, const T2& /* other_operand */) { - return FormatForComparison::Format(value); +AssertionResult CmpHelperEQFailure(const char* lhs_expression, + const char* rhs_expression, + const T1& lhs, const T2& rhs) { + return EqFailure(lhs_expression, + rhs_expression, + FormatForComparisonFailureMessage(lhs, rhs), + FormatForComparisonFailureMessage(rhs, lhs), + false); } // The helper function for {ASSERT|EXPECT}_EQ. template -AssertionResult CmpHelperEQ(const char* expected_expression, - const char* actual_expression, - const T1& expected, - const T2& actual) { -#ifdef _MSC_VER -# pragma warning(push) // Saves the current warning state. -# pragma warning(disable:4389) // Temporarily disables warning on - // signed/unsigned mismatch. -#endif - - if (expected == actual) { +AssertionResult CmpHelperEQ(const char* lhs_expression, + const char* rhs_expression, + const T1& lhs, + const T2& rhs) { +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4389 /* signed/unsigned mismatch */) + if (lhs == rhs) { return AssertionSuccess(); } +GTEST_DISABLE_MSC_WARNINGS_POP_() -#ifdef _MSC_VER -# pragma warning(pop) // Restores the warning state. -#endif - - return EqFailure(expected_expression, - actual_expression, - FormatForComparisonFailureMessage(expected, actual), - FormatForComparisonFailureMessage(actual, expected), - false); + return CmpHelperEQFailure(lhs_expression, rhs_expression, lhs, rhs); } // With this overloaded version, we allow anonymous enums to be used // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous enums // can be implicitly cast to BiggestInt. -GTEST_API_ AssertionResult CmpHelperEQ(const char* expected_expression, - const char* actual_expression, - BiggestInt expected, - BiggestInt actual); +GTEST_API_ AssertionResult CmpHelperEQ(const char* lhs_expression, + const char* rhs_expression, + BiggestInt lhs, + BiggestInt rhs); // The helper class for {ASSERT|EXPECT}_EQ. The template argument // lhs_is_null_literal is true iff the first argument to ASSERT_EQ() @@ -18954,12 +20115,11 @@ class EqHelper { public: // This templatized version is for the general case. template - static AssertionResult Compare(const char* expected_expression, - const char* actual_expression, - const T1& expected, - const T2& actual) { - return CmpHelperEQ(expected_expression, actual_expression, expected, - actual); + static AssertionResult Compare(const char* lhs_expression, + const char* rhs_expression, + const T1& lhs, + const T2& rhs) { + return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs); } // With this overloaded version, we allow anonymous enums to be used @@ -18968,12 +20128,11 @@ class EqHelper { // // Even though its body looks the same as the above version, we // cannot merge the two, as it will make anonymous enums unhappy. - static AssertionResult Compare(const char* expected_expression, - const char* actual_expression, - BiggestInt expected, - BiggestInt actual) { - return CmpHelperEQ(expected_expression, actual_expression, expected, - actual); + static AssertionResult Compare(const char* lhs_expression, + const char* rhs_expression, + BiggestInt lhs, + BiggestInt rhs) { + return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs); } }; @@ -18988,40 +20147,52 @@ class EqHelper { // EXPECT_EQ(false, a_bool). template static AssertionResult Compare( - const char* expected_expression, - const char* actual_expression, - const T1& expected, - const T2& actual, + const char* lhs_expression, + const char* rhs_expression, + const T1& lhs, + const T2& rhs, // The following line prevents this overload from being considered if T2 // is not a pointer type. We need this because ASSERT_EQ(NULL, my_ptr) // expands to Compare("", "", NULL, my_ptr), which requires a conversion // to match the Secret* in the other overload, which would otherwise make // this template match better. typename EnableIf::value>::type* = 0) { - return CmpHelperEQ(expected_expression, actual_expression, expected, - actual); + return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs); } // This version will be picked when the second argument to ASSERT_EQ() is a // pointer, e.g. ASSERT_EQ(NULL, a_pointer). template static AssertionResult Compare( - const char* expected_expression, - const char* actual_expression, + const char* lhs_expression, + const char* rhs_expression, // We used to have a second template parameter instead of Secret*. That // template parameter would deduce to 'long', making this a better match // than the first overload even without the first overload's EnableIf. // Unfortunately, gcc with -Wconversion-null warns when "passing NULL to // non-pointer argument" (even a deduced integral argument), so the old // implementation caused warnings in user code. - Secret* /* expected (NULL) */, - T* actual) { - // We already know that 'expected' is a null pointer. - return CmpHelperEQ(expected_expression, actual_expression, - static_cast(NULL), actual); + Secret* /* lhs (NULL) */, + T* rhs) { + // We already know that 'lhs' is a null pointer. + return CmpHelperEQ(lhs_expression, rhs_expression, + static_cast(NULL), rhs); } }; +// Separate the error generating code from the code path to reduce the stack +// frame size of CmpHelperOP. This helps reduce the overhead of some sanitizers +// when calling EXPECT_OP in a tight loop. +template +AssertionResult CmpHelperOpFailure(const char* expr1, const char* expr2, + const T1& val1, const T2& val2, + const char* op) { + return AssertionFailure() + << "Expected: (" << expr1 << ") " << op << " (" << expr2 + << "), actual: " << FormatForComparisonFailureMessage(val1, val2) + << " vs " << FormatForComparisonFailureMessage(val2, val1); +} + // A macro for implementing the helper functions needed to implement // ASSERT_?? and EXPECT_??. It is here just to avoid copy-and-paste // of similar code. @@ -19032,6 +20203,7 @@ class EqHelper { // with gcc 4. // // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + #define GTEST_IMPL_CMP_HELPER_(op_name, op)\ template \ AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ @@ -19039,10 +20211,7 @@ AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ if (val1 op val2) {\ return AssertionSuccess();\ } else {\ - return AssertionFailure() \ - << "Expected: (" << expr1 << ") " #op " (" << expr2\ - << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ - << " vs " << FormatForComparisonFailureMessage(val2, val1);\ + return CmpHelperOpFailure(expr1, expr2, val1, val2, #op);\ }\ }\ GTEST_API_ AssertionResult CmpHelper##op_name(\ @@ -19066,18 +20235,18 @@ GTEST_IMPL_CMP_HELPER_(GT, >); // The helper function for {ASSERT|EXPECT}_STREQ. // // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult CmpHelperSTREQ(const char* expected_expression, - const char* actual_expression, - const char* expected, - const char* actual); +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); // The helper function for {ASSERT|EXPECT}_STRCASEEQ. // // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* expected_expression, - const char* actual_expression, - const char* expected, - const char* actual); +GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); // The helper function for {ASSERT|EXPECT}_STRNE. // @@ -19099,10 +20268,10 @@ GTEST_API_ AssertionResult CmpHelperSTRCASENE(const char* s1_expression, // Helper function for *_STREQ on wide strings. // // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult CmpHelperSTREQ(const char* expected_expression, - const char* actual_expression, - const wchar_t* expected, - const wchar_t* actual); +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2); // Helper function for *_STRNE on wide strings. // @@ -19160,28 +20329,28 @@ namespace internal { // // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. template -AssertionResult CmpHelperFloatingPointEQ(const char* expected_expression, - const char* actual_expression, - RawType expected, - RawType actual) { - const FloatingPoint lhs(expected), rhs(actual); +AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression, + const char* rhs_expression, + RawType lhs_value, + RawType rhs_value) { + const FloatingPoint lhs(lhs_value), rhs(rhs_value); if (lhs.AlmostEquals(rhs)) { return AssertionSuccess(); } - ::std::stringstream expected_ss; - expected_ss << std::setprecision(std::numeric_limits::digits10 + 2) - << expected; + ::std::stringstream lhs_ss; + lhs_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << lhs_value; - ::std::stringstream actual_ss; - actual_ss << std::setprecision(std::numeric_limits::digits10 + 2) - << actual; + ::std::stringstream rhs_ss; + rhs_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << rhs_value; - return EqFailure(expected_expression, - actual_expression, - StringStreamToString(&expected_ss), - StringStreamToString(&actual_ss), + return EqFailure(lhs_expression, + rhs_expression, + StringStreamToString(&lhs_ss), + StringStreamToString(&rhs_ss), false); } @@ -19389,13 +20558,13 @@ class TestWithParam : public Test, public WithParamInterface { // AssertionResult. For more information on how to use AssertionResult with // these macros see comments on that class. #define EXPECT_TRUE(condition) \ - GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_TEST_BOOLEAN_((condition), #condition, false, true, \ GTEST_NONFATAL_FAILURE_) #define EXPECT_FALSE(condition) \ GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ GTEST_NONFATAL_FAILURE_) #define ASSERT_TRUE(condition) \ - GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_TEST_BOOLEAN_((condition), #condition, false, true, \ GTEST_FATAL_FAILURE_) #define ASSERT_FALSE(condition) \ GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ @@ -19764,12 +20933,12 @@ AssertionResult AssertPred5Helper(const char* pred_text, // Macros for testing equalities and inequalities. // -// * {ASSERT|EXPECT}_EQ(expected, actual): Tests that expected == actual -// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2 -// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2 -// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2 -// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2 -// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2 +// * {ASSERT|EXPECT}_EQ(v1, v2): Tests that v1 == v2 +// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2 +// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2 +// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2 +// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2 +// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2 // // When they are not, Google Test prints both the tested expressions and // their actual values. The values must be compatible built-in types, @@ -19791,8 +20960,8 @@ AssertionResult AssertPred5Helper(const char* pred_text, // are related, not how their content is related. To compare two C // strings by content, use {ASSERT|EXPECT}_STR*(). // -// 3. {ASSERT|EXPECT}_EQ(expected, actual) is preferred to -// {ASSERT|EXPECT}_TRUE(expected == actual), as the former tells you +// 3. {ASSERT|EXPECT}_EQ(v1, v2) is preferred to +// {ASSERT|EXPECT}_TRUE(v1 == v2), as the former tells you // what the actual value is when it fails, and similarly for the // other comparisons. // @@ -19808,12 +20977,12 @@ AssertionResult AssertPred5Helper(const char* pred_text, // ASSERT_LT(i, array_size); // ASSERT_GT(records.size(), 0) << "There is no record left."; -#define EXPECT_EQ(expected, actual) \ +#define EXPECT_EQ(val1, val2) \ EXPECT_PRED_FORMAT2(::testing::internal:: \ - EqHelper::Compare, \ - expected, actual) -#define EXPECT_NE(expected, actual) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, expected, actual) + EqHelper::Compare, \ + val1, val2) +#define EXPECT_NE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) #define EXPECT_LE(val1, val2) \ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) #define EXPECT_LT(val1, val2) \ @@ -19823,10 +20992,10 @@ AssertionResult AssertPred5Helper(const char* pred_text, #define EXPECT_GT(val1, val2) \ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) -#define GTEST_ASSERT_EQ(expected, actual) \ +#define GTEST_ASSERT_EQ(val1, val2) \ ASSERT_PRED_FORMAT2(::testing::internal:: \ - EqHelper::Compare, \ - expected, actual) + EqHelper::Compare, \ + val1, val2) #define GTEST_ASSERT_NE(val1, val2) \ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) #define GTEST_ASSERT_LE(val1, val2) \ @@ -19881,29 +21050,29 @@ AssertionResult AssertPred5Helper(const char* pred_text, // // These macros evaluate their arguments exactly once. -#define EXPECT_STREQ(expected, actual) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define EXPECT_STREQ(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2) #define EXPECT_STRNE(s1, s2) \ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) -#define EXPECT_STRCASEEQ(expected, actual) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define EXPECT_STRCASEEQ(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2) #define EXPECT_STRCASENE(s1, s2)\ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) -#define ASSERT_STREQ(expected, actual) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define ASSERT_STREQ(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2) #define ASSERT_STRNE(s1, s2) \ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) -#define ASSERT_STRCASEEQ(expected, actual) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define ASSERT_STRCASEEQ(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2) #define ASSERT_STRCASENE(s1, s2)\ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) // Macros for comparing floating-point numbers. // -// * {ASSERT|EXPECT}_FLOAT_EQ(expected, actual): +// * {ASSERT|EXPECT}_FLOAT_EQ(val1, val2): // Tests that two float values are almost equal. -// * {ASSERT|EXPECT}_DOUBLE_EQ(expected, actual): +// * {ASSERT|EXPECT}_DOUBLE_EQ(val1, val2): // Tests that two double values are almost equal. // * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error): // Tests that v1 and v2 are within the given distance to each other. @@ -19913,21 +21082,21 @@ AssertionResult AssertPred5Helper(const char* pred_text, // FloatingPoint template class in gtest-internal.h if you are // interested in the implementation details. -#define EXPECT_FLOAT_EQ(expected, actual)\ +#define EXPECT_FLOAT_EQ(val1, val2)\ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ - expected, actual) + val1, val2) -#define EXPECT_DOUBLE_EQ(expected, actual)\ +#define EXPECT_DOUBLE_EQ(val1, val2)\ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ - expected, actual) + val1, val2) -#define ASSERT_FLOAT_EQ(expected, actual)\ +#define ASSERT_FLOAT_EQ(val1, val2)\ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ - expected, actual) + val1, val2) -#define ASSERT_DOUBLE_EQ(expected, actual)\ +#define ASSERT_DOUBLE_EQ(val1, val2)\ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ - expected, actual) + val1, val2) #define EXPECT_NEAR(val1, val2, abs_error)\ EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ @@ -20049,8 +21218,8 @@ bool StaticAssertTypeEq() { // The convention is to end the test case name with "Test". For // example, a test case for the Foo class can be named FooTest. // -// The user should put his test code between braces after using this -// macro. Example: +// Test code should appear between braces after an invocation of +// this macro. Example: // // TEST(FooTest, InitializesCorrectly) { // Foo foo; diff --git a/modules/ts/src/ts_gtest.cpp b/modules/ts/src/ts_gtest.cpp index a9a1d9cba3..6597d30df4 100644 --- a/modules/ts/src/ts_gtest.cpp +++ b/modules/ts/src/ts_gtest.cpp @@ -321,6 +321,8 @@ class GTEST_API_ SingleFailureChecker { #include #include #include +#include +#include #include // NOLINT #include #include @@ -355,6 +357,7 @@ class GTEST_API_ SingleFailureChecker { #elif GTEST_OS_WINDOWS_MOBILE // We are on Windows CE. # include // NOLINT +# undef min #elif GTEST_OS_WINDOWS // We are on Windows proper. @@ -377,6 +380,7 @@ class GTEST_API_ SingleFailureChecker { // cpplint thinks that the header is already included, so we want to // silence it. # include // NOLINT +# undef min #else @@ -399,6 +403,8 @@ class GTEST_API_ SingleFailureChecker { #if GTEST_CAN_STREAM_RESULTS_ # include // NOLINT # include // NOLINT +# include // NOLINT +# include // NOLINT #endif // Indicates that this translation unit is part of Google Test's @@ -449,7 +455,7 @@ class GTEST_API_ SingleFailureChecker { // GTEST_IMPLEMENTATION_ is defined to 1 iff the current translation unit is // part of Google Test's implementation; otherwise it's undefined. #if !GTEST_IMPLEMENTATION_ -// A user is trying to include this from his code - just say no. +// If this file is included from the user's code, just say no. # error "gtest-internal-inl.h is part of Google Test's internal implementation." # error "It must not be included except by Google Test itself." #endif // GTEST_IMPLEMENTATION_ @@ -507,6 +513,7 @@ const char kShuffleFlag[] = "shuffle"; const char kStackTraceDepthFlag[] = "stack_trace_depth"; const char kStreamResultToFlag[] = "stream_result_to"; const char kThrowOnFailureFlag[] = "throw_on_failure"; +const char kFlagfileFlag[] = "flagfile"; // A valid random seed must be in [1, kMaxRandomSeed]. const int kMaxRandomSeed = 99999; @@ -842,6 +849,10 @@ class OsStackTraceGetterInterface { // CurrentStackTrace() will use to find and hide Google Test stack frames. virtual void UponLeavingGTest() = 0; + // This string is inserted in place of stack frames that are part of + // Google Test's implementation. + static const char* const kElidedFramesMarker; + private: GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetterInterface); }; @@ -849,26 +860,12 @@ class OsStackTraceGetterInterface { // A working implementation of the OsStackTraceGetterInterface interface. class OsStackTraceGetter : public OsStackTraceGetterInterface { public: - OsStackTraceGetter() : caller_frame_(NULL) {} + OsStackTraceGetter() {} - virtual string CurrentStackTrace(int max_depth, int skip_count) - GTEST_LOCK_EXCLUDED_(mutex_); - - virtual void UponLeavingGTest() GTEST_LOCK_EXCLUDED_(mutex_); - - // This string is inserted in place of stack frames that are part of - // Google Test's implementation. - static const char* const kElidedFramesMarker; + virtual string CurrentStackTrace(int max_depth, int skip_count); + virtual void UponLeavingGTest(); private: - Mutex mutex_; // protects all internal state - - // We save the stack frame below the frame that calls user code. - // We do this because the address of the frame immediately below - // the user code changes between the call to UponLeavingGTest() - // and any calls to CurrentStackTrace() from within the user code. - void* caller_frame_; - GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetter); }; @@ -1379,32 +1376,6 @@ GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv); // platform. GTEST_API_ std::string GetLastErrnoDescription(); -# if GTEST_OS_WINDOWS -// Provides leak-safe Windows kernel handle ownership. -class AutoHandle { - public: - AutoHandle() : handle_(INVALID_HANDLE_VALUE) {} - explicit AutoHandle(HANDLE handle) : handle_(handle) {} - - ~AutoHandle() { Reset(); } - - HANDLE Get() const { return handle_; } - void Reset() { Reset(INVALID_HANDLE_VALUE); } - void Reset(HANDLE handle) { - if (handle != handle_) { - if (handle_ != INVALID_HANDLE_VALUE) - ::CloseHandle(handle_); - handle_ = handle; - } - } - - private: - HANDLE handle_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle); -}; -# endif // GTEST_OS_WINDOWS - // Attempts to parse a string into a positive integer pointed to by the // number parameter. Returns true if that is possible. // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use @@ -1478,7 +1449,7 @@ class TestResultAccessor { #if GTEST_CAN_STREAM_RESULTS_ // Streams test results to the given port on the given host machine. -class StreamingListener : public EmptyTestEventListener { +class GTEST_API_ StreamingListener : public EmptyTestEventListener { public: // Abstract base class for writing strings to a socket. class AbstractSocketWriter { @@ -1677,6 +1648,12 @@ bool g_help_flag = false; } // namespace internal static const char* GetDefaultFilter() { +#ifdef GTEST_TEST_FILTER_ENV_VAR_ + const char* const testbridge_test_only = getenv(GTEST_TEST_FILTER_ENV_VAR_); + if (testbridge_test_only != NULL) { + return testbridge_test_only; + } +#endif // GTEST_TEST_FILTER_ENV_VAR_ return kUniversalFilter; } @@ -1783,6 +1760,13 @@ GTEST_DEFINE_bool_( "if exceptions are enabled or exit the program with a non-zero code " "otherwise."); +#if GTEST_USE_OWN_FLAGFILE_FLAG_ +GTEST_DEFINE_string_( + flagfile, + internal::StringFromGTestEnv("flagfile", ""), + "This flag specifies the flagfile to read command-line flags from."); +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ + namespace internal { // Generates a random number from [0, range), using a Linear @@ -1807,13 +1791,7 @@ UInt32 Random::Generate(UInt32 range) { // GTestIsInitialized() returns true iff the user has initialized // Google Test. Useful for catching the user mistake of not initializing // Google Test before calling RUN_ALL_TESTS(). -// -// A user must call testing::InitGoogleTest() to initialize Google -// Test. g_init_gtest_count is set to the number of times -// InitGoogleTest() has been called. We don't protect this variable -// under a mutex as it is only accessed in the main thread. -GTEST_API_ int g_init_gtest_count = 0; -static bool GTestIsInitialized() { return g_init_gtest_count != 0; } +static bool GTestIsInitialized() { return GetArgvs().size() > 0; } // Iterates over a vector of TestCases, keeping a running sum of the // results of calling a given int-returning method on each. @@ -1869,8 +1847,16 @@ void AssertHelper::operator=(const Message& message) const { // Mutex for linked pointers. GTEST_API_ GTEST_DEFINE_STATIC_MUTEX_(g_linked_ptr_mutex); -// Application pathname gotten in InitGoogleTest. -std::string g_executable_path; +// A copy of all command line arguments. Set by InitGoogleTest(). +::std::vector g_argvs; + +const ::std::vector& GetArgvs() { +#if defined(GTEST_CUSTOM_GET_ARGVS_) + return GTEST_CUSTOM_GET_ARGVS_(); +#else // defined(GTEST_CUSTOM_GET_ARGVS_) + return g_argvs; +#endif // defined(GTEST_CUSTOM_GET_ARGVS_) +} // Returns the current application's name, removing directory path if that // is present. @@ -1878,9 +1864,9 @@ FilePath GetCurrentExecutableName() { FilePath result; #if GTEST_OS_WINDOWS - result.Set(FilePath(g_executable_path).RemoveExtension("exe")); + result.Set(FilePath(GetArgvs()[0]).RemoveExtension("exe")); #else - result.Set(FilePath(g_executable_path)); + result.Set(FilePath(GetArgvs()[0])); #endif // GTEST_OS_WINDOWS return result.RemoveDirectoryName(); @@ -2272,8 +2258,12 @@ int UnitTestImpl::test_to_run_count() const { // CurrentOsStackTraceExceptTop(1), Foo() will be included in the // trace but Bar() and CurrentOsStackTraceExceptTop() won't. std::string UnitTestImpl::CurrentOsStackTraceExceptTop(int skip_count) { - (void)skip_count; - return ""; + return os_stack_trace_getter()->CurrentStackTrace( + static_cast(GTEST_FLAG(stack_trace_depth)), + skip_count + 1 + // Skips the user-specified number of frames plus this function + // itself. + ); // NOLINT } // Returns the current time in milliseconds. @@ -2302,21 +2292,13 @@ TimeInMillis GetTimeInMillis() { #elif GTEST_OS_WINDOWS && !GTEST_HAS_GETTIMEOFDAY_ __timeb64 now; -# ifdef _MSC_VER - // MSVC 8 deprecates _ftime64(), so we want to suppress warning 4996 // (deprecated function) there. // TODO(kenton@google.com): Use GetTickCount()? Or use // SystemTimeToFileTime() -# pragma warning(push) // Saves the current warning state. -# pragma warning(disable:4996) // Temporarily disables warning 4996. + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4996) _ftime64(&now); -# pragma warning(pop) // Restores the warning state. -# else - - _ftime64(&now); - -# endif // _MSC_VER + GTEST_DISABLE_MSC_WARNINGS_POP_() return static_cast(now.time) * 1000 + now.millitm; #elif GTEST_HAS_GETTIMEOFDAY_ @@ -2401,6 +2383,23 @@ static void StreamWideCharsToMessage(const wchar_t* wstr, size_t length, #endif // GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING +void SplitString(const ::std::string& str, char delimiter, + ::std::vector< ::std::string>* dest) { + ::std::vector< ::std::string> parsed; + ::std::string::size_type pos = 0; + while (::testing::internal::AlwaysTrue()) { + const ::std::string::size_type colon = str.find(delimiter, pos); + if (colon == ::std::string::npos) { + parsed.push_back(str.substr(pos)); + break; + } else { + parsed.push_back(str.substr(pos, colon - pos)); + pos = colon + 1; + } + } + dest->swap(parsed); +} + } // namespace internal // Constructs an empty Message. @@ -2456,6 +2455,13 @@ AssertionResult::AssertionResult(const AssertionResult& other) static_cast< ::std::string*>(NULL)) { } +// Swaps two AssertionResults. +void AssertionResult::swap(AssertionResult& other) { + using std::swap; + swap(success_, other.success_); + swap(message_, other.message_); +} + // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. AssertionResult AssertionResult::operator!() const { AssertionResult negation(!success_); @@ -2482,6 +2488,276 @@ AssertionResult AssertionFailure(const Message& message) { namespace internal { +namespace edit_distance { +std::vector CalculateOptimalEdits(const std::vector& left, + const std::vector& right) { + std::vector > costs( + left.size() + 1, std::vector(right.size() + 1)); + std::vector > best_move( + left.size() + 1, std::vector(right.size() + 1)); + + // Populate for empty right. + for (size_t l_i = 0; l_i < costs.size(); ++l_i) { + costs[l_i][0] = static_cast(l_i); + best_move[l_i][0] = kRemove; + } + // Populate for empty left. + for (size_t r_i = 1; r_i < costs[0].size(); ++r_i) { + costs[0][r_i] = static_cast(r_i); + best_move[0][r_i] = kAdd; + } + + for (size_t l_i = 0; l_i < left.size(); ++l_i) { + for (size_t r_i = 0; r_i < right.size(); ++r_i) { + if (left[l_i] == right[r_i]) { + // Found a match. Consume it. + costs[l_i + 1][r_i + 1] = costs[l_i][r_i]; + best_move[l_i + 1][r_i + 1] = kMatch; + continue; + } + + const double add = costs[l_i + 1][r_i]; + const double remove = costs[l_i][r_i + 1]; + const double replace = costs[l_i][r_i]; + if (add < remove && add < replace) { + costs[l_i + 1][r_i + 1] = add + 1; + best_move[l_i + 1][r_i + 1] = kAdd; + } else if (remove < add && remove < replace) { + costs[l_i + 1][r_i + 1] = remove + 1; + best_move[l_i + 1][r_i + 1] = kRemove; + } else { + // We make replace a little more expensive than add/remove to lower + // their priority. + costs[l_i + 1][r_i + 1] = replace + 1.00001; + best_move[l_i + 1][r_i + 1] = kReplace; + } + } + } + + // Reconstruct the best path. We do it in reverse order. + std::vector best_path; + for (size_t l_i = left.size(), r_i = right.size(); l_i > 0 || r_i > 0;) { + EditType move = best_move[l_i][r_i]; + best_path.push_back(move); + l_i -= move != kAdd; + r_i -= move != kRemove; + } + std::reverse(best_path.begin(), best_path.end()); + return best_path; +} + +namespace { + +// Helper class to convert string into ids with deduplication. +class InternalStrings { + public: + size_t GetId(const std::string& str) { + IdMap::iterator it = ids_.find(str); + if (it != ids_.end()) return it->second; + size_t id = ids_.size(); + return ids_[str] = id; + } + + private: + typedef std::map IdMap; + IdMap ids_; +}; + +} // namespace + +std::vector CalculateOptimalEdits( + const std::vector& left, + const std::vector& right) { + std::vector left_ids, right_ids; + { + InternalStrings intern_table; + for (size_t i = 0; i < left.size(); ++i) { + left_ids.push_back(intern_table.GetId(left[i])); + } + for (size_t i = 0; i < right.size(); ++i) { + right_ids.push_back(intern_table.GetId(right[i])); + } + } + return CalculateOptimalEdits(left_ids, right_ids); +} + +namespace { + +// Helper class that holds the state for one hunk and prints it out to the +// stream. +// It reorders adds/removes when possible to group all removes before all +// adds. It also adds the hunk header before printint into the stream. +class Hunk { + public: + Hunk(size_t left_start, size_t right_start) + : left_start_(left_start), + right_start_(right_start), + adds_(), + removes_(), + common_() {} + + void PushLine(char edit, const char* line) { + switch (edit) { + case ' ': + ++common_; + FlushEdits(); + hunk_.push_back(std::make_pair(' ', line)); + break; + case '-': + ++removes_; + hunk_removes_.push_back(std::make_pair('-', line)); + break; + case '+': + ++adds_; + hunk_adds_.push_back(std::make_pair('+', line)); + break; + } + } + + void PrintTo(std::ostream* os) { + PrintHeader(os); + FlushEdits(); + for (std::list >::const_iterator it = + hunk_.begin(); + it != hunk_.end(); ++it) { + *os << it->first << it->second << "\n"; + } + } + + bool has_edits() const { return adds_ || removes_; } + + private: + void FlushEdits() { + hunk_.splice(hunk_.end(), hunk_removes_); + hunk_.splice(hunk_.end(), hunk_adds_); + } + + // Print a unified diff header for one hunk. + // The format is + // "@@ -, +, @@" + // where the left/right parts are ommitted if unnecessary. + void PrintHeader(std::ostream* ss) const { + *ss << "@@ "; + if (removes_) { + *ss << "-" << left_start_ << "," << (removes_ + common_); + } + if (removes_ && adds_) { + *ss << " "; + } + if (adds_) { + *ss << "+" << right_start_ << "," << (adds_ + common_); + } + *ss << " @@\n"; + } + + size_t left_start_, right_start_; + size_t adds_, removes_, common_; + std::list > hunk_, hunk_adds_, hunk_removes_; +}; + +} // namespace + +// Create a list of diff hunks in Unified diff format. +// Each hunk has a header generated by PrintHeader above plus a body with +// lines prefixed with ' ' for no change, '-' for deletion and '+' for +// addition. +// 'context' represents the desired unchanged prefix/suffix around the diff. +// If two hunks are close enough that their contexts overlap, then they are +// joined into one hunk. +std::string CreateUnifiedDiff(const std::vector& left, + const std::vector& right, + size_t context) { + const std::vector edits = CalculateOptimalEdits(left, right); + + size_t l_i = 0, r_i = 0, edit_i = 0; + std::stringstream ss; + while (edit_i < edits.size()) { + // Find first edit. + while (edit_i < edits.size() && edits[edit_i] == kMatch) { + ++l_i; + ++r_i; + ++edit_i; + } + + // Find the first line to include in the hunk. + const size_t prefix_context = std::min(l_i, context); + Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1); + for (size_t i = prefix_context; i > 0; --i) { + hunk.PushLine(' ', left[l_i - i].c_str()); + } + + // Iterate the edits until we found enough suffix for the hunk or the input + // is over. + size_t n_suffix = 0; + for (; edit_i < edits.size(); ++edit_i) { + if (n_suffix >= context) { + // Continue only if the next hunk is very close. + std::vector::const_iterator it = edits.begin() + edit_i; + while (it != edits.end() && *it == kMatch) ++it; + if (it == edits.end() || (it - edits.begin()) - edit_i >= context) { + // There is no next edit or it is too far away. + break; + } + } + + EditType edit = edits[edit_i]; + // Reset count when a non match is found. + n_suffix = edit == kMatch ? n_suffix + 1 : 0; + + if (edit == kMatch || edit == kRemove || edit == kReplace) { + hunk.PushLine(edit == kMatch ? ' ' : '-', left[l_i].c_str()); + } + if (edit == kAdd || edit == kReplace) { + hunk.PushLine('+', right[r_i].c_str()); + } + + // Advance indices, depending on edit type. + l_i += edit != kAdd; + r_i += edit != kRemove; + } + + if (!hunk.has_edits()) { + // We are done. We don't want this hunk. + break; + } + + hunk.PrintTo(&ss); + } + return ss.str(); +} + +} // namespace edit_distance + +namespace { + +// The string representation of the values received in EqFailure() are already +// escaped. Split them on escaped '\n' boundaries. Leave all other escaped +// characters the same. +std::vector SplitEscapedString(const std::string& str) { + std::vector lines; + size_t start = 0, end = str.size(); + if (end > 2 && str[0] == '"' && str[end - 1] == '"') { + ++start; + --end; + } + bool escaped = false; + for (size_t i = start; i + 1 < end; ++i) { + if (escaped) { + escaped = false; + if (str[i] == 'n') { + lines.push_back(str.substr(start, i - start - 1)); + start = i + 1; + } + } else { + escaped = str[i] == '\\'; + } + } + lines.push_back(str.substr(start, end - start)); + return lines; +} + +} // namespace + // Constructs and returns the message for an equality assertion // (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. // @@ -2489,31 +2765,42 @@ namespace internal { // and their values, as strings. For example, for ASSERT_EQ(foo, bar) // where foo is 5 and bar is 6, we have: // -// expected_expression: "foo" -// actual_expression: "bar" -// expected_value: "5" -// actual_value: "6" +// lhs_expression: "foo" +// rhs_expression: "bar" +// lhs_value: "5" +// rhs_value: "6" // // The ignoring_case parameter is true iff the assertion is a -// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// *_STRCASEEQ*. When it's true, the string "Ignoring case" will // be inserted into the message. -AssertionResult EqFailure(const char* expected_expression, - const char* actual_expression, - const std::string& expected_value, - const std::string& actual_value, +AssertionResult EqFailure(const char* lhs_expression, + const char* rhs_expression, + const std::string& lhs_value, + const std::string& rhs_value, bool ignoring_case) { Message msg; - msg << "Value of: " << actual_expression; - if (actual_value != actual_expression) { - msg << "\n Actual: " << actual_value; + msg << " Expected: " << lhs_expression; + if (lhs_value != lhs_expression) { + msg << "\n Which is: " << lhs_value; + } + msg << "\nTo be equal to: " << rhs_expression; + if (rhs_value != rhs_expression) { + msg << "\n Which is: " << rhs_value; } - msg << "\nExpected: " << expected_expression; if (ignoring_case) { - msg << " (ignoring case)"; + msg << "\nIgnoring case"; } - if (expected_value != expected_expression) { - msg << "\nWhich is: " << expected_value; + + if (!lhs_value.empty() && !rhs_value.empty()) { + const std::vector lhs_lines = + SplitEscapedString(lhs_value); + const std::vector rhs_lines = + SplitEscapedString(rhs_value); + if (lhs_lines.size() > 1 || rhs_lines.size() > 1) { + msg << "\nWith diff:\n" + << edit_distance::CreateUnifiedDiff(lhs_lines, rhs_lines); + } } return AssertionFailure() << msg; @@ -2611,18 +2898,18 @@ namespace internal { // The helper function for {ASSERT|EXPECT}_EQ with int or enum // arguments. -AssertionResult CmpHelperEQ(const char* expected_expression, - const char* actual_expression, - BiggestInt expected, - BiggestInt actual) { - if (expected == actual) { +AssertionResult CmpHelperEQ(const char* lhs_expression, + const char* rhs_expression, + BiggestInt lhs, + BiggestInt rhs) { + if (lhs == rhs) { return AssertionSuccess(); } - return EqFailure(expected_expression, - actual_expression, - FormatForComparisonFailureMessage(expected, actual), - FormatForComparisonFailureMessage(actual, expected), + return EqFailure(lhs_expression, + rhs_expression, + FormatForComparisonFailureMessage(lhs, rhs), + FormatForComparisonFailureMessage(rhs, lhs), false); } @@ -2661,34 +2948,34 @@ GTEST_IMPL_CMP_HELPER_(GT, > ) #undef GTEST_IMPL_CMP_HELPER_ // The helper function for {ASSERT|EXPECT}_STREQ. -AssertionResult CmpHelperSTREQ(const char* expected_expression, - const char* actual_expression, - const char* expected, - const char* actual) { - if (String::CStringEquals(expected, actual)) { +AssertionResult CmpHelperSTREQ(const char* lhs_expression, + const char* rhs_expression, + const char* lhs, + const char* rhs) { + if (String::CStringEquals(lhs, rhs)) { return AssertionSuccess(); } - return EqFailure(expected_expression, - actual_expression, - PrintToString(expected), - PrintToString(actual), + return EqFailure(lhs_expression, + rhs_expression, + PrintToString(lhs), + PrintToString(rhs), false); } // The helper function for {ASSERT|EXPECT}_STRCASEEQ. -AssertionResult CmpHelperSTRCASEEQ(const char* expected_expression, - const char* actual_expression, - const char* expected, - const char* actual) { - if (String::CaseInsensitiveCStringEquals(expected, actual)) { +AssertionResult CmpHelperSTRCASEEQ(const char* lhs_expression, + const char* rhs_expression, + const char* lhs, + const char* rhs) { + if (String::CaseInsensitiveCStringEquals(lhs, rhs)) { return AssertionSuccess(); } - return EqFailure(expected_expression, - actual_expression, - PrintToString(expected), - PrintToString(actual), + return EqFailure(lhs_expression, + rhs_expression, + PrintToString(lhs), + PrintToString(rhs), true); } @@ -3043,18 +3330,18 @@ bool String::WideCStringEquals(const wchar_t * lhs, const wchar_t * rhs) { } // Helper function for *_STREQ on wide strings. -AssertionResult CmpHelperSTREQ(const char* expected_expression, - const char* actual_expression, - const wchar_t* expected, - const wchar_t* actual) { - if (String::WideCStringEquals(expected, actual)) { +AssertionResult CmpHelperSTREQ(const char* lhs_expression, + const char* rhs_expression, + const wchar_t* lhs, + const wchar_t* rhs) { + if (String::WideCStringEquals(lhs, rhs)) { return AssertionSuccess(); } - return EqFailure(expected_expression, - actual_expression, - PrintToString(expected), - PrintToString(actual), + return EqFailure(lhs_expression, + rhs_expression, + PrintToString(lhs), + PrintToString(rhs), false); } @@ -3387,14 +3674,15 @@ int TestResult::test_property_count() const { // Creates a Test object. -// The c'tor saves the values of all Google Test flags. +// The c'tor saves the states of all flags. Test::Test() - : gtest_flag_saver_(new internal::GTestFlagSaver) { + : gtest_flag_saver_(new GTEST_FLAG_SAVER_) { } -// The d'tor restores the values of all Google Test flags. +// The d'tor restores the states of all flags. The actual work is +// done by the d'tor of the gtest_flag_saver_ field, and thus not +// visible here. Test::~Test() { - delete gtest_flag_saver_; } // Sets up the test fixture. @@ -3463,8 +3751,8 @@ bool Test::HasSameFixtureClass() { const bool this_is_TEST = this_fixture_id == internal::GetTestTypeId(); if (first_is_TEST || this_is_TEST) { - // The user mixed TEST and TEST_F in this test case - we'll tell - // him/her how to fix it. + // Both TEST and TEST_F appear in same test case, which is incorrect. + // Tell the user how to fix this. // Gets the name of the TEST and the name of the TEST_F. Note // that first_is_TEST and this_is_TEST cannot both be true, as @@ -3484,8 +3772,8 @@ bool Test::HasSameFixtureClass() { << "want to change the TEST to TEST_F or move it to another test\n" << "case."; } else { - // The user defined two fixture classes with the same name in - // two namespaces - we'll tell him/her how to fix it. + // Two fixture classes with the same name appear in two different + // namespaces, which is not allowed. Tell the user how to fix this. ADD_FAILURE() << "All tests in the same test case must use the same test fixture\n" << "class. However, in test case " @@ -3678,12 +3966,14 @@ TestInfo::TestInfo(const std::string& a_test_case_name, const std::string& a_name, const char* a_type_param, const char* a_value_param, + internal::CodeLocation a_code_location, internal::TypeId fixture_class_id, internal::TestFactoryBase* factory) : test_case_name_(a_test_case_name), name_(a_name), type_param_(a_type_param ? new std::string(a_type_param) : NULL), value_param_(a_value_param ? new std::string(a_value_param) : NULL), + location_(a_code_location), fixture_class_id_(fixture_class_id), should_run_(false), is_disabled_(false), @@ -3707,6 +3997,7 @@ namespace internal { // this is not a typed or a type-parameterized test. // value_param: text representation of the test's value parameter, // or NULL if this is not a value-parameterized test. +// code_location: code location where the test is defined // fixture_class_id: ID of the test fixture class // set_up_tc: pointer to the function that sets up the test case // tear_down_tc: pointer to the function that tears down the test case @@ -3718,20 +4009,21 @@ TestInfo* MakeAndRegisterTestInfo( const char* name, const char* type_param, const char* value_param, + CodeLocation code_location, TypeId fixture_class_id, SetUpTestCaseFunc set_up_tc, TearDownTestCaseFunc tear_down_tc, TestFactoryBase* factory) { TestInfo* const test_info = new TestInfo(test_case_name, name, type_param, value_param, - fixture_class_id, factory); + code_location, fixture_class_id, factory); GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); return test_info; } #if GTEST_HAS_PARAM_TEST void ReportInvalidTestCaseType(const char* test_case_name, - const char* file, int line) { + CodeLocation code_location) { Message errors; errors << "Attempted redefinition of test case " << test_case_name << ".\n" @@ -3743,7 +4035,9 @@ void ReportInvalidTestCaseType(const char* test_case_name, << "probably rename one of the classes to put the tests into different\n" << "test cases."; - fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), + fprintf(stderr, "%s %s", + FormatFileLocation(code_location.file.c_str(), + code_location.line).c_str(), errors.GetString().c_str()); } #endif // GTEST_HAS_PARAM_TEST @@ -4054,7 +4348,8 @@ enum GTestColor { COLOR_YELLOW }; -#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && !defined WINRT +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \ + !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT // Returns the character attribute for the given color. WORD GetColorAttribute(GTestColor color) { @@ -4099,6 +4394,10 @@ bool ShouldUseColor(bool stdout_is_tty) { String::CStringEquals(term, "xterm-256color") || String::CStringEquals(term, "screen") || String::CStringEquals(term, "screen-256color") || + String::CStringEquals(term, "tmux") || + String::CStringEquals(term, "tmux-256color") || + String::CStringEquals(term, "rxvt-unicode") || + String::CStringEquals(term, "rxvt-unicode-256color") || String::CStringEquals(term, "linux") || String::CStringEquals(term, "cygwin"); return stdout_is_tty && term_supports_color; @@ -4122,8 +4421,9 @@ static void ColoredPrintf(GTestColor color, const char* fmt, ...) { va_list args; va_start(args, fmt); -#if GTEST_OS_WINDOWS_MOBILE || WINRT || GTEST_OS_SYMBIAN || GTEST_OS_ZOS || GTEST_OS_IOS - const bool use_color = false; +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || GTEST_OS_ZOS || \ + GTEST_OS_IOS || GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT + const bool use_color = AlwaysFalse(); #else static const bool in_color_mode = ShouldUseColor(posix::IsATTY(posix::FileNo(stdout)) != 0); @@ -4137,7 +4437,8 @@ static void ColoredPrintf(GTestColor color, const char* fmt, ...) { return; } -#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && !defined WINRT +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \ + !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); // Gets the current text color. @@ -4720,34 +5021,39 @@ std::string XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters( // Formats the given time in milliseconds as seconds. std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) { ::std::stringstream ss; - ss << ms/1000.0; + ss << (static_cast(ms) * 1e-3); return ss.str(); } +static bool PortableLocaltime(time_t seconds, struct tm* out) { +#if defined(_MSC_VER) + return localtime_s(out, &seconds) == 0; +#elif defined(__MINGW32__) || defined(__MINGW64__) + // MINGW provides neither localtime_r nor localtime_s, but uses + // Windows' localtime(), which has a thread-local tm buffer. + struct tm* tm_ptr = localtime(&seconds); // NOLINT + if (tm_ptr == NULL) + return false; + *out = *tm_ptr; + return true; +#else + return localtime_r(&seconds, out) != NULL; +#endif +} + // Converts the given epoch time in milliseconds to a date string in the ISO // 8601 format, without the timezone information. std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) { - // Using non-reentrant version as localtime_r is not portable. - time_t seconds = static_cast(ms / 1000); -#ifdef _MSC_VER -# pragma warning(push) // Saves the current warning state. -# pragma warning(disable:4996) // Temporarily disables warning 4996 - // (function or variable may be unsafe). - const struct tm* const time_struct = localtime(&seconds); // NOLINT -# pragma warning(pop) // Restores the warning state again. -#else - const struct tm* const time_struct = localtime(&seconds); // NOLINT -#endif - if (time_struct == NULL) - return ""; // Invalid ms value - + struct tm time_struct; + if (!PortableLocaltime(static_cast(ms / 1000), &time_struct)) + return ""; // YYYY-MM-DDThh:mm:ss - return StreamableToString(time_struct->tm_year + 1900) + "-" + - String::FormatIntWidth2(time_struct->tm_mon + 1) + "-" + - String::FormatIntWidth2(time_struct->tm_mday) + "T" + - String::FormatIntWidth2(time_struct->tm_hour) + ":" + - String::FormatIntWidth2(time_struct->tm_min) + ":" + - String::FormatIntWidth2(time_struct->tm_sec); + return StreamableToString(time_struct.tm_year + 1900) + "-" + + String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" + + String::FormatIntWidth2(time_struct.tm_mday) + "T" + + String::FormatIntWidth2(time_struct.tm_hour) + ":" + + String::FormatIntWidth2(time_struct.tm_min) + ":" + + String::FormatIntWidth2(time_struct.tm_sec); } // Streams an XML CDATA section, escaping invalid CDATA sequences as needed. @@ -5010,26 +5316,15 @@ ScopedTrace::~ScopedTrace() // class OsStackTraceGetter -// Returns the current OS stack trace as an std::string. Parameters: -// -// max_depth - the maximum number of stack frames to be included -// in the trace. -// skip_count - the number of top frames to be skipped; doesn't count -// against max_depth. -// -string OsStackTraceGetter::CurrentStackTrace(int /* max_depth */, - int /* skip_count */) - GTEST_LOCK_EXCLUDED_(mutex_) { +const char* const OsStackTraceGetterInterface::kElidedFramesMarker = + "... " GTEST_NAME_ " internal frames ..."; + +string OsStackTraceGetter::CurrentStackTrace(int /*max_depth*/, + int /*skip_count*/) { return ""; } -void OsStackTraceGetter::UponLeavingGTest() - GTEST_LOCK_EXCLUDED_(mutex_) { -} - -const char* const -OsStackTraceGetter::kElidedFramesMarker = - "... " GTEST_NAME_ " internal frames ..."; +void OsStackTraceGetter::UponLeavingGTest() {} // A helper class that creates the premature-exit file in its // constructor and deletes the file in its destructor. @@ -5320,7 +5615,7 @@ void UnitTest::AddTestPartResult( // with another testing framework) and specify the former on the // command line for debugging. if (GTEST_FLAG(break_on_failure)) { -#if GTEST_OS_WINDOWS && !defined WINRT +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT // Using DebugBreak on Windows allows gtest to still break into a debugger // when a failure happens and both the --gtest_break_on_failure and // the --gtest_catch_exceptions flags are specified. @@ -5398,7 +5693,7 @@ int UnitTest::Run() { // process. In either case the user does not want to see pop-up dialogs // about crashes - they are expected. if (impl()->catch_exceptions() || in_death_test_child_process) { -# if !GTEST_OS_WINDOWS_MOBILE && !defined WINRT +# if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT // SetErrorMode doesn't exist on CE. SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); @@ -5501,17 +5796,10 @@ namespace internal { UnitTestImpl::UnitTestImpl(UnitTest* parent) : parent_(parent), -#ifdef _MSC_VER -# pragma warning(push) // Saves the current warning state. -# pragma warning(disable:4355) // Temporarily disables warning 4355 - // (using this in initializer). + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4355 /* using this in initializer */) default_global_test_part_result_reporter_(this), default_per_thread_test_part_result_reporter_(this), -# pragma warning(pop) // Restores the warning state again. -#else - default_global_test_part_result_reporter_(this), - default_per_thread_test_part_result_reporter_(this), -#endif // _MSC_VER + GTEST_DISABLE_MSC_WARNINGS_POP_() global_test_part_result_repoter_( &default_global_test_part_result_reporter_), per_thread_test_part_result_reporter_( @@ -5622,6 +5910,11 @@ void UnitTestImpl::PostFlagParsingInit() { if (!post_flag_parse_init_performed_) { post_flag_parse_init_performed_ = true; +#if defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_) + // Register to send notifications about key process state changes. + listeners()->Append(new GTEST_CUSTOM_TEST_EVENT_LISTENER_()); +#endif // defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_) + #if GTEST_HAS_DEATH_TEST InitDeathTestSubprocessControlInfo(); SuppressTestEventsIfInSubprocess(); @@ -5755,6 +6048,11 @@ bool UnitTestImpl::RunAllTests() { #if GTEST_HAS_DEATH_TEST in_subprocess_for_death_test = (internal_run_death_test_flag_.get() != NULL); +# if defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) + if (in_subprocess_for_death_test) { + GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_(); + } +# endif // defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) #endif // GTEST_HAS_DEATH_TEST const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex, @@ -6097,7 +6395,11 @@ void UnitTestImpl::set_os_stack_trace_getter( // getter, and returns it. OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() { if (os_stack_trace_getter_ == NULL) { +#ifdef GTEST_OS_STACK_TRACE_GETTER_ + os_stack_trace_getter_ = new GTEST_OS_STACK_TRACE_GETTER_; +#else os_stack_trace_getter_ = new OsStackTraceGetter; +#endif // GTEST_OS_STACK_TRACE_GETTER_ } return os_stack_trace_getter_; @@ -6402,6 +6704,59 @@ static const char kColorEncodedHelpMessage[] = "(not one in your own code or tests), please report it to\n" "@G<" GTEST_DEV_EMAIL_ ">@D.\n"; +static bool ParseGoogleTestFlag(const char* const arg) { + return ParseBoolFlag(arg, kAlsoRunDisabledTestsFlag, + >EST_FLAG(also_run_disabled_tests)) || + ParseBoolFlag(arg, kBreakOnFailureFlag, + >EST_FLAG(break_on_failure)) || + ParseBoolFlag(arg, kCatchExceptionsFlag, + >EST_FLAG(catch_exceptions)) || + ParseStringFlag(arg, kColorFlag, >EST_FLAG(color)) || + ParseStringFlag(arg, kDeathTestStyleFlag, + >EST_FLAG(death_test_style)) || + ParseBoolFlag(arg, kDeathTestUseFork, + >EST_FLAG(death_test_use_fork)) || + ParseStringFlag(arg, kFilterFlag, >EST_FLAG(filter)) || + ParseStringFlag(arg, kParamFilterFlag, >EST_FLAG(param_filter)) || + ParseStringFlag(arg, kInternalRunDeathTestFlag, + >EST_FLAG(internal_run_death_test)) || + ParseBoolFlag(arg, kListTestsFlag, >EST_FLAG(list_tests)) || + ParseStringFlag(arg, kOutputFlag, >EST_FLAG(output)) || + ParseBoolFlag(arg, kPrintTimeFlag, >EST_FLAG(print_time)) || + ParseInt32Flag(arg, kRandomSeedFlag, >EST_FLAG(random_seed)) || + ParseInt32Flag(arg, kRepeatFlag, >EST_FLAG(repeat)) || + ParseBoolFlag(arg, kShuffleFlag, >EST_FLAG(shuffle)) || + ParseInt32Flag(arg, kStackTraceDepthFlag, + >EST_FLAG(stack_trace_depth)) || + ParseStringFlag(arg, kStreamResultToFlag, + >EST_FLAG(stream_result_to)) || + ParseBoolFlag(arg, kThrowOnFailureFlag, + >EST_FLAG(throw_on_failure)); +} + +#if GTEST_USE_OWN_FLAGFILE_FLAG_ +static void LoadFlagsFromFile(const std::string& path) { + FILE* flagfile = posix::FOpen(path.c_str(), "r"); + if (!flagfile) { + fprintf(stderr, + "Unable to open file \"%s\"\n", + GTEST_FLAG(flagfile).c_str()); + fflush(stderr); + exit(EXIT_FAILURE); + } + std::string contents(ReadEntireFile(flagfile)); + posix::FClose(flagfile); + std::vector lines; + SplitString(contents, '\n', &lines); + for (size_t i = 0; i < lines.size(); ++i) { + if (lines[i].empty()) + continue; + if (!ParseGoogleTestFlag(lines[i].c_str())) + g_help_flag = true; + } +} +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ + // Parses the command line for Google Test flags, without initializing // other parts of Google Test. The type parameter CharType can be // instantiated to either char or wchar_t. @@ -6415,36 +6770,24 @@ void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) { using internal::ParseInt32Flag; using internal::ParseStringFlag; - // Do we see a Google Test flag? - if (ParseBoolFlag(arg, kAlsoRunDisabledTestsFlag, - >EST_FLAG(also_run_disabled_tests)) || - ParseBoolFlag(arg, kBreakOnFailureFlag, - >EST_FLAG(break_on_failure)) || - ParseBoolFlag(arg, kCatchExceptionsFlag, - >EST_FLAG(catch_exceptions)) || - ParseStringFlag(arg, kColorFlag, >EST_FLAG(color)) || - ParseStringFlag(arg, kDeathTestStyleFlag, - >EST_FLAG(death_test_style)) || - ParseBoolFlag(arg, kDeathTestUseFork, - >EST_FLAG(death_test_use_fork)) || - ParseStringFlag(arg, kFilterFlag, >EST_FLAG(filter)) || - ParseStringFlag(arg, kParamFilterFlag, >EST_FLAG(param_filter)) || - ParseStringFlag(arg, kInternalRunDeathTestFlag, - >EST_FLAG(internal_run_death_test)) || - ParseBoolFlag(arg, kListTestsFlag, >EST_FLAG(list_tests)) || - ParseStringFlag(arg, kOutputFlag, >EST_FLAG(output)) || - ParseBoolFlag(arg, kPrintTimeFlag, >EST_FLAG(print_time)) || - ParseInt32Flag(arg, kRandomSeedFlag, >EST_FLAG(random_seed)) || - ParseInt32Flag(arg, kRepeatFlag, >EST_FLAG(repeat)) || - ParseBoolFlag(arg, kShuffleFlag, >EST_FLAG(shuffle)) || - ParseInt32Flag(arg, kStackTraceDepthFlag, - >EST_FLAG(stack_trace_depth)) || - ParseStringFlag(arg, kStreamResultToFlag, - >EST_FLAG(stream_result_to)) || - ParseBoolFlag(arg, kThrowOnFailureFlag, - >EST_FLAG(throw_on_failure)) - ) { - // Yes. Shift the remainder of the argv list left by one. Note + bool remove_flag = false; + if (ParseGoogleTestFlag(arg)) { + remove_flag = true; +#if GTEST_USE_OWN_FLAGFILE_FLAG_ + } else if (ParseStringFlag(arg, kFlagfileFlag, >EST_FLAG(flagfile))) { + LoadFlagsFromFile(GTEST_FLAG(flagfile)); + remove_flag = true; +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ + } else if (arg_string == "--help" || arg_string == "-h" || + arg_string == "-?" || arg_string == "/?" || + HasGoogleTestFlagPrefix(arg)) { + // Both help flag and unrecognized Google Test flags (excluding + // internal ones) trigger help display. + g_help_flag = true; + } + + if (remove_flag) { + // Shift the remainder of the argv list left by one. Note // that argv has (*argc + 1) elements, the last one always being // NULL. The following loop moves the trailing NULL element as // well. @@ -6458,12 +6801,6 @@ void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) { // We also need to decrement the iterator as we just removed // an element. i--; - } else if (arg_string == "--help" || arg_string == "-h" || - arg_string == "-?" || arg_string == "/?" || - HasGoogleTestFlagPrefix(arg)) { - // Both help flag and unrecognized Google Test flags (excluding - // internal ones) trigger help display. - g_help_flag = true; } } @@ -6490,24 +6827,16 @@ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv) { // wchar_t. template void InitGoogleTestImpl(int* argc, CharType** argv) { - g_init_gtest_count++; - // We don't want to run the initialization code twice. - if (g_init_gtest_count != 1) return; + if (GTestIsInitialized()) return; if (*argc <= 0) return; - internal::g_executable_path = internal::StreamableToString(argv[0]); - -#if GTEST_HAS_DEATH_TEST - g_argvs.clear(); for (int i = 0; i != *argc; i++) { g_argvs.push_back(StreamableToString(argv[i])); } -#endif // GTEST_HAS_DEATH_TEST - ParseGoogleTestFlagsOnly(argc, argv); GetUnitTestImpl()->PostFlagParsingInit(); } @@ -6524,13 +6853,21 @@ void InitGoogleTestImpl(int* argc, CharType** argv) { // // Calling the function for the second time has no user-visible effect. void InitGoogleTest(int* argc, char** argv) { +#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv); +#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) internal::InitGoogleTestImpl(argc, argv); +#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) } // This overloaded version can be used in Windows programs compiled in // UNICODE mode. void InitGoogleTest(int* argc, wchar_t** argv) { +#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv); +#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) internal::InitGoogleTestImpl(argc, argv); +#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) } } // namespace testing @@ -6600,9 +6937,9 @@ void InitGoogleTest(int* argc, wchar_t** argv) { // Indicates that this translation unit is part of Google Test's // implementation. It must come before gtest-internal-inl.h is -// included, or there will be a compiler error. This trick is to -// prevent a user from accidentally including gtest-internal-inl.h in -// his code. +// included, or there will be a compiler error. This trick exists to +// prevent the accidental inclusion of gtest-internal-inl.h in the +// user's code. #define GTEST_IMPLEMENTATION_ 1 #undef GTEST_IMPLEMENTATION_ @@ -6702,6 +7039,14 @@ KilledBySignal::KilledBySignal(int signum) : signum_(signum) { // KilledBySignal function-call operator. bool KilledBySignal::operator()(int exit_status) const { +# if defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_) + { + bool result; + if (GTEST_KILLED_BY_SIGNAL_OVERRIDE_(signum_, exit_status, &result)) { + return result; + } + } +# endif // defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_) return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_; } # endif // !GTEST_OS_WINDOWS @@ -7110,7 +7455,6 @@ bool DeathTestImpl::Passed(bool status_ok) { } # if GTEST_OS_WINDOWS -#ifndef WINRT // WindowsDeathTest implements death tests on Windows. Due to the // specifics of starting new processes on Windows, death tests there are // always threadsafe, and Google Test considers the @@ -7302,7 +7646,6 @@ DeathTest::TestRole WindowsDeathTest::AssumeRole() { set_spawned(true); return OVERSEE_TEST; } -#endif # else // We are not on Windows. // ForkingDeathTest provides implementations for most of the abstract @@ -7410,6 +7753,11 @@ class ExecDeathTest : public ForkingDeathTest { static ::std::vector GetArgvsForDeathTestChildProcess() { ::std::vector args = GetInjectableArgvs(); +# if defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_) + ::std::vector extra_args = + GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_(); + args.insert(args.end(), extra_args.begin(), extra_args.end()); +# endif // defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_) return args; } // The name of the file in which the death test is located. @@ -7521,6 +7869,7 @@ void StackLowerThanAddress(const void* ptr, bool* result) { } #if GTEST_HAS_CLONE +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ static bool StackGrowsDown() { int dummy; bool result; @@ -7715,12 +8064,7 @@ bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex, # if GTEST_OS_WINDOWS if (GTEST_FLAG(death_test_style) == "threadsafe" || GTEST_FLAG(death_test_style) == "fast") { -#ifndef WINRT *test = new WindowsDeathTest(statement, regex, file, line); -#else - printf("DeathTest is not supported on winrt!\n"); - return false; -#endif } # else @@ -7743,28 +8087,7 @@ bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex, return true; } -// Splits a given string on a given delimiter, populating a given -// vector with the fields. GTEST_HAS_DEATH_TEST implies that we have -// ::std::string, so we can use it here. -static void SplitString(const ::std::string& str, char delimiter, - ::std::vector< ::std::string>* dest) { - ::std::vector< ::std::string> parsed; - ::std::string::size_type pos = 0; - while (::testing::internal::AlwaysTrue()) { - const ::std::string::size_type colon = str.find(delimiter, pos); - if (colon == ::std::string::npos) { - parsed.push_back(str.substr(pos)); - break; - } else { - parsed.push_back(str.substr(pos, colon - pos)); - pos = colon + 1; - } - } - dest->swap(parsed); -} - # if GTEST_OS_WINDOWS -#ifndef WINRT // Recreates the pipe and event handles from the provided parameters, // signals the event, and returns a file descriptor wrapped around the pipe // handle. This function is called in the child process only. @@ -7830,7 +8153,6 @@ int GetStatusFileDescriptor(unsigned int parent_process_id, return write_fd; } -#endif # endif // GTEST_OS_WINDOWS // Returns a newly created InternalRunDeathTestFlag object with fields @@ -7848,7 +8170,6 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { int write_fd = -1; # if GTEST_OS_WINDOWS -#ifndef WINRT unsigned int parent_process_id = 0; size_t write_handle_as_size_t = 0; size_t event_handle_as_size_t = 0; @@ -7865,7 +8186,6 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { write_fd = GetStatusFileDescriptor(parent_process_id, write_handle_as_size_t, event_handle_as_size_t); -#endif # else if (fields.size() != 4 @@ -7983,7 +8303,7 @@ static bool IsPathSeparator(char c) { // Returns the current working directory, or "" if unsuccessful. FilePath FilePath::GetCurrentDir() { -#if GTEST_OS_WINDOWS_MOBILE || WINRT +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT // Windows CE doesn't have a current directory, so we just return // something reasonable. return FilePath(kCurrentDirectoryString); @@ -7992,7 +8312,14 @@ FilePath FilePath::GetCurrentDir() { return FilePath(_getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); #else char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; - return FilePath(getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); + char* result = getcwd(cwd, sizeof(cwd)); +# if GTEST_OS_NACL + // getcwd will likely fail in NaCl due to the sandbox, so return something + // reasonable. The user may have provided a shim implementation for getcwd, + // however, so fallback only when failure is detected. + return FilePath(result == NULL ? kCurrentDirectoryString : cwd); +# endif // GTEST_OS_NACL + return FilePath(result == NULL ? "" : cwd); #endif // GTEST_OS_WINDOWS_MOBILE } @@ -8300,15 +8627,16 @@ void FilePath::Normalize() { #include #include #include +#include -#if GTEST_OS_WINDOWS_MOBILE -# include // For TerminateProcess() -#elif GTEST_OS_WINDOWS +#if GTEST_OS_WINDOWS +# include # include # include +# include // Used in ThreadLocal. #else # include -#endif // GTEST_OS_WINDOWS_MOBILE +#endif // GTEST_OS_WINDOWS #if GTEST_OS_MAC # include @@ -8318,15 +8646,21 @@ void FilePath::Normalize() { #if GTEST_OS_QNX # include +# include # include #endif // GTEST_OS_QNX +#if GTEST_OS_AIX +# include +# include +#endif // GTEST_OS_AIX + // Indicates that this translation unit is part of Google Test's // implementation. It must come before gtest-internal-inl.h is -// included, or there will be a compiler error. This trick is to -// prevent a user from accidentally including gtest-internal-inl.h in -// his code. +// included, or there will be a compiler error. This trick exists to +// prevent the accidental inclusion of gtest-internal-inl.h in the +// user's code. #define GTEST_IMPLEMENTATION_ 1 #undef GTEST_IMPLEMENTATION_ @@ -8342,10 +8676,31 @@ const int kStdOutFileno = STDOUT_FILENO; const int kStdErrFileno = STDERR_FILENO; #endif // _MSC_VER -#if GTEST_OS_MAC +#if GTEST_OS_LINUX + +namespace { +template +T ReadProcFileField(const string& filename, int field) { + std::string dummy; + std::ifstream file(filename.c_str()); + while (field-- > 0) { + file >> dummy; + } + T output = 0; + file >> output; + return output; +} +} // namespace + +// Returns the number of active threads, or 0 when there is an error. +size_t GetThreadCount() { + const string filename = + (Message() << "/proc/" << getpid() << "/stat").GetString(); + return ReadProcFileField(filename, 19); +} + +#elif GTEST_OS_MAC -// Returns the number of threads running in the process, or 0 to indicate that -// we cannot detect it. size_t GetThreadCount() { const task_t task = mach_task_self(); mach_msg_type_number_t thread_count; @@ -8383,6 +8738,19 @@ size_t GetThreadCount() { } } +#elif GTEST_OS_AIX + +size_t GetThreadCount() { + struct procentry64 entry; + pid_t pid = getpid(); + int status = getprocs64(&entry, sizeof(entry), NULL, 0, &pid, 1); + if (status == 1) { + return entry.pi_thcount; + } else { + return 0; + } +} + #else size_t GetThreadCount() { @@ -8391,7 +8759,390 @@ size_t GetThreadCount() { return 0; } -#endif // GTEST_OS_MAC +#endif // GTEST_OS_LINUX + +#if GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS + +void SleepMilliseconds(int n) { + ::Sleep(n); +} + +AutoHandle::AutoHandle() + : handle_(INVALID_HANDLE_VALUE) {} + +AutoHandle::AutoHandle(Handle handle) + : handle_(handle) {} + +AutoHandle::~AutoHandle() { + Reset(); +} + +AutoHandle::Handle AutoHandle::Get() const { + return handle_; +} + +void AutoHandle::Reset() { + Reset(INVALID_HANDLE_VALUE); +} + +void AutoHandle::Reset(HANDLE handle) { + // Resetting with the same handle we already own is invalid. + if (handle_ != handle) { + if (IsCloseable()) { + ::CloseHandle(handle_); + } + handle_ = handle; + } else { + GTEST_CHECK_(!IsCloseable()) + << "Resetting a valid handle to itself is likely a programmer error " + "and thus not allowed."; + } +} + +bool AutoHandle::IsCloseable() const { + // Different Windows APIs may use either of these values to represent an + // invalid handle. + return handle_ != NULL && handle_ != INVALID_HANDLE_VALUE; +} + +Notification::Notification() + : event_(::CreateEvent(NULL, // Default security attributes. + TRUE, // Do not reset automatically. + FALSE, // Initially unset. + NULL)) { // Anonymous event. + GTEST_CHECK_(event_.Get() != NULL); +} + +void Notification::Notify() { + GTEST_CHECK_(::SetEvent(event_.Get()) != FALSE); +} + +void Notification::WaitForNotification() { + GTEST_CHECK_( + ::WaitForSingleObject(event_.Get(), INFINITE) == WAIT_OBJECT_0); +} + +Mutex::Mutex() + : owner_thread_id_(0), + type_(kDynamic), + critical_section_init_phase_(0), + critical_section_(new CRITICAL_SECTION) { + ::InitializeCriticalSection(critical_section_); +} + +Mutex::~Mutex() { + // Static mutexes are leaked intentionally. It is not thread-safe to try + // to clean them up. + // TODO(yukawa): Switch to Slim Reader/Writer (SRW) Locks, which requires + // nothing to clean it up but is available only on Vista and later. + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa904937.aspx + if (type_ == kDynamic) { + ::DeleteCriticalSection(critical_section_); + delete critical_section_; + critical_section_ = NULL; + } +} + +void Mutex::Lock() { + ThreadSafeLazyInit(); + ::EnterCriticalSection(critical_section_); + owner_thread_id_ = ::GetCurrentThreadId(); +} + +void Mutex::Unlock() { + ThreadSafeLazyInit(); + // We don't protect writing to owner_thread_id_ here, as it's the + // caller's responsibility to ensure that the current thread holds the + // mutex when this is called. + owner_thread_id_ = 0; + ::LeaveCriticalSection(critical_section_); +} + +// Does nothing if the current thread holds the mutex. Otherwise, crashes +// with high probability. +void Mutex::AssertHeld() { + ThreadSafeLazyInit(); + GTEST_CHECK_(owner_thread_id_ == ::GetCurrentThreadId()) + << "The current thread is not holding the mutex @" << this; +} + +// Initializes owner_thread_id_ and critical_section_ in static mutexes. +void Mutex::ThreadSafeLazyInit() { + // Dynamic mutexes are initialized in the constructor. + if (type_ == kStatic) { + switch ( + ::InterlockedCompareExchange(&critical_section_init_phase_, 1L, 0L)) { + case 0: + // If critical_section_init_phase_ was 0 before the exchange, we + // are the first to test it and need to perform the initialization. + owner_thread_id_ = 0; + critical_section_ = new CRITICAL_SECTION; + ::InitializeCriticalSection(critical_section_); + // Updates the critical_section_init_phase_ to 2 to signal + // initialization complete. + GTEST_CHECK_(::InterlockedCompareExchange( + &critical_section_init_phase_, 2L, 1L) == + 1L); + break; + case 1: + // Somebody else is already initializing the mutex; spin until they + // are done. + while (::InterlockedCompareExchange(&critical_section_init_phase_, + 2L, + 2L) != 2L) { + // Possibly yields the rest of the thread's time slice to other + // threads. + ::Sleep(0); + } + break; + + case 2: + break; // The mutex is already initialized and ready for use. + + default: + GTEST_CHECK_(false) + << "Unexpected value of critical_section_init_phase_ " + << "while initializing a static mutex."; + } + } +} + +namespace { + +class ThreadWithParamSupport : public ThreadWithParamBase { + public: + static HANDLE CreateThread(Runnable* runnable, + Notification* thread_can_start) { + ThreadMainParam* param = new ThreadMainParam(runnable, thread_can_start); + DWORD thread_id; + // TODO(yukawa): Consider to use _beginthreadex instead. + HANDLE thread_handle = ::CreateThread( + NULL, // Default security. + 0, // Default stack size. + &ThreadWithParamSupport::ThreadMain, + param, // Parameter to ThreadMainStatic + 0x0, // Default creation flags. + &thread_id); // Need a valid pointer for the call to work under Win98. + GTEST_CHECK_(thread_handle != NULL) << "CreateThread failed with error " + << ::GetLastError() << "."; + if (thread_handle == NULL) { + delete param; + } + return thread_handle; + } + + private: + struct ThreadMainParam { + ThreadMainParam(Runnable* runnable, Notification* thread_can_start) + : runnable_(runnable), + thread_can_start_(thread_can_start) { + } + scoped_ptr runnable_; + // Does not own. + Notification* thread_can_start_; + }; + + static DWORD WINAPI ThreadMain(void* ptr) { + // Transfers ownership. + scoped_ptr param(static_cast(ptr)); + if (param->thread_can_start_ != NULL) + param->thread_can_start_->WaitForNotification(); + param->runnable_->Run(); + return 0; + } + + // Prohibit instantiation. + ThreadWithParamSupport(); + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParamSupport); +}; + +} // namespace + +ThreadWithParamBase::ThreadWithParamBase(Runnable *runnable, + Notification* thread_can_start) + : thread_(ThreadWithParamSupport::CreateThread(runnable, + thread_can_start)) { +} + +ThreadWithParamBase::~ThreadWithParamBase() { + Join(); +} + +void ThreadWithParamBase::Join() { + GTEST_CHECK_(::WaitForSingleObject(thread_.Get(), INFINITE) == WAIT_OBJECT_0) + << "Failed to join the thread with error " << ::GetLastError() << "."; +} + +// Maps a thread to a set of ThreadIdToThreadLocals that have values +// instantiated on that thread and notifies them when the thread exits. A +// ThreadLocal instance is expected to persist until all threads it has +// values on have terminated. +class ThreadLocalRegistryImpl { + public: + // Registers thread_local_instance as having value on the current thread. + // Returns a value that can be used to identify the thread from other threads. + static ThreadLocalValueHolderBase* GetValueOnCurrentThread( + const ThreadLocalBase* thread_local_instance) { + DWORD current_thread = ::GetCurrentThreadId(); + MutexLock lock(&mutex_); + ThreadIdToThreadLocals* const thread_to_thread_locals = + GetThreadLocalsMapLocked(); + ThreadIdToThreadLocals::iterator thread_local_pos = + thread_to_thread_locals->find(current_thread); + if (thread_local_pos == thread_to_thread_locals->end()) { + thread_local_pos = thread_to_thread_locals->insert( + std::make_pair(current_thread, ThreadLocalValues())).first; + StartWatcherThreadFor(current_thread); + } + ThreadLocalValues& thread_local_values = thread_local_pos->second; + ThreadLocalValues::iterator value_pos = + thread_local_values.find(thread_local_instance); + if (value_pos == thread_local_values.end()) { + value_pos = + thread_local_values + .insert(std::make_pair( + thread_local_instance, + linked_ptr( + thread_local_instance->NewValueForCurrentThread()))) + .first; + } + return value_pos->second.get(); + } + + static void OnThreadLocalDestroyed( + const ThreadLocalBase* thread_local_instance) { + std::vector > value_holders; + // Clean up the ThreadLocalValues data structure while holding the lock, but + // defer the destruction of the ThreadLocalValueHolderBases. + { + MutexLock lock(&mutex_); + ThreadIdToThreadLocals* const thread_to_thread_locals = + GetThreadLocalsMapLocked(); + for (ThreadIdToThreadLocals::iterator it = + thread_to_thread_locals->begin(); + it != thread_to_thread_locals->end(); + ++it) { + ThreadLocalValues& thread_local_values = it->second; + ThreadLocalValues::iterator value_pos = + thread_local_values.find(thread_local_instance); + if (value_pos != thread_local_values.end()) { + value_holders.push_back(value_pos->second); + thread_local_values.erase(value_pos); + // This 'if' can only be successful at most once, so theoretically we + // could break out of the loop here, but we don't bother doing so. + } + } + } + // Outside the lock, let the destructor for 'value_holders' deallocate the + // ThreadLocalValueHolderBases. + } + + static void OnThreadExit(DWORD thread_id) { + GTEST_CHECK_(thread_id != 0) << ::GetLastError(); + std::vector > value_holders; + // Clean up the ThreadIdToThreadLocals data structure while holding the + // lock, but defer the destruction of the ThreadLocalValueHolderBases. + { + MutexLock lock(&mutex_); + ThreadIdToThreadLocals* const thread_to_thread_locals = + GetThreadLocalsMapLocked(); + ThreadIdToThreadLocals::iterator thread_local_pos = + thread_to_thread_locals->find(thread_id); + if (thread_local_pos != thread_to_thread_locals->end()) { + ThreadLocalValues& thread_local_values = thread_local_pos->second; + for (ThreadLocalValues::iterator value_pos = + thread_local_values.begin(); + value_pos != thread_local_values.end(); + ++value_pos) { + value_holders.push_back(value_pos->second); + } + thread_to_thread_locals->erase(thread_local_pos); + } + } + // Outside the lock, let the destructor for 'value_holders' deallocate the + // ThreadLocalValueHolderBases. + } + + private: + // In a particular thread, maps a ThreadLocal object to its value. + typedef std::map > ThreadLocalValues; + // Stores all ThreadIdToThreadLocals having values in a thread, indexed by + // thread's ID. + typedef std::map ThreadIdToThreadLocals; + + // Holds the thread id and thread handle that we pass from + // StartWatcherThreadFor to WatcherThreadFunc. + typedef std::pair ThreadIdAndHandle; + + static void StartWatcherThreadFor(DWORD thread_id) { + // The returned handle will be kept in thread_map and closed by + // watcher_thread in WatcherThreadFunc. + HANDLE thread = ::OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION, + FALSE, + thread_id); + GTEST_CHECK_(thread != NULL); + // We need to to pass a valid thread ID pointer into CreateThread for it + // to work correctly under Win98. + DWORD watcher_thread_id; + HANDLE watcher_thread = ::CreateThread( + NULL, // Default security. + 0, // Default stack size + &ThreadLocalRegistryImpl::WatcherThreadFunc, + reinterpret_cast(new ThreadIdAndHandle(thread_id, thread)), + CREATE_SUSPENDED, + &watcher_thread_id); + GTEST_CHECK_(watcher_thread != NULL); + // Give the watcher thread the same priority as ours to avoid being + // blocked by it. + ::SetThreadPriority(watcher_thread, + ::GetThreadPriority(::GetCurrentThread())); + ::ResumeThread(watcher_thread); + ::CloseHandle(watcher_thread); + } + + // Monitors exit from a given thread and notifies those + // ThreadIdToThreadLocals about thread termination. + static DWORD WINAPI WatcherThreadFunc(LPVOID param) { + const ThreadIdAndHandle* tah = + reinterpret_cast(param); + GTEST_CHECK_( + ::WaitForSingleObject(tah->second, INFINITE) == WAIT_OBJECT_0); + OnThreadExit(tah->first); + ::CloseHandle(tah->second); + delete tah; + return 0; + } + + // Returns map of thread local instances. + static ThreadIdToThreadLocals* GetThreadLocalsMapLocked() { + mutex_.AssertHeld(); + static ThreadIdToThreadLocals* map = new ThreadIdToThreadLocals; + return map; + } + + // Protects access to GetThreadLocalsMapLocked() and its return value. + static Mutex mutex_; + // Protects access to GetThreadMapLocked() and its return value. + static Mutex thread_map_mutex_; +}; + +Mutex ThreadLocalRegistryImpl::mutex_(Mutex::kStaticMutex); +Mutex ThreadLocalRegistryImpl::thread_map_mutex_(Mutex::kStaticMutex); + +ThreadLocalValueHolderBase* ThreadLocalRegistry::GetValueOnCurrentThread( + const ThreadLocalBase* thread_local_instance) { + return ThreadLocalRegistryImpl::GetValueOnCurrentThread( + thread_local_instance); +} + +void ThreadLocalRegistry::OnThreadLocalDestroyed( + const ThreadLocalBase* thread_local_instance) { + ThreadLocalRegistryImpl::OnThreadLocalDestroyed(thread_local_instance); +} + +#endif // GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS #if GTEST_USES_POSIX_RE @@ -8741,7 +9492,6 @@ GTEST_API_ ::std::string FormatCompilerIndependentFileLocation( return file_name + ":" + StreamableToString(line); } - GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) : severity_(severity) { const char* const marker = @@ -8762,10 +9512,7 @@ GTestLog::~GTestLog() { } // Disable Microsoft deprecation warnings for POSIX functions called from // this class (creat, dup, dup2, and close) -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4996) -#endif // _MSC_VER +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4996) #if GTEST_HAS_STREAM_REDIRECTION @@ -8774,7 +9521,6 @@ class CapturedStream { public: // The ctor redirects the stream to a temporary file. explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { -#ifndef WINRT # if GTEST_OS_WINDOWS char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT @@ -8820,7 +9566,6 @@ class CapturedStream { fflush(NULL); dup2(captured_fd, fd_); close(captured_fd); -#endif } ~CapturedStream() { @@ -8843,12 +9588,6 @@ class CapturedStream { } private: - // Reads the entire content of a file as an std::string. - static std::string ReadEntireFile(FILE* file); - - // Returns the size (in bytes) of a file. - static size_t GetFileSize(FILE* file); - const int fd_; // A stream to capture. int uncaptured_fd_; // Name of the temporary file holding the stderr output. @@ -8857,38 +9596,7 @@ class CapturedStream { GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream); }; -// Returns the size (in bytes) of a file. -size_t CapturedStream::GetFileSize(FILE* file) { - fseek(file, 0, SEEK_END); - return static_cast(ftell(file)); -} - -// Reads the entire content of a file as a string. -std::string CapturedStream::ReadEntireFile(FILE* file) { - const size_t file_size = GetFileSize(file); - char* const buffer = new char[file_size]; - - size_t bytes_last_read = 0; // # of bytes read in the last fread() - size_t bytes_read = 0; // # of bytes read so far - - fseek(file, 0, SEEK_SET); - - // Keeps reading the file until we cannot read further or the - // pre-determined file size is reached. - do { - bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file); - bytes_read += bytes_last_read; - } while (bytes_last_read > 0 && bytes_read < file_size); - - const std::string content(buffer, bytes_read); - delete[] buffer; - - return content; -} - -# ifdef _MSC_VER -# pragma warning(pop) -# endif // _MSC_VER +GTEST_DISABLE_MSC_WARNINGS_POP_() static CapturedStream* g_captured_stderr = NULL; static CapturedStream* g_captured_stdout = NULL; @@ -8934,10 +9642,52 @@ std::string GetCapturedStderr() { #endif // GTEST_HAS_STREAM_REDIRECTION -#if GTEST_HAS_DEATH_TEST +std::string TempDir() { +#if GTEST_OS_WINDOWS_MOBILE + return "\\temp\\"; +#elif GTEST_OS_WINDOWS + const char* temp_dir = posix::GetEnv("TEMP"); + if (temp_dir == NULL || temp_dir[0] == '\0') + return "\\temp\\"; + else if (temp_dir[strlen(temp_dir) - 1] == '\\') + return temp_dir; + else + return std::string(temp_dir) + "\\"; +#elif GTEST_OS_LINUX_ANDROID + return "/sdcard/"; +#else + return "/tmp/"; +#endif // GTEST_OS_WINDOWS_MOBILE +} -// A copy of all command line arguments. Set by InitGoogleTest(). -::std::vector g_argvs; +size_t GetFileSize(FILE* file) { + fseek(file, 0, SEEK_END); + return static_cast(ftell(file)); +} + +std::string ReadEntireFile(FILE* file) { + const size_t file_size = GetFileSize(file); + char* const buffer = new char[file_size]; + + size_t bytes_last_read = 0; // # of bytes read in the last fread() + size_t bytes_read = 0; // # of bytes read so far + + fseek(file, 0, SEEK_SET); + + // Keeps reading the file until we cannot read further or the + // pre-determined file size is reached. + do { + bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file); + bytes_read += bytes_last_read; + } while (bytes_last_read > 0 && bytes_read < file_size); + + const std::string content(buffer, bytes_read); + delete[] buffer; + + return content; +} + +#if GTEST_HAS_DEATH_TEST static const ::std::vector* g_injected_test_argvs = NULL; // Owned. @@ -8952,7 +9702,7 @@ const ::std::vector& GetInjectableArgvs() { if (g_injected_test_argvs != NULL) { return *g_injected_test_argvs; } - return g_argvs; + return GetArgvs(); } #endif // GTEST_HAS_DEATH_TEST @@ -9026,6 +9776,9 @@ bool ParseInt32(const Message& src_text, const char* str, Int32* value) { // // The value is considered true iff it's not "0". bool BoolFromGTestEnv(const char* flag, bool default_value) { +#if defined(GTEST_GET_BOOL_FROM_ENV_) + return GTEST_GET_BOOL_FROM_ENV_(flag, default_value); +#endif // defined(GTEST_GET_BOOL_FROM_ENV_) const std::string env_var = FlagToEnvVar(flag); const char* const string_value = posix::GetEnv(env_var.c_str()); return string_value == NULL ? @@ -9036,6 +9789,9 @@ bool BoolFromGTestEnv(const char* flag, bool default_value) { // variable corresponding to the given flag; if it isn't set or // doesn't represent a valid 32-bit integer, returns default_value. Int32 Int32FromGTestEnv(const char* flag, Int32 default_value) { +#if defined(GTEST_GET_INT32_FROM_ENV_) + return GTEST_GET_INT32_FROM_ENV_(flag, default_value); +#endif // defined(GTEST_GET_INT32_FROM_ENV_) const std::string env_var = FlagToEnvVar(flag); const char* const string_value = posix::GetEnv(env_var.c_str()); if (string_value == NULL) { @@ -9057,10 +9813,33 @@ Int32 Int32FromGTestEnv(const char* flag, Int32 default_value) { // Reads and returns the string environment variable corresponding to // the given flag; if it's not set, returns default_value. -const char* StringFromGTestEnv(const char* flag, const char* default_value) { +std::string StringFromGTestEnv(const char* flag, const char* default_value) { +#if defined(GTEST_GET_STRING_FROM_ENV_) + return GTEST_GET_STRING_FROM_ENV_(flag, default_value); +#endif // defined(GTEST_GET_STRING_FROM_ENV_) const std::string env_var = FlagToEnvVar(flag); - const char* const value = posix::GetEnv(env_var.c_str()); - return value == NULL ? default_value : value; + const char* value = posix::GetEnv(env_var.c_str()); + if (value != NULL) { + return value; + } + + // As a special case for the 'output' flag, if GTEST_OUTPUT is not + // set, we look for XML_OUTPUT_FILE, which is set by the Bazel build + // system. The value of XML_OUTPUT_FILE is a filename without the + // "xml:" prefix of GTEST_OUTPUT. + // + // The net priority order after flag processing is thus: + // --gtest_output command line flag + // GTEST_OUTPUT environment variable + // XML_OUTPUT_FILE environment variable + // 'default_value' + if (strcmp(flag, "output") == 0) { + value = posix::GetEnv("XML_OUTPUT_FILE"); + if (value != NULL) { + return std::string("xml:") + value; + } + } + return default_value; } } // namespace internal @@ -9111,6 +9890,7 @@ const char* StringFromGTestEnv(const char* flag, const char* default_value) { #include #include +#include #include // NOLINT #include @@ -9121,6 +9901,9 @@ namespace { using ::std::ostream; // Prints a segment of bytes in the given object. +GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start, size_t count, ostream* os) { char text[5] = ""; @@ -9317,6 +10100,9 @@ void PrintTo(wchar_t wc, ostream* os) { // The array starts at begin, the length is len, it may include '\0' characters // and may not be NUL-terminated. template +GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ static void PrintCharsAsStringTo( const CharType* begin, size_t len, ostream* os) { const char* const kQuoteBegin = sizeof(CharType) == 1 ? "\"" : "L\""; @@ -9338,6 +10124,9 @@ static void PrintCharsAsStringTo( // Prints a (const) char/wchar_t array of 'len' elements, starting at address // 'begin'. CharType must be either char or wchar_t. template +GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ static void UniversalPrintCharArray( const CharType* begin, size_t len, ostream* os) { // The code @@ -9394,7 +10183,7 @@ void PrintTo(const wchar_t* s, ostream* os) { *os << "NULL"; } else { *os << ImplicitCast_(s) << " pointing to "; - PrintCharsAsStringTo(s, wcslen(s), os); + PrintCharsAsStringTo(s, std::wcslen(s), os); } } #endif // wchar_t is native @@ -9462,9 +10251,9 @@ void PrintWideStringTo(const ::std::wstring& s, ostream* os) { // Indicates that this translation unit is part of Google Test's // implementation. It must come before gtest-internal-inl.h is -// included, or there will be a compiler error. This trick is to -// prevent a user from accidentally including gtest-internal-inl.h in -// his code. +// included, or there will be a compiler error. This trick exists to +// prevent the accidental inclusion of gtest-internal-inl.h in the +// user's code. #define GTEST_IMPLEMENTATION_ 1 #undef GTEST_IMPLEMENTATION_ @@ -9579,33 +10368,41 @@ static const char* SkipSpaces(const char* str) { return str; } +static std::vector SplitIntoTestNames(const char* src) { + std::vector name_vec; + src = SkipSpaces(src); + for (; src != NULL; src = SkipComma(src)) { + name_vec.push_back(StripTrailingSpaces(GetPrefixUntilComma(src))); + } + return name_vec; +} + // Verifies that registered_tests match the test names in -// defined_test_names_; returns registered_tests if successful, or +// registered_tests_; returns registered_tests if successful, or // aborts the program otherwise. const char* TypedTestCasePState::VerifyRegisteredTestNames( const char* file, int line, const char* registered_tests) { - typedef ::std::set::const_iterator DefinedTestIter; + typedef RegisteredTestsMap::const_iterator RegisteredTestIter; registered_ = true; - // Skip initial whitespace in registered_tests since some - // preprocessors prefix stringizied literals with whitespace. - registered_tests = SkipSpaces(registered_tests); + std::vector name_vec = SplitIntoTestNames(registered_tests); Message errors; - ::std::set tests; - for (const char* names = registered_tests; names != NULL; - names = SkipComma(names)) { - const std::string name = GetPrefixUntilComma(names); + + std::set tests; + for (std::vector::const_iterator name_it = name_vec.begin(); + name_it != name_vec.end(); ++name_it) { + const std::string& name = *name_it; if (tests.count(name) != 0) { errors << "Test " << name << " is listed more than once.\n"; continue; } bool found = false; - for (DefinedTestIter it = defined_test_names_.begin(); - it != defined_test_names_.end(); + for (RegisteredTestIter it = registered_tests_.begin(); + it != registered_tests_.end(); ++it) { - if (name == *it) { + if (name == it->first) { found = true; break; } @@ -9619,11 +10416,11 @@ const char* TypedTestCasePState::VerifyRegisteredTestNames( } } - for (DefinedTestIter it = defined_test_names_.begin(); - it != defined_test_names_.end(); + for (RegisteredTestIter it = registered_tests_.begin(); + it != registered_tests_.end(); ++it) { - if (tests.count(*it) == 0) { - errors << "You forgot to list test " << *it << ".\n"; + if (tests.count(it->first) == 0) { + errors << "You forgot to list test " << it->first << ".\n"; } } From 4a7d441b37fb6cff17faf5e29b40de67a56e6bb9 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 28 Nov 2016 23:56:58 +0300 Subject: [PATCH 133/409] test: fix cuda build --- modules/cudaarithm/perf/perf_arithm.cpp | 4 ++++ modules/cudaarithm/test/test_core.cpp | 4 ++++ modules/cudafeatures2d/test/test_features2d.cpp | 4 ++++ modules/cudafilters/test/test_filters.cpp | 4 ++++ modules/cudaimgproc/test/test_canny.cpp | 4 ++++ modules/cudaimgproc/test/test_histogram.cpp | 4 ++++ modules/cudaimgproc/test/test_hough.cpp | 4 ++++ modules/cudaobjdetect/test/test_objdetect.cpp | 4 ++++ modules/cudaoptflow/test/test_optflow.cpp | 6 +++++- modules/cudastereo/test/test_stereo.cpp | 4 ++++ 10 files changed, 41 insertions(+), 1 deletion(-) diff --git a/modules/cudaarithm/perf/perf_arithm.cpp b/modules/cudaarithm/perf/perf_arithm.cpp index c58397bf36..02cff85aae 100644 --- a/modules/cudaarithm/perf/perf_arithm.cpp +++ b/modules/cudaarithm/perf/perf_arithm.cpp @@ -46,6 +46,8 @@ using namespace std; using namespace testing; using namespace perf; +namespace { // workaround conflict with DftFlags + ////////////////////////////////////////////////////////////////////// // GEMM @@ -252,3 +254,5 @@ PERF_TEST_P(Sz_KernelSz_Ccorr, Convolve, CPU_SANITY_CHECK(dst); } } + +} // namespace diff --git a/modules/cudaarithm/test/test_core.cpp b/modules/cudaarithm/test/test_core.cpp index f67854ecd4..cd918ba8aa 100644 --- a/modules/cudaarithm/test/test_core.cpp +++ b/modules/cudaarithm/test/test_core.cpp @@ -46,6 +46,8 @@ using namespace cvtest; +namespace { + //////////////////////////////////////////////////////////////////////////////// // Merge @@ -416,4 +418,6 @@ INSTANTIATE_TEST_CASE_P(CUDA_Arithm, CopyMakeBorder, testing::Combine( ALL_BORDER_TYPES, WHOLE_SUBMAT)); +} //namespace + #endif // HAVE_CUDA diff --git a/modules/cudafeatures2d/test/test_features2d.cpp b/modules/cudafeatures2d/test/test_features2d.cpp index 3046a604b3..7fccd9ea50 100644 --- a/modules/cudafeatures2d/test/test_features2d.cpp +++ b/modules/cudafeatures2d/test/test_features2d.cpp @@ -55,6 +55,8 @@ namespace IMPLEMENT_PARAM_CLASS(FAST_NonmaxSuppression, bool) } +namespace { + PARAM_TEST_CASE(FAST, cv::cuda::DeviceInfo, FAST_Threshold, FAST_NonmaxSuppression) { cv::cuda::DeviceInfo devInfo; @@ -708,4 +710,6 @@ INSTANTIATE_TEST_CASE_P(CUDA_Features2D, BruteForceMatcher, testing::Combine( testing::Values(DescriptorSize(57), DescriptorSize(64), DescriptorSize(83), DescriptorSize(128), DescriptorSize(179), DescriptorSize(256), DescriptorSize(304)), testing::Values(UseMask(false), UseMask(true)))); +} // namespace + #endif // HAVE_CUDA diff --git a/modules/cudafilters/test/test_filters.cpp b/modules/cudafilters/test/test_filters.cpp index 332daf2c0c..74bc3b422d 100644 --- a/modules/cudafilters/test/test_filters.cpp +++ b/modules/cudafilters/test/test_filters.cpp @@ -68,6 +68,8 @@ namespace } } +namespace { + ///////////////////////////////////////////////////////////////////////////////////////////////// // Blur @@ -708,4 +710,6 @@ INSTANTIATE_TEST_CASE_P(CUDA_Filters, Median, testing::Combine( WHOLE_SUBMAT) ); +} //namespace + #endif // HAVE_CUDA diff --git a/modules/cudaimgproc/test/test_canny.cpp b/modules/cudaimgproc/test/test_canny.cpp index adb5c0d7ef..e9baae214b 100644 --- a/modules/cudaimgproc/test/test_canny.cpp +++ b/modules/cudaimgproc/test/test_canny.cpp @@ -55,6 +55,8 @@ namespace IMPLEMENT_PARAM_CLASS(L2gradient, bool) } +namespace { + PARAM_TEST_CASE(Canny, cv::cuda::DeviceInfo, AppertureSize, L2gradient, UseRoi) { cv::cuda::DeviceInfo devInfo; @@ -98,4 +100,6 @@ INSTANTIATE_TEST_CASE_P(CUDA_ImgProc, Canny, testing::Combine( testing::Values(L2gradient(false), L2gradient(true)), WHOLE_SUBMAT)); +} // namespace + #endif // HAVE_CUDA diff --git a/modules/cudaimgproc/test/test_histogram.cpp b/modules/cudaimgproc/test/test_histogram.cpp index 5ff59630b6..3d3217375e 100644 --- a/modules/cudaimgproc/test/test_histogram.cpp +++ b/modules/cudaimgproc/test/test_histogram.cpp @@ -46,6 +46,8 @@ using namespace cvtest; +namespace { + /////////////////////////////////////////////////////////////////////////////////////////////////////// // HistEven @@ -212,4 +214,6 @@ INSTANTIATE_TEST_CASE_P(CUDA_ImgProc, CLAHE, testing::Combine( DIFFERENT_SIZES, testing::Values(0.0, 40.0))); +} // namespace + #endif // HAVE_CUDA diff --git a/modules/cudaimgproc/test/test_hough.cpp b/modules/cudaimgproc/test/test_hough.cpp index 5e55abe1bc..fd6952193e 100644 --- a/modules/cudaimgproc/test/test_hough.cpp +++ b/modules/cudaimgproc/test/test_hough.cpp @@ -46,6 +46,8 @@ using namespace cvtest; +namespace { + /////////////////////////////////////////////////////////////////////////////////////////////////////// // HoughLines @@ -256,4 +258,6 @@ INSTANTIATE_TEST_CASE_P(CUDA_ImgProc, GeneralizedHough, testing::Combine( ALL_DEVICES, WHOLE_SUBMAT)); +} // namespace + #endif // HAVE_CUDA diff --git a/modules/cudaobjdetect/test/test_objdetect.cpp b/modules/cudaobjdetect/test/test_objdetect.cpp index 25c3efddde..fcb566e9c1 100644 --- a/modules/cudaobjdetect/test/test_objdetect.cpp +++ b/modules/cudaobjdetect/test/test_objdetect.cpp @@ -46,6 +46,8 @@ using namespace cvtest; +namespace { + //#define DUMP struct HOG : testing::TestWithParam @@ -558,4 +560,6 @@ CUDA_TEST_P(LBP_classify, Accuracy) INSTANTIATE_TEST_CASE_P(CUDA_ObjDetect, LBP_classify, testing::Combine(ALL_DEVICES, testing::Values(0))); +} // namespace + #endif // HAVE_CUDA diff --git a/modules/cudaoptflow/test/test_optflow.cpp b/modules/cudaoptflow/test/test_optflow.cpp index 9a3e3e57f6..fe910d2d82 100644 --- a/modules/cudaoptflow/test/test_optflow.cpp +++ b/modules/cudaoptflow/test/test_optflow.cpp @@ -46,6 +46,8 @@ using namespace cvtest; +namespace { + ////////////////////////////////////////////////////// // BroxOpticalFlow @@ -401,4 +403,6 @@ INSTANTIATE_TEST_CASE_P(CUDA_OptFlow, OpticalFlowDual_TVL1, testing::Combine( ALL_DEVICES, testing::Values(Gamma(0.0), Gamma(1.0)))); -#endif // HAVE_CUDA \ No newline at end of file +} // namespace + +#endif // HAVE_CUDA diff --git a/modules/cudastereo/test/test_stereo.cpp b/modules/cudastereo/test/test_stereo.cpp index bc9aa37bb4..de801cf5a3 100644 --- a/modules/cudastereo/test/test_stereo.cpp +++ b/modules/cudastereo/test/test_stereo.cpp @@ -46,6 +46,8 @@ using namespace cvtest; +namespace { + ////////////////////////////////////////////////////////////////////////// // StereoBM @@ -209,4 +211,6 @@ INSTANTIATE_TEST_CASE_P(CUDA_Stereo, ReprojectImageTo3D, testing::Combine( testing::Values(MatDepth(CV_8U), MatDepth(CV_16S)), WHOLE_SUBMAT)); +} // namespace + #endif // HAVE_CUDA From b72d19675353b909ac5344e4d64495962b31fc13 Mon Sep 17 00:00:00 2001 From: Li Peng Date: Wed, 23 Nov 2016 12:57:33 +0800 Subject: [PATCH 134/409] optimization for warpAffine and warpPerspective Add new ocl kernels for warpAffine and warpPerspective, The average performance improvemnt is about 30%. The new ocl kernels require CV_8UC1 format and support nearest neighbor and bilinear interpolation. Signed-off-by: Li Peng --- modules/imgproc/src/imgwarp.cpp | 85 ++++++++ modules/imgproc/src/opencl/warp_transform.cl | 214 +++++++++++++++++++ modules/imgproc/test/ocl/test_warp.cpp | 106 +++++++++ 3 files changed, 405 insertions(+) create mode 100644 modules/imgproc/src/opencl/warp_transform.cl diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 0d0fb7236b..943a515fbd 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -5647,6 +5647,81 @@ private: enum { OCL_OP_PERSPECTIVE = 1, OCL_OP_AFFINE = 0 }; +static bool ocl_warpTransform_cols4(InputArray _src, OutputArray _dst, InputArray _M0, + Size dsize, int flags, int borderType, const Scalar& borderValue, + int op_type) +{ + CV_Assert(op_type == OCL_OP_AFFINE || op_type == OCL_OP_PERSPECTIVE); + const ocl::Device & dev = ocl::Device::getDefault(); + int type = _src.type(), dtype = _dst.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type); + + int interpolation = flags & INTER_MAX; + if( interpolation == INTER_AREA ) + interpolation = INTER_LINEAR; + + if ( !dev.isIntel() || !(type == CV_8UC1) || + !(dtype == CV_8UC1) || !(_dst.cols() % 4 == 0) || + !(borderType == cv::BORDER_CONSTANT && + (interpolation == cv::INTER_NEAREST || interpolation == cv::INTER_LINEAR))) + return false; + + const char * const warp_op[2] = { "Affine", "Perspective" }; + const char * const interpolationMap[3] = { "nearest", "linear", NULL }; + ocl::ProgramSource program = ocl::imgproc::warp_transform_oclsrc; + String kernelName = format("warp%s_%s_8u", warp_op[op_type], interpolationMap[interpolation]); + + bool is32f = (interpolation == INTER_LINEAR); + int wdepth = interpolation == INTER_NEAREST ? depth : std::max(is32f ? CV_32F : CV_32S, depth); + int sctype = CV_MAKETYPE(wdepth, cn); + + ocl::Kernel k; + String opts = format("-D ST=%s", ocl::typeToStr(sctype)); + + k.create(kernelName.c_str(), program, opts); + if (k.empty()) + return false; + + float borderBuf[] = { 0, 0, 0, 0 }; + scalarToRawData(borderValue, borderBuf, sctype); + + UMat src = _src.getUMat(), M0; + _dst.create( dsize.area() == 0 ? src.size() : dsize, src.type() ); + UMat dst = _dst.getUMat(); + + float M[9]; + int matRows = (op_type == OCL_OP_AFFINE ? 2 : 3); + Mat matM(matRows, 3, CV_32F, M), M1 = _M0.getMat(); + CV_Assert( (M1.type() == CV_32F || M1.type() == CV_64F) && M1.rows == matRows && M1.cols == 3 ); + M1.convertTo(matM, matM.type()); + + if( !(flags & WARP_INVERSE_MAP) ) + { + if (op_type == OCL_OP_PERSPECTIVE) + invert(matM, matM); + else + { + float D = M[0]*M[4] - M[1]*M[3]; + D = D != 0 ? 1.f/D : 0; + float A11 = M[4]*D, A22=M[0]*D; + M[0] = A11; M[1] *= -D; + M[3] *= -D; M[4] = A22; + float b1 = -M[0]*M[2] - M[1]*M[5]; + float b2 = -M[3]*M[2] - M[4]*M[5]; + M[2] = b1; M[5] = b2; + } + } + matM.convertTo(M0, CV_32F); + + k.args(ocl::KernelArg::ReadOnly(src), ocl::KernelArg::WriteOnly(dst), ocl::KernelArg::PtrReadOnly(M0), + ocl::KernelArg(0, 0, 0, 0, borderBuf, CV_ELEM_SIZE(sctype))); + + size_t globalThreads[2]; + globalThreads[0] = (size_t)(dst.cols / 4); + globalThreads[1] = (size_t)dst.rows; + + return k.run(2, globalThreads, NULL, false); +} + static bool ocl_warpTransform(InputArray _src, OutputArray _dst, InputArray _M0, Size dsize, int flags, int borderType, const Scalar& borderValue, int op_type) @@ -5786,6 +5861,11 @@ void cv::warpAffine( InputArray _src, OutputArray _dst, { CV_INSTRUMENT_REGION() + CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat() && + _src.cols() <= SHRT_MAX && _src.rows() <= SHRT_MAX, + ocl_warpTransform_cols4(_src, _dst, _M0, dsize, flags, borderType, + borderValue, OCL_OP_AFFINE)) + CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(), ocl_warpTransform(_src, _dst, _M0, dsize, flags, borderType, borderValue, OCL_OP_AFFINE)) @@ -6312,6 +6392,11 @@ void cv::warpPerspective( InputArray _src, OutputArray _dst, InputArray _M0, CV_Assert( _src.total() > 0 ); + CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat() && + _src.cols() <= SHRT_MAX && _src.rows() <= SHRT_MAX, + ocl_warpTransform_cols4(_src, _dst, _M0, dsize, flags, borderType, borderValue, + OCL_OP_PERSPECTIVE)) + CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(), ocl_warpTransform(_src, _dst, _M0, dsize, flags, borderType, borderValue, OCL_OP_PERSPECTIVE)) diff --git a/modules/imgproc/src/opencl/warp_transform.cl b/modules/imgproc/src/opencl/warp_transform.cl new file mode 100644 index 0000000000..bfe35f6fc5 --- /dev/null +++ b/modules/imgproc/src/opencl/warp_transform.cl @@ -0,0 +1,214 @@ +// 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. + +__constant short4 vec_offset = (short4)(0, 1, 2, 3); + +#define GET_VAL(x, y) ((x) < 0 || (x) >= src_cols || (y) < 0 || (y) >= src_rows) ? scalar : src[src_offset + y * src_step + x] + +__kernel void warpAffine_nearest_8u(__global const uchar * src, int src_step, int src_offset, int src_rows, int src_cols, + __global uchar * dst, int dst_step, int dst_offset, int dst_rows, int dst_cols, + __constant float * M, ST scalar_) +{ + int x = get_global_id(0) * 4; + int y = get_global_id(1); + uchar scalar = convert_uchar_sat_rte(scalar_); + + if (x >= dst_cols || y >= dst_rows) return; + + /* { M0, M1, M2 } + * { M3, M4, M5 } + */ + + short4 new_x, new_y; + new_x = convert_short4_sat_rte(M[0] * convert_float4(vec_offset + (short4)(x)) + + M[1] * convert_float4((short4)y) + M[2]); + + new_y = convert_short4_sat_rte(M[3] * convert_float4(vec_offset + (short4)(x)) + + M[4] * convert_float4((short4)y) + M[5]); + + uchar4 pix = (uchar4)scalar; + + pix.s0 = GET_VAL(new_x.s0, new_y.s0); + pix.s1 = GET_VAL(new_x.s1, new_y.s1); + pix.s2 = GET_VAL(new_x.s2, new_y.s2); + pix.s3 = GET_VAL(new_x.s3, new_y.s3); + + int dst_index = x + y * dst_step + dst_offset; + + vstore4(pix, 0, dst + dst_index); +} + +uchar4 read_pixels(__global const uchar * src, short tx, short ty, + int src_offset, int src_step, int src_cols, int + src_rows, uchar scalar) +{ + uchar2 pt, pb; + short bx, by; + + bx = tx + 1; + by = ty + 1; + + if (tx >= 0 && (tx + 1) < src_cols && ty >= 0 && ty < src_rows) + { + pt = vload2(0, src + src_offset + ty * src_step + tx); + } + else + { + pt.s0 = GET_VAL(tx, ty); + pt.s1 = GET_VAL(bx, ty); + } + + if (tx >= 0 && (tx + 1) < src_cols && by >= 0 && by < src_rows) + { + pb = vload2(0, src + src_offset + by * src_step + tx); + } + else + { + pb.s0 = GET_VAL(tx, by); + pb.s1 = GET_VAL(bx, by); + } + + return (uchar4)(pt, pb); +} + +__kernel void warpAffine_linear_8u(__global const uchar * src, int src_step, int src_offset, int src_rows, int src_cols, + __global uchar * dst, int dst_step, int dst_offset, int dst_rows, int dst_cols, + __constant float * M, ST scalar_) +{ + int x = get_global_id(0) * 4; + int y = get_global_id(1); + uchar scalar = convert_uchar_sat_rte(scalar_); + + if (x >= dst_cols || y >= dst_rows) return; + + /* { M0, M1, M2 } + * { M3, M4, M5 } + */ + + float4 nx, ny; + nx = M[0] * convert_float4((vec_offset + (short4)x)) + M[1] * convert_float4((short4)y) + M[2]; + ny = M[3] * convert_float4((vec_offset + (short4)x)) + M[4] * convert_float4((short4)y) + M[5]; + + float4 s, t; + s = round((nx - floor(nx)) * 32.0f) / 32.0f; + t = round((ny - floor(ny)) * 32.0f) / 32.0f; + + short4 tx, ty; + tx = convert_short4_sat_rtn(nx); + ty = convert_short4_sat_rtn(ny); + + uchar4 pix[4]; + pix[0] = read_pixels(src, tx.s0, ty.s0, src_offset, src_step, src_cols, src_rows, scalar); + pix[1] = read_pixels(src, tx.s1, ty.s1, src_offset, src_step, src_cols, src_rows, scalar); + pix[2] = read_pixels(src, tx.s2, ty.s2, src_offset, src_step, src_cols, src_rows, scalar); + pix[3] = read_pixels(src, tx.s3, ty.s3, src_offset, src_step, src_cols, src_rows, scalar); + + float4 tl, tr, bl, br; + tl = convert_float4((uchar4)(pix[0].s0, pix[1].s0, pix[2].s0, pix[3].s0)); + tr = convert_float4((uchar4)(pix[0].s1, pix[1].s1, pix[2].s1, pix[3].s1)); + bl = convert_float4((uchar4)(pix[0].s2, pix[1].s2, pix[2].s2, pix[3].s2)); + br = convert_float4((uchar4)(pix[0].s3, pix[1].s3, pix[2].s3, pix[3].s3)); + + float4 pixel; + pixel = tl * (1 - s) * (1 - t) + tr * s * (1 - t) + bl * (1 - s) * t + br * s * t; + + int dst_index = x + y * dst_step + dst_offset; + vstore4(convert_uchar4_sat_rte(pixel), 0, dst + dst_index); +} + +__kernel void warpPerspective_nearest_8u(__global const uchar * src, int src_step, int src_offset, int src_rows, int src_cols, + __global uchar * dst, int dst_step, int dst_offset, int dst_rows, int dst_cols, + __constant float * M, ST scalar_) +{ + int x = get_global_id(0) * 4; + int y = get_global_id(1); + uchar scalar = convert_uchar_sat_rte(scalar_); + + if (x >= dst_cols || y >= dst_rows) return; + + /* { M0, M1, M2 } + * { M3, M4, M5 } + * { M6, M7, M8 } + */ + + float4 nx, ny, nz; + nx = M[0] * convert_float4(vec_offset + (short4)(x)) + + M[1] * convert_float4((short4)y) + M[2]; + + ny = M[3] * convert_float4(vec_offset + (short4)(x)) + + M[4] * convert_float4((short4)y) + M[5]; + + nz = M[6] * convert_float4(vec_offset + (short4)(x)) + + M[7] * convert_float4((short4)y) + M[8]; + + short4 new_x, new_y; + float4 fz = select((float4)(0.0f), (float4)(1.0f / nz), nz != 0.0f); + new_x = convert_short4_sat_rte(nx * fz); + new_y = convert_short4_sat_rte(ny * fz); + + uchar4 pix = (uchar4)scalar; + + pix.s0 = GET_VAL(new_x.s0, new_y.s0); + pix.s1 = GET_VAL(new_x.s1, new_y.s1); + pix.s2 = GET_VAL(new_x.s2, new_y.s2); + pix.s3 = GET_VAL(new_x.s3, new_y.s3); + + int dst_index = x + y * dst_step + dst_offset; + + vstore4(pix, 0, dst + dst_index); +} + +__kernel void warpPerspective_linear_8u(__global const uchar * src, int src_step, int src_offset, int src_rows, int src_cols, + __global uchar * dst, int dst_step, int dst_offset, int dst_rows, int dst_cols, + __constant float * M, ST scalar_) +{ + int x = get_global_id(0) * 4; + int y = get_global_id(1); + uchar scalar = convert_uchar_sat_rte(scalar_); + + if (x >= dst_cols || y >= dst_rows) return; + + /* { M0, M1, M2 } + * { M3, M4, M5 } + * { M6, M7, M8 } + */ + + float4 nx, ny, nz; + nx = M[0] * convert_float4(vec_offset + (short4)(x)) + M[1] * convert_float4((short4)y) + M[2]; + + ny = M[3] * convert_float4(vec_offset + (short4)(x)) + M[4] * convert_float4((short4)y) + M[5]; + + nz = M[6] * convert_float4(vec_offset + (short4)(x)) + M[7] * convert_float4((short4)y) + M[8]; + + float4 fz = select((float4)(0.0f), (float4)(1.0f / nz), nz != 0.0f); + + nx = nx * fz; + ny = ny * fz; + + float4 s, t; + s = round((nx - floor(nx)) * 32.0f) / (float4)32.0f; + t = round((ny - floor(ny)) * 32.0f) / (float4)32.0f; + + short4 tx, ty; + tx = convert_short4_sat_rtn(nx); + ty = convert_short4_sat_rtn(ny); + + uchar4 pix[4]; + pix[0] = read_pixels(src, tx.s0, ty.s0, src_offset, src_step, src_cols, src_rows, scalar); + pix[1] = read_pixels(src, tx.s1, ty.s1, src_offset, src_step, src_cols, src_rows, scalar); + pix[2] = read_pixels(src, tx.s2, ty.s2, src_offset, src_step, src_cols, src_rows, scalar); + pix[3] = read_pixels(src, tx.s3, ty.s3, src_offset, src_step, src_cols, src_rows, scalar); + + float4 tl, tr, bl, br; + tl = convert_float4((uchar4)(pix[0].s0, pix[1].s0, pix[2].s0, pix[3].s0)); + tr = convert_float4((uchar4)(pix[0].s1, pix[1].s1, pix[2].s1, pix[3].s1)); + bl = convert_float4((uchar4)(pix[0].s2, pix[1].s2, pix[2].s2, pix[3].s2)); + br = convert_float4((uchar4)(pix[0].s3, pix[1].s3, pix[2].s3, pix[3].s3)); + + float4 pixel; + pixel = tl * (1 - s) * (1 - t) + tr * s * (1 - t) + bl * (1 - s) * t + br * s * t; + + int dst_index = x + y * dst_step + dst_offset; + vstore4(convert_uchar4_sat_rte(pixel), 0, dst + dst_index); +} diff --git a/modules/imgproc/test/ocl/test_warp.cpp b/modules/imgproc/test/ocl/test_warp.cpp index da70f732df..8534658278 100644 --- a/modules/imgproc/test/ocl/test_warp.cpp +++ b/modules/imgproc/test/ocl/test_warp.cpp @@ -113,6 +113,57 @@ PARAM_TEST_CASE(WarpTestBase, MatType, Interpolation, bool, bool) } }; +PARAM_TEST_CASE(WarpTest_cols4_Base, MatType, Interpolation, bool, bool) +{ + int type, interpolation; + Size dsize; + bool useRoi, mapInverse; + int depth; + + TEST_DECLARE_INPUT_PARAMETER(src); + TEST_DECLARE_OUTPUT_PARAMETER(dst); + + virtual void SetUp() + { + type = GET_PARAM(0); + interpolation = GET_PARAM(1); + mapInverse = GET_PARAM(2); + useRoi = GET_PARAM(3); + depth = CV_MAT_DEPTH(type); + + if (mapInverse) + interpolation |= WARP_INVERSE_MAP; + } + + void random_roi() + { + dsize = randomSize(1, MAX_VALUE); + dsize.width = ((dsize.width >> 2) + 1) * 4; + + Size roiSize = randomSize(1, MAX_VALUE); + Border srcBorder = randomBorder(0, useRoi ? MAX_VALUE : 0); + randomSubMat(src, src_roi, roiSize, srcBorder, type, -MAX_VALUE, MAX_VALUE); + + Border dstBorder = randomBorder(0, useRoi ? MAX_VALUE : 0); + randomSubMat(dst, dst_roi, dsize, dstBorder, type, -MAX_VALUE, MAX_VALUE); + + UMAT_UPLOAD_INPUT_PARAMETER(src); + UMAT_UPLOAD_OUTPUT_PARAMETER(dst); + } + + void Near(double threshold = 0.0) + { + if (depth < CV_32F) + EXPECT_MAT_N_DIFF(dst_roi, udst_roi, cvRound(dst_roi.total()*threshold)); + else + OCL_EXPECT_MATS_NEAR_RELATIVE(dst, threshold); + } +}; + +/////warpAffine + +typedef WarpTestBase WarpAffine; + /////warpAffine typedef WarpTestBase WarpAffine; @@ -134,6 +185,25 @@ OCL_TEST_P(WarpAffine, Mat) } } +typedef WarpTest_cols4_Base WarpAffine_cols4; + +OCL_TEST_P(WarpAffine_cols4, Mat) +{ + for (int j = 0; j < test_loop_times; j++) + { + double eps = depth < CV_32F ? 0.04 : 0.06; + random_roi(); + + Mat M = getRotationMatrix2D(Point2f(src_roi.cols / 2.0f, src_roi.rows / 2.0f), + rng.uniform(-180.f, 180.f), rng.uniform(0.4f, 2.0f)); + + OCL_OFF(cv::warpAffine(src_roi, dst_roi, M, dsize, interpolation)); + OCL_ON(cv::warpAffine(usrc_roi, udst_roi, M, dsize, interpolation)); + + Near(eps); + } +} + //// warpPerspective typedef WarpTestBase WarpPerspective; @@ -161,6 +231,30 @@ OCL_TEST_P(WarpPerspective, Mat) } } +typedef WarpTest_cols4_Base WarpPerspective_cols4; + +OCL_TEST_P(WarpPerspective_cols4, Mat) +{ + for (int j = 0; j < test_loop_times; j++) + { + double eps = depth < CV_32F ? 0.03 : 0.06; + random_roi(); + + float cols = static_cast(src_roi.cols), rows = static_cast(src_roi.rows); + float cols2 = cols / 2.0f, rows2 = rows / 2.0f; + Point2f sp[] = { Point2f(0.0f, 0.0f), Point2f(cols, 0.0f), Point2f(0.0f, rows), Point2f(cols, rows) }; + Point2f dp[] = { Point2f(rng.uniform(0.0f, cols2), rng.uniform(0.0f, rows2)), + Point2f(rng.uniform(cols2, cols), rng.uniform(0.0f, rows2)), + Point2f(rng.uniform(0.0f, cols2), rng.uniform(rows2, rows)), + Point2f(rng.uniform(cols2, cols), rng.uniform(rows2, rows)) }; + Mat M = getPerspectiveTransform(sp, dp); + + OCL_OFF(cv::warpPerspective(src_roi, dst_roi, M, dsize, interpolation)); + OCL_ON(cv::warpPerspective(usrc_roi, udst_roi, M, dsize, interpolation)); + + Near(eps); + } +} ///////////////////////////////////////////////////////////////////////////////////////////////// //// resize @@ -341,12 +435,24 @@ OCL_INSTANTIATE_TEST_CASE_P(ImgprocWarp, WarpAffine, Combine( Bool(), Bool())); +OCL_INSTANTIATE_TEST_CASE_P(ImgprocWarp, WarpAffine_cols4, Combine( + Values((MatType)CV_8UC1), + Values((Interpolation)INTER_NEAREST, (Interpolation)INTER_LINEAR), + Bool(), + Bool())); + OCL_INSTANTIATE_TEST_CASE_P(ImgprocWarp, WarpPerspective, Combine( Values(CV_8UC1, CV_8UC3, CV_8UC4, CV_32FC1, CV_32FC3, CV_32FC4), Values((Interpolation)INTER_NEAREST, (Interpolation)INTER_LINEAR, (Interpolation)INTER_CUBIC), Bool(), Bool())); +OCL_INSTANTIATE_TEST_CASE_P(ImgprocWarp, WarpPerspective_cols4, Combine( + Values((MatType)CV_8UC1), + Values((Interpolation)INTER_NEAREST, (Interpolation)INTER_LINEAR), + Bool(), + Bool())); + OCL_INSTANTIATE_TEST_CASE_P(ImgprocWarp, Resize, Combine( Values(CV_8UC1, CV_8UC4, CV_16UC2, CV_32FC1, CV_32FC4), Values(0.5, 1.5, 2.0, 0.2), From c56c0e140b7a45e18ccdf70012d2e41f194f235e Mon Sep 17 00:00:00 2001 From: LaurentBerger Date: Tue, 29 Nov 2016 12:10:33 +0100 Subject: [PATCH 135/409] Solve exception for 3D Mat --- modules/core/src/convert.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/convert.cpp b/modules/core/src/convert.cpp index de36cc67a2..9d65f3b906 100644 --- a/modules/core/src/convert.cpp +++ b/modules/core/src/convert.cpp @@ -5692,7 +5692,7 @@ void cv::normalize( InputArray _src, InputOutputArray _dst, double a, double b, { double smin = 0, smax = 0; double dmin = MIN( a, b ), dmax = MAX( a, b ); - minMaxLoc( _src, &smin, &smax, 0, 0, _mask ); + minMaxIdx( _src, &smin, &smax, 0, 0, _mask ); scale = (dmax - dmin)*(smax - smin > DBL_EPSILON ? 1./(smax - smin) : 0); shift = dmin - smin*scale; } From e8f3469096df0a65a8c38e57e8eb58632fb1e806 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Thu, 3 Nov 2016 16:54:06 +0300 Subject: [PATCH 136/409] Added OpenVX based processing to Sobel --- modules/imgproc/src/deriv.cpp | 135 ++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/modules/imgproc/src/deriv.cpp b/modules/imgproc/src/deriv.cpp index f8821f0ab1..c3c30cb354 100644 --- a/modules/imgproc/src/deriv.cpp +++ b/modules/imgproc/src/deriv.cpp @@ -43,6 +43,12 @@ #include "precomp.hpp" #include "opencl_kernels_imgproc.hpp" +#ifdef HAVE_OPENVX +#define IVX_HIDE_INFO_WARNINGS +#define IVX_USE_OPENCV +#include "ivx.hpp" +#endif + /****************************************************************************************\ Sobel & Scharr Derivative Filters \****************************************************************************************/ @@ -179,6 +185,130 @@ cv::Ptr cv::createDerivFilter(int srcType, int dstType, kx, ky, Point(-1,-1), 0, borderType ); } +#ifdef HAVE_OPENVX +namespace cv +{ + static bool openvx_sobel(InputArray _src, OutputArray _dst, + int dx, int dy, int ksize, + double scale, double delta, int borderType) + { + int stype = _src.type(); + int dtype = _dst.type(); + if (stype != CV_8UC1 || (dtype != CV_16SC1 && dtype != CV_8UC1) || + ksize < 3 || ksize % 2 != 1 || delta != 0.0) + return false; + + Mat src = _src.getMat(); + Mat dst = _dst.getMat(); + + if (src.cols < ksize || src.rows < ksize) + return false; + + int iscale = 1; + vx_uint32 cscale = 1; + if(scale != 1.0) + { + iscale = static_cast(scale); + if (std::abs(scale - iscale) >= DBL_EPSILON) + { + int exp = 0; + float significand = frexp(scale, &exp); + if ((significand == 0.5f) && (exp <= 0)) + { + iscale = 1; + cscale = 1 << (exp = -exp + 1); + } + else + return false; + } + } + + if ((borderType & BORDER_ISOLATED) == 0 && src.isSubmatrix()) + return false; //Process isolated borders only + vx_border_t border; + switch (borderType & ~BORDER_ISOLATED) + { + case BORDER_CONSTANT: + border.mode = VX_BORDER_CONSTANT; +#if VX_VERSION > VX_VERSION_1_0 + border.constant_value.U8 = (vx_uint8)(0); +#else + border.constant_value = (vx_uint32)(0); +#endif + break; + case BORDER_REPLICATE: + border.mode = VX_BORDER_REPLICATE; + break; + default: + return false; + } + + try + { + ivx::Context ctx = ivx::Context::create(); + if ((vx_size)ksize > ctx.convolutionMaxDimension()) + return false; + + Mat a; + if (dst.data != src.data) + a = src; + else + src.copyTo(a); + + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(a.cols, a.rows, 1, (vx_int32)(a.step)), a.data), + ib = ivx::Image::createFromHandle(ctx, dtype == CV_16SC1 ? VX_DF_IMAGE_S16 : VX_DF_IMAGE_U8, + ivx::Image::createAddressing(dst.cols, dst.rows, dtype == CV_16SC1 ? 2 : 1, (vx_int32)(dst.step)), dst.data); + + //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); + if (dtype == CV_16SC1 && ksize == 3 && ((dx | dy) == 1) && (dx + dy) == 1) + { + if(dx) + ivx::IVX_CHECK_STATUS(vxuSobel3x3(ctx, ia, ib, NULL)); + else + ivx::IVX_CHECK_STATUS(vxuSobel3x3(ctx, ia, NULL, ib)); + } + else + { +#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); + return false; + } +#endif + Mat kx, ky; + getDerivKernels(kx, ky, dx, dy, ksize, false); + flip(kx, kx, 0); + flip(ky, ky, 0); + Mat convData; + cv::Mat(ky*kx.t()).convertTo(convData, CV_16SC1, iscale); + ivx::Convolution cnv = ivx::Convolution::create(ctx, convData.cols, convData.rows); + cnv.copyFrom(convData); + cnv.setScale(cscale); + ivx::IVX_CHECK_STATUS(vxuConvolve(ctx, ia, cnv, ib)); + } + ctx.setBorderMode(prevBorder); + return true; + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + } +} +#endif + #ifdef HAVE_IPP namespace cv { @@ -599,6 +729,11 @@ void cv::Sobel( InputArray _src, OutputArray _dst, int ddepth, int dx, int dy, } #endif +#ifdef HAVE_OPENVX + if (openvx_sobel(_src, _dst, dx, dy, ksize, scale, delta, borderType)) + return; +#endif + CV_IPP_RUN(!(ocl::useOpenCL() && _dst.isUMat()), ipp_sobel(_src, _dst, ddepth, dx, dy, ksize, scale, delta, borderType)); int ktype = std::max(CV_32F, std::max(ddepth, sdepth)); From 9200db778c05afa076bf472b0f2c171dfbd8f553 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Mon, 31 Oct 2016 13:27:40 +0300 Subject: [PATCH 137/409] Added OpenVX based processing to medianBlur --- modules/imgproc/src/smooth.cpp | 98 ++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/modules/imgproc/src/smooth.cpp b/modules/imgproc/src/smooth.cpp index e7860b8807..ae0652421c 100644 --- a/modules/imgproc/src/smooth.cpp +++ b/modules/imgproc/src/smooth.cpp @@ -44,6 +44,11 @@ #include "precomp.hpp" #include "opencl_kernels_imgproc.hpp" +#ifdef HAVE_OPENVX +#define IVX_HIDE_INFO_WARNINGS +#define IVX_USE_OPENCV +#include "ivx.hpp" +#endif /* * This file includes the code, contributed by Simon Perreault * (the function icvMedianBlur_8u_O1) @@ -3122,6 +3127,94 @@ static bool ocl_medianFilter(InputArray _src, OutputArray _dst, int m) } +#ifdef HAVE_OPENVX +namespace cv +{ + static bool openvx_medianFilter(InputArray _src, OutputArray _dst, int ksize) + { + if (_src.type() != CV_8UC1 || _dst.type() != CV_8U +#ifndef VX_VERSION_1_1 + || ksize != 3 +#endif + ) + return false; + + Mat src = _src.getMat(); + Mat dst = _dst.getMat(); + + vx_border_t border; + border.mode = VX_BORDER_REPLICATE; + + try + { + ivx::Context ctx = ivx::Context::create(); +#ifdef VX_VERSION_1_1 + if ((vx_size)ksize > ctx.nonlinearMaxDimension()) + return false; +#endif + + Mat a; + if (dst.data != src.data) + a = src; + else + src.copyTo(a); + + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(a.cols, a.rows, 1, (vx_int32)(a.step)), a.data), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(dst.cols, dst.rows, 1, (vx_int32)(dst.step)), dst.data); + + //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); +#ifdef VX_VERSION_1_1 + if (ksize == 3) +#endif + { + ivx::IVX_CHECK_STATUS(vxuMedian3x3(ctx, ia, ib)); + } +#ifdef VX_VERSION_1_1 + else + { + ivx::Matrix mtx; + if(ksize == 5) + mtx = ivx::Matrix::createFromPattern(ctx, VX_PATTERN_BOX, ksize, ksize); + else + { + vx_size supportedSize; + ivx::IVX_CHECK_STATUS(vxQueryContext(ctx, VX_CONTEXT_NONLINEAR_MAX_DIMENSION, &supportedSize, sizeof(supportedSize))); + if ((vx_size)ksize > supportedSize) + { + ctx.setBorderMode(prevBorder); + return false; + } + Mat mask(ksize, ksize, CV_8UC1, Scalar(255)); + mtx = ivx::Matrix::create(ctx, VX_TYPE_UINT8, ksize, ksize); + mtx.copyFrom(mask); + } + ivx::IVX_CHECK_STATUS(vxuNonLinearFilter(ctx, VX_NONLINEAR_FILTER_MEDIAN, ia, mtx, ib)); + } +#endif + ctx.setBorderMode(prevBorder); + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + + return true; + } +} +#endif + #ifdef HAVE_IPP namespace cv { @@ -3202,6 +3295,11 @@ void cv::medianBlur( InputArray _src0, OutputArray _dst, int ksize ) _dst.create( src0.size(), src0.type() ); Mat dst = _dst.getMat(); +#ifdef HAVE_OPENVX + if (openvx_medianFilter(_src0, _dst, ksize)) + return; +#endif + CV_IPP_RUN(IPP_VERSION_X100 >= 810 && ksize <= 5, ipp_medianFilter(_src0,_dst, ksize)); #ifdef HAVE_TEGRA_OPTIMIZATION From e9cc0f6a4c5c547979dbbc0637c301d2a32872bd Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Fri, 11 Nov 2016 17:04:54 +0300 Subject: [PATCH 138/409] Added OpenVX based processing to minMaxLoc --- 3rdparty/openvx/include/ivx.hpp | 154 ++++++++++++++++++++++++++++++++ modules/core/src/stat.cpp | 87 ++++++++++++++++++ 2 files changed, 241 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index b813ac9cf5..b9f7b2346c 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -1693,6 +1693,160 @@ public: /// vxCreateVirtualArray() wrapper static Array createVirtual(vx_graph g, vx_enum type, vx_size capacity) { return Array(vxCreateVirtualArray(g, type, capacity)); } + +#ifndef VX_VERSION_1_1 + static const vx_enum + VX_MEMORY_TYPE_HOST = VX_IMPORT_TYPE_HOST, + VX_ARRAY_ITEMTYPE = VX_ARRAY_ATTRIBUTE_ITEMTYPE, + VX_ARRAY_NUMITEMS = VX_ARRAY_ATTRIBUTE_NUMITEMS, + VX_ARRAY_CAPACITY = VX_ARRAY_ATTRIBUTE_CAPACITY, + VX_ARRAY_ITEMSIZE = VX_ARRAY_ATTRIBUTE_ITEMSIZE; +#endif + + template + void query(vx_enum att, T& value) const + { IVX_CHECK_STATUS( vxQueryArray(ref, att, &value, sizeof(value)) ); } + + vx_enum itemType() const + { + vx_enum v; + query(VX_ARRAY_ITEMTYPE, v); + return v; + } + + vx_size itemSize() const + { + vx_size v; + query(VX_ARRAY_ITEMSIZE, v); + return v; + } + + vx_size capacity() const + { + vx_size v; + query(VX_ARRAY_CAPACITY, v); + return v; + } + + vx_size itemCount() const + { + vx_size v; + query(VX_ARRAY_NUMITEMS, v); + return v; + } + + void copyRangeTo(size_t start, size_t end, void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): output pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyArrayRange(ref, start, end, itemSize(), data, VX_READ_ONLY, VX_MEMORY_TYPE_HOST)); +#else + vx_size stride = itemSize(); + IVX_CHECK_STATUS(vxAccessArrayRange(ref, start, end, &stride, &data, VX_READ_ONLY)); + IVX_CHECK_STATUS(vxCommitArrayRange(ref, start, end, data)); +#endif + } + + void copyTo(void* data) + { copyRangeTo(0, itemCount(), data); } + + void copyRangeFrom(size_t start, size_t end, const void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): input pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyArrayRange(ref, start, end, itemSize(), const_cast(data), VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST)); +#else + vx_size stride = itemSize(); + IVX_CHECK_STATUS(vxAccessArrayRange(ref, start, end, &stride, const_cast(&data), VX_WRITE_ONLY)); + IVX_CHECK_STATUS(vxCommitArrayRange(ref, start, end, data)); +#endif + } + + void copyFrom(const void* data) + { copyRangeFrom(0, itemCount(), data); } + + void copyRange(size_t start, size_t end, void* data, vx_enum usage, vx_enum memType = VX_MEMORY_TYPE_HOST) + { + if (!data) throw WrapperError(std::string(__func__) + "(): data pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyArrayRange(ref, start, end, itemSize(), data, usage, memType)); +#else + vx_size stride = itemSize(); + IVX_CHECK_STATUS(vxAccessArrayRange(ref, start, end, &stride, &data, usage)); + IVX_CHECK_STATUS(vxCommitArrayRange(ref, start, end, data)); + (void)memType; +#endif + } + + void copy(void* data, vx_enum usage, vx_enum memType = VX_MEMORY_TYPE_HOST) + { copyRange(0, itemCount(), data, usage, memType); } + + template void copyRangeTo(size_t start, size_t end, std::vector& data) + { + if (TypeToEnum::value != itemType()) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (data.size() != (end - start)) + { + if (data.size() == 0) + data.resize((end - start)); + else + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + } + copyRangeTo(start, end, &data[0]); + } + + template void copyTo(std::vector& data) + { copyRangeTo(0, itemCount(), data); } + + template void copyRangeFrom(size_t start, size_t end, const std::vector& data) + { + if (TypeToEnum::value != itemType()) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + if (data.size() != (end - start)) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + copyRangeFrom(start, end, &data[0]); + } + + template void copyFrom(std::vector& data) + { copyRangeFrom(0, itemCount(), data); } + +#ifdef IVX_USE_OPENCV + void copyRangeTo(size_t start, size_t end, cv::Mat& m) + { + if (m.type() != enumToCVType(itemType())) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (!( + ((vx_size)(m.rows) == (end - start) && m.cols == 1) || + ((vx_size)(m.cols) == (end - start) && m.rows == 1) + ) && !m.empty()) throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + + if (m.isContinuous() && (vx_size)(m.total()) == (end - start)) + { + copyRangeTo(start, end, m.ptr()); + } + else + { + cv::Mat tmp(1, (int)(end - start), enumToCVType(itemType())); + copyRangeTo(start, end, tmp.ptr()); + if (m.empty()) + m = tmp; + else + tmp.copyTo(m); + } + } + + void copyTo(cv::Mat& m) + { copyRangeTo(0, itemCount(), m); } + + void copyRangeFrom(size_t start, size_t end, const cv::Mat& m) + { + if (!( + ((vx_size)(m.rows) == (end - start) && m.cols == 1) || + ((vx_size)(m.cols) == (end - start) && m.rows == 1) + )) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + if (m.type() != enumToCVType(itemType())) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + copyFrom(m.isContinuous() ? m.ptr() : m.clone().ptr()); + } + + void copyFrom(const cv::Mat& m) + { copyRangeFrom(0, itemCount(), m); } +#endif //IVX_USE_OPENCV }; /* diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index 9def2e171d..d829c69a9d 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -47,6 +47,12 @@ #include "opencl_kernels_core.hpp" +#ifdef HAVE_OPENVX +#define IVX_HIDE_INFO_WARNINGS +#define IVX_USE_OPENCV +#include "ivx.hpp" +#endif + namespace cv { @@ -2226,6 +2232,81 @@ static bool ocl_minMaxIdx( InputArray _src, double* minVal, double* maxVal, int* #endif +#ifdef HAVE_OPENVX +static bool openvx_minMaxIdx(Mat &src, double* minVal, double* maxVal, int* minIdx, int* maxIdx, Mat &mask) +{ + int stype = src.type(); + size_t total_size = src.total(); + int rows = src.size[0], cols = rows ? (int)(total_size / rows) : 0; + if ((stype != CV_8UC1 && stype != CV_16SC1) || !mask.empty() || + (src.dims != 2 && !(src.isContinuous() && cols > 0 && (size_t)rows*cols == total_size)) + ) + return false; + + try + { + ivx::Context ctx = ivx::Context::create(); + ivx::Image + ia = ivx::Image::createFromHandle(ctx, stype == CV_8UC1 ? VX_DF_IMAGE_U8 : VX_DF_IMAGE_S16, + ivx::Image::createAddressing(cols, rows, stype == CV_8UC1 ? 1 : 2, (vx_int32)(src.step[0])), src.ptr()); + + ivx::Scalar vxMinVal = ivx::Scalar::create(ctx, stype == CV_8UC1 ? VX_TYPE_UINT8 : VX_TYPE_INT16, 0); + ivx::Scalar vxMaxVal = ivx::Scalar::create(ctx, stype == CV_8UC1 ? VX_TYPE_UINT8 : VX_TYPE_INT16, 0); + ivx::Array vxMinInd, vxMaxInd; + ivx::Scalar vxMinCount, vxMaxCount; + if (minIdx) + { + vxMinInd = ivx::Array::create(ctx, VX_TYPE_COORDINATES2D, 1); + vxMinCount = ivx::Scalar::create(ctx, VX_TYPE_UINT32, 0); + } + if (maxIdx) + { + vxMaxInd = ivx::Array::create(ctx, VX_TYPE_COORDINATES2D, 1); + vxMaxCount = ivx::Scalar::create(ctx, VX_TYPE_UINT32, 0); + } + + ivx::IVX_CHECK_STATUS(vxuMinMaxLoc(ctx, ia, vxMinVal, vxMaxVal, vxMinInd, vxMaxInd, vxMinCount, vxMaxCount)); + + if (minVal) + { + *minVal = stype == CV_8UC1 ? vxMinVal.getValue() : vxMinVal.getValue(); + } + if (maxVal) + { + *maxVal = stype == CV_8UC1 ? vxMaxVal.getValue() : vxMaxVal.getValue(); + } + if (minIdx) + { + if(vxMinCount.getValue()<1) throw ivx::RuntimeError(VX_ERROR_INVALID_VALUE, std::string(__func__) + "(): minimum value location not found"); + vx_coordinates2d_t loc; + vxMinInd.copyRangeTo(0, 1, &loc); + size_t minidx = loc.y * cols + loc.x + 1; + ofs2idx(src, minidx, minIdx); + } + if (maxIdx) + { + if (vxMaxCount.getValue()<1) throw ivx::RuntimeError(VX_ERROR_INVALID_VALUE, std::string(__func__) + "(): maximum value location not found"); + vx_coordinates2d_t loc; + vxMaxInd.copyRangeTo(0, 1, &loc); + size_t maxidx = loc.y * cols + loc.x + 1; + ofs2idx(src, maxidx, maxIdx); + } + + return true; + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } +} +#endif + #ifdef HAVE_IPP static bool ipp_minMaxIdx( Mat &src, double* minVal, double* maxVal, int* minIdx, int* maxIdx, Mat &mask) { @@ -2349,6 +2430,12 @@ void cv::minMaxIdx(InputArray _src, double* minVal, ocl_minMaxIdx(_src, minVal, maxVal, minIdx, maxIdx, _mask)) Mat src = _src.getMat(), mask = _mask.getMat(); + +#ifdef HAVE_OPENVX + if (openvx_minMaxIdx(src, minVal, maxVal, minIdx, maxIdx, mask)) + return; +#endif + CV_IPP_RUN(IPP_VERSION_X100 >= 700, ipp_minMaxIdx(src, minVal, maxVal, minIdx, maxIdx, mask)) MinMaxIdxFunc func = getMinmaxTab(depth); From 0cd37886241ba8a5c7f3b7e46f28be195b147551 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Tue, 1 Nov 2016 16:31:37 +0300 Subject: [PATCH 139/409] Added OpenVX based processing to threshold --- 3rdparty/openvx/include/ivx.hpp | 20 ++++++ modules/imgproc/src/thresh.cpp | 105 ++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index f47831d1c8..0ac2108d47 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -1823,6 +1823,26 @@ static const vx_enum query(VX_THRESHOLD_FALSE_VALUE, v); return v; } + + /// vxSetThresholdAttribute(THRESHOLD_VALUE) wrapper + void setValue(vx_int32 &val) + { IVX_CHECK_STATUS(vxSetThresholdAttribute(ref, VX_THRESHOLD_THRESHOLD_VALUE, &val, sizeof(val))); } + + /// vxSetThresholdAttribute(THRESHOLD_LOWER) wrapper + void setValueLower(vx_int32 &val) + { IVX_CHECK_STATUS(vxSetThresholdAttribute(ref, VX_THRESHOLD_THRESHOLD_LOWER, &val, sizeof(val))); } + + /// vxSetThresholdAttribute(THRESHOLD_UPPER) wrapper + void setValueUpper(vx_int32 &val) + { IVX_CHECK_STATUS(vxSetThresholdAttribute(ref, VX_THRESHOLD_THRESHOLD_UPPER, &val, sizeof(val))); } + + /// vxSetThresholdAttribute(TRUE_VALUE) wrapper + void setValueTrue(vx_int32 &val) + { IVX_CHECK_STATUS(vxSetThresholdAttribute(ref, VX_THRESHOLD_TRUE_VALUE, &val, sizeof(val))); } + + /// vxSetThresholdAttribute(FALSE_VALUE) wrapper + void setValueFalse(vx_int32 &val) + { IVX_CHECK_STATUS(vxSetThresholdAttribute(ref, VX_THRESHOLD_FALSE_VALUE, &val, sizeof(val))); } }; /// vx_array wrapper diff --git a/modules/imgproc/src/thresh.cpp b/modules/imgproc/src/thresh.cpp index a5273ecd7c..e7709ba3e0 100644 --- a/modules/imgproc/src/thresh.cpp +++ b/modules/imgproc/src/thresh.cpp @@ -44,6 +44,12 @@ #include "opencl_kernels_imgproc.hpp" #include "opencv2/core/hal/intrin.hpp" +#ifdef HAVE_OPENVX +#define IVX_HIDE_INFO_WARNINGS +#define IVX_USE_OPENCV +#include "ivx.hpp" +#endif + namespace cv { @@ -1244,6 +1250,99 @@ static bool ocl_threshold( InputArray _src, OutputArray _dst, double & thresh, d #endif + +#ifdef HAVE_OPENVX +#define IMPL_OPENVX_TOZERO 1 +static bool openvx_threshold(Mat src, Mat dst, int thresh, int maxval, int type) +{ + Mat a = src; + + int trueVal, falseVal; + switch (type) + { + case THRESH_BINARY: +#ifndef VX_VERSION_1_1 + if (maxval != 255) + return false; +#endif + trueVal = maxval; + falseVal = 0; + break; + case THRESH_TOZERO: +#if IMPL_OPENVX_TOZERO + trueVal = 255; + falseVal = 0; + if (dst.data == src.data) + { + a = Mat(src.size(), src.type()); + src.copyTo(a); + } + break; +#endif + case THRESH_BINARY_INV: +#ifdef VX_VERSION_1_1 + trueVal = 0; + falseVal = maxval; + break; +#endif + case THRESH_TOZERO_INV: +#ifdef VX_VERSION_1_1 +#if IMPL_OPENVX_TOZERO + trueVal = 0; + falseVal = 255; + if (dst.data == src.data) + { + a = Mat(src.size(), src.type()); + src.copyTo(a); + } + break; +#endif +#endif + case THRESH_TRUNC: + default: + return false; + } + + try + { + ivx::Context ctx = ivx::Context::create(); + + ivx::Threshold thh = ivx::Threshold::createBinary(ctx, VX_TYPE_UINT8, thresh); + thh.setValueTrue(trueVal); + thh.setValueFalse(falseVal); + + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(a.cols*a.channels(), a.rows, 1, (vx_int32)(a.step)), src.data), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(dst.cols*dst.channels(), dst.rows, 1, (vx_int32)(dst.step)), dst.data); + + ivx::IVX_CHECK_STATUS(vxuThreshold(ctx, ia, thh, ib)); +#if IMPL_OPENVX_TOZERO + if (type == THRESH_TOZERO || type == THRESH_TOZERO_INV) + { + ivx::Image + ic = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(dst.cols*dst.channels(), dst.rows, 1, (vx_int32)(dst.step)), dst.data); + ivx::IVX_CHECK_STATUS(vxuAnd(ctx, ib, ia, ic)); + } +#endif + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + + return true; +} +#endif + } double cv::threshold( InputArray _src, OutputArray _dst, double thresh, double maxval, int type ) @@ -1296,6 +1395,12 @@ double cv::threshold( InputArray _src, OutputArray _dst, double thresh, double m src.copyTo(dst); return thresh; } + +#ifdef HAVE_OPENVX + if (openvx_threshold(src, dst, ithresh, imaxval, type)) + return thresh; +#endif + thresh = ithresh; maxval = imaxval; } From a4b9eb23d1b8d9c38a1f629ddd66fe4711b68fc2 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Thu, 10 Nov 2016 18:33:37 +0300 Subject: [PATCH 140/409] Added OpenVX based processing to meanStdDev --- modules/core/src/stat.cpp | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index 9def2e171d..f60faf494c 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -47,6 +47,12 @@ #include "opencl_kernels_core.hpp" +#ifdef HAVE_OPENVX +#define IVX_HIDE_INFO_WARNINGS +#define IVX_USE_OPENCV +#include "ivx.hpp" +#endif + namespace cv { @@ -1648,6 +1654,75 @@ static bool ocl_meanStdDev( InputArray _src, OutputArray _mean, OutputArray _sdv #endif +#ifdef HAVE_OPENVX +namespace cv +{ + static bool openvx_meanStdDev(Mat& src, OutputArray _mean, OutputArray _sdv, Mat& mask) + { + size_t total_size = src.total(); + int rows = src.size[0], cols = rows ? (int)(total_size / rows) : 0; + if (src.type() != CV_8UC1|| !mask.empty() || + (src.dims != 2 && !(src.isContinuous() && cols > 0 && (size_t)rows*cols == total_size)) + ) + return false; + + try + { + ivx::Context ctx = ivx::Context::create(); +#ifndef VX_VERSION_1_1 + if (ctx.vendorID() == VX_ID_KHRONOS) + return false; // Do not use OpenVX meanStdDev estimation for sample 1.0.1 implementation due to lack of accuracy +#endif + + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(cols, rows, 1, (vx_int32)(src.step[0])), src.ptr()); + + vx_float32 mean_temp, stddev_temp; + ivx::IVX_CHECK_STATUS(vxuMeanStdDev(ctx, ia, &mean_temp, &stddev_temp)); + + if (_mean.needed()) + { + if (!_mean.fixedSize()) + _mean.create(1, 1, CV_64F, -1, true); + Mat mean = _mean.getMat(); + CV_Assert(mean.type() == CV_64F && mean.isContinuous() && + (mean.cols == 1 || mean.rows == 1) && mean.total() >= 1); + double *pmean = mean.ptr(); + pmean[0] = mean_temp; + for (int c = 1; c < (int)mean.total(); c++) + pmean[c] = 0; + } + + if (_sdv.needed()) + { + if (!_sdv.fixedSize()) + _sdv.create(1, 1, CV_64F, -1, true); + Mat stddev = _sdv.getMat(); + CV_Assert(stddev.type() == CV_64F && stddev.isContinuous() && + (stddev.cols == 1 || stddev.rows == 1) && stddev.total() >= 1); + double *pstddev = stddev.ptr(); + pstddev[0] = stddev_temp; + for (int c = 1; c < (int)stddev.total(); c++) + pstddev[c] = 0; + } + + return true; + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + } +} +#endif + #ifdef HAVE_IPP namespace cv { @@ -1773,6 +1848,11 @@ void cv::meanStdDev( InputArray _src, OutputArray _mean, OutputArray _sdv, Input Mat src = _src.getMat(), mask = _mask.getMat(); CV_Assert( mask.empty() || mask.type() == CV_8UC1 ); +#ifdef HAVE_OPENVX + if (openvx_meanStdDev(src, _mean, _sdv, mask)) + return; +#endif + CV_IPP_RUN(IPP_VERSION_X100 >= 700, ipp_meanStdDev(src, _mean, _sdv, mask)); int k, cn = src.channels(), depth = src.depth(); From 0a6958813c973706e2ce631d0d65278c124bc254 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Fri, 11 Nov 2016 21:47:46 +0300 Subject: [PATCH 141/409] added OpenVX call to Mat::convertTo() (w/o scaling) --- modules/core/src/convert.cpp | 82 ++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/modules/core/src/convert.cpp b/modules/core/src/convert.cpp index de36cc67a2..94d80bd381 100644 --- a/modules/core/src/convert.cpp +++ b/modules/core/src/convert.cpp @@ -46,6 +46,12 @@ #include "opencl_kernels_core.hpp" #include "opencv2/core/hal/intrin.hpp" +#ifdef HAVE_OPENVX +#define IVX_USE_OPENCV +#define IVX_HIDE_INFO_WARNINGS +#include "ivx.hpp" +#endif + #ifdef __APPLE__ #undef CV_NEON #define CV_NEON 0 @@ -4634,10 +4640,86 @@ cvtScaleHalf_( const short* src, size_t sstep, float* dst, size_t } } +#ifdef HAVE_OPENVX +template +static bool _openvx_cvt(const T* src, size_t sstep, + DT* dst, size_t dstep, Size size) +{ + using namespace ivx; + + try + { + Context context = Context::create(); + Image srcImage = Image::createFromHandle(context, TypeToEnum::value, + Image::createAddressing(size.width, size.height, + (vx_uint32)sizeof(T), (vx_uint32)sstep), + (void*)src); + Image dstImage = Image::createFromHandle(context, TypeToEnum
::value, + Image::createAddressing(size.width, size.height, + (vx_uint32)sizeof(DT), (vx_uint32)dstep), + (void*)dst); + + IVX_CHECK_STATUS(vxuConvertDepth(context, srcImage, dstImage, VX_CONVERT_POLICY_SATURATE, 0)); + +#ifdef VX_VERSION_1_1 + //we should take user memory back before release + //(it's not done automatically according to standard) + srcImage.swapHandle(); dstImage.swapHandle(); +#endif + } + catch (RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + + return true; +} + +template +static bool openvx_cvt(const T* src, size_t sstep, + DT* dst, size_t dstep, Size size) +{ + (void)src; (void)sstep; (void)dst; (void)dstep; (void)size; + return false; +} + +#define DEFINE_OVX_CVT_SPECIALIZATION(T, DT) \ +template<> \ +bool openvx_cvt(const T *src, size_t sstep, DT *dst, size_t dstep, Size size) \ +{ \ + return _openvx_cvt(src, sstep, dst, dstep, size); \ +} + +DEFINE_OVX_CVT_SPECIALIZATION(uchar, ushort) +DEFINE_OVX_CVT_SPECIALIZATION(uchar, short) +DEFINE_OVX_CVT_SPECIALIZATION(uchar, int) +DEFINE_OVX_CVT_SPECIALIZATION(ushort, uchar) +DEFINE_OVX_CVT_SPECIALIZATION(ushort, int) +DEFINE_OVX_CVT_SPECIALIZATION(short, uchar) +DEFINE_OVX_CVT_SPECIALIZATION(short, int) +DEFINE_OVX_CVT_SPECIALIZATION(int, uchar) +DEFINE_OVX_CVT_SPECIALIZATION(int, ushort) +DEFINE_OVX_CVT_SPECIALIZATION(int, short) + +#endif + template static void cvt_( const T* src, size_t sstep, DT* dst, size_t dstep, Size size ) { +#ifdef HAVE_OPENVX + if(openvx_cvt(src, sstep, dst, dstep, size)) + { + return; + } +#endif + sstep /= sizeof(src[0]); dstep /= sizeof(dst[0]); Cvt_SIMD vop; From 2b56b174e8846fd9b48d38911dd3d22fc1dfa409 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Tue, 29 Nov 2016 17:50:37 +0300 Subject: [PATCH 142/409] fixed: data types, empty input case --- modules/core/src/convert.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/core/src/convert.cpp b/modules/core/src/convert.cpp index 94d80bd381..7dc8e898a8 100644 --- a/modules/core/src/convert.cpp +++ b/modules/core/src/convert.cpp @@ -4647,14 +4647,19 @@ static bool _openvx_cvt(const T* src, size_t sstep, { using namespace ivx; + if(!(size.width > 0 && size.height > 0)) + { + return true; + } + try { Context context = Context::create(); - Image srcImage = Image::createFromHandle(context, TypeToEnum::value, + Image srcImage = Image::createFromHandle(context, Image::matTypeToFormat(DataType::type), Image::createAddressing(size.width, size.height, (vx_uint32)sizeof(T), (vx_uint32)sstep), (void*)src); - Image dstImage = Image::createFromHandle(context, TypeToEnum
::value, + Image dstImage = Image::createFromHandle(context, Image::matTypeToFormat(DataType
::type), Image::createAddressing(size.width, size.height, (vx_uint32)sizeof(DT), (vx_uint32)dstep), (void*)dst); From 7c2734ff1447ed31127801aa4ae16abb155fd68f Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Tue, 8 Nov 2016 18:59:16 +0300 Subject: [PATCH 143/409] added OpenVX calls to accumulate(), accumulateSquare(), accumulateWeighted() --- modules/imgproc/src/accum.cpp | 97 +++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/modules/imgproc/src/accum.cpp b/modules/imgproc/src/accum.cpp index eaf85a2654..a156d147c6 100644 --- a/modules/imgproc/src/accum.cpp +++ b/modules/imgproc/src/accum.cpp @@ -45,6 +45,12 @@ #include "opencl_kernels_imgproc.hpp" #include "opencv2/core/hal/intrin.hpp" +#ifdef HAVE_OPENVX +#define IVX_USE_OPENCV +#define IVX_HIDE_INFO_WARNINGS +#include "ivx.hpp" +#endif + namespace cv { @@ -1930,6 +1936,77 @@ static bool ipp_accumulate(InputArray _src, InputOutputArray _dst, InputArray _m } #endif +#ifdef HAVE_OPENVX +namespace cv +{ +enum +{ + VX_ACCUMULATE_OP = 0, + VX_ACCUMULATE_SQUARE_OP = 1, + VX_ACCUMULATE_WEIGHTED_OP = 2 +}; + +static bool openvx_accumulate(InputArray _src, InputOutputArray _dst, InputArray _mask, double _weight, int opType) +{ + Mat srcMat = _src.getMat(), dstMat = _dst.getMat(); + if(!_mask.empty() || + (opType == VX_ACCUMULATE_WEIGHTED_OP && dstMat.type() != CV_8UC1 ) || + (opType != VX_ACCUMULATE_WEIGHTED_OP && dstMat.type() != CV_16SC1 ) || + srcMat.type() != CV_8UC1) + { + return false; + } + //TODO: handle different number of channels (channel extract && channel combine) + //TODO: handle mask (threshold mask to 0xff && bitwise AND with src) + //(both things can be done by creating a graph) + + try + { + ivx::Context context = ivx::Context::create(); + ivx::Image srcImage = ivx::Image::createFromHandle(context, ivx::Image::matTypeToFormat(srcMat.type()), + ivx::Image::createAddressing(srcMat), srcMat.data); + ivx::Image dstImage = ivx::Image::createFromHandle(context, ivx::Image::matTypeToFormat(dstMat.type()), + ivx::Image::createAddressing(dstMat), dstMat.data); + ivx::Scalar shift = ivx::Scalar::create(context, 0); + ivx::Scalar alpha = ivx::Scalar::create(context, _weight); + + switch (opType) + { + case VX_ACCUMULATE_OP: + ivx::IVX_CHECK_STATUS(vxuAccumulateImage(context, srcImage, dstImage)); + break; + case VX_ACCUMULATE_SQUARE_OP: + ivx::IVX_CHECK_STATUS(vxuAccumulateSquareImage(context, srcImage, shift, dstImage)); + break; + case VX_ACCUMULATE_WEIGHTED_OP: + ivx::IVX_CHECK_STATUS(vxuAccumulateWeightedImage(context, srcImage, alpha, dstImage)); + break; + default: + break; + } + +#ifdef VX_VERSION_1_1 + //we should take user memory back before release + //(it's not done automatically according to standard) + srcImage.swapHandle(); dstImage.swapHandle(); +#endif + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + + return true; +} +} +#endif + void cv::accumulate( InputArray _src, InputOutputArray _dst, InputArray _mask ) { CV_INSTRUMENT_REGION() @@ -1946,6 +2023,13 @@ void cv::accumulate( InputArray _src, InputOutputArray _dst, InputArray _mask ) CV_IPP_RUN((_src.dims() <= 2 || (_src.isContinuous() && _dst.isContinuous() && (_mask.empty() || _mask.isContinuous()))), ipp_accumulate(_src, _dst, _mask)); +#ifdef HAVE_OPENVX + if(openvx_accumulate(_src, _dst, _mask, 0.0, VX_ACCUMULATE_OP)) + { + return; + } +#endif + Mat src = _src.getMat(), dst = _dst.getMat(), mask = _mask.getMat(); @@ -2042,6 +2126,13 @@ void cv::accumulateSquare( InputArray _src, InputOutputArray _dst, InputArray _m CV_IPP_RUN((_src.dims() <= 2 || (_src.isContinuous() && _dst.isContinuous() && (_mask.empty() || _mask.isContinuous()))), ipp_accumulate_square(_src, _dst, _mask)); +#ifdef HAVE_OPENVX + if(openvx_accumulate(_src, _dst, _mask, 0.0, VX_ACCUMULATE_SQUARE_OP)) + { + return; + } +#endif + Mat src = _src.getMat(), dst = _dst.getMat(), mask = _mask.getMat(); int fidx = getAccTabIdx(sdepth, ddepth); @@ -2243,6 +2334,12 @@ void cv::accumulateWeighted( InputArray _src, InputOutputArray _dst, CV_IPP_RUN((_src.dims() <= 2 || (_src.isContinuous() && _dst.isContinuous() && _mask.isContinuous())), ipp_accumulate_weighted(_src, _dst, alpha, _mask)); +#ifdef HAVE_OPENVX + if(openvx_accumulate(_src, _dst, _mask, alpha, VX_ACCUMULATE_WEIGHTED_OP)) + { + return; + } +#endif Mat src = _src.getMat(), dst = _dst.getMat(), mask = _mask.getMat(); From 0ac934d09a7345a06c1e454ee30ab9abaad02c54 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Mon, 14 Nov 2016 18:40:43 +0300 Subject: [PATCH 144/409] added OpenVX call to equalizeHist() function --- modules/imgproc/src/histogram.cpp | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index 61fc09820f..7f46bc2d03 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -42,6 +42,12 @@ #include "precomp.hpp" #include "opencl_kernels_imgproc.hpp" +#ifdef HAVE_OPENVX +#define IVX_USE_OPENCV +#define IVX_HIDE_INFO_WARNINGS +#include "ivx.hpp" +#endif + namespace cv { @@ -3700,6 +3706,45 @@ static bool ocl_equalizeHist(InputArray _src, OutputArray _dst) #endif +#ifdef HAVE_OPENVX +namespace cv +{ +static bool openvx_equalize_hist(Mat srcMat, Mat dstMat) +{ + using namespace ivx; + + try + { + Context context = Context::create(); + Image srcImage = Image::createFromHandle(context, Image::matTypeToFormat(srcMat.type()), + Image::createAddressing(srcMat), srcMat.data); + Image dstImage = Image::createFromHandle(context, Image::matTypeToFormat(dstMat.type()), + Image::createAddressing(dstMat), dstMat.data); + + IVX_CHECK_STATUS(vxuEqualizeHist(context, srcImage, dstImage)); + +#ifdef VX_VERSION_1_1 + //we should take user memory back before release + //(it's not done automatically according to standard) + srcImage.swapHandle(); dstImage.swapHandle(); +#endif + } + catch (RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + + return true; +} +} +#endif + void cv::equalizeHist( InputArray _src, OutputArray _dst ) { CV_INSTRUMENT_REGION() @@ -3716,6 +3761,13 @@ void cv::equalizeHist( InputArray _src, OutputArray _dst ) _dst.create( src.size(), src.type() ); Mat dst = _dst.getMat(); +#ifdef HAVE_OPENVX + if(openvx_equalize_hist(src, dst)) + { + return; + } +#endif + Mutex histogramLockInstance; const int hist_sz = EqualizeHistCalcHist_Invoker::HIST_SZ; From 78b80b6abd12be0fdb71a9d7e73664b3fb82bd64 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Tue, 29 Nov 2016 19:34:46 +0300 Subject: [PATCH 145/409] Added wrappers for OpenVX context attributes and setter for convolution scale attribute (#7732) --- 3rdparty/openvx/include/ivx.hpp | 148 ++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index b813ac9cf5..f47831d1c8 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -696,6 +696,11 @@ 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; +#endif + /// vx_context wrapper class Context : public RefWrapper { @@ -719,6 +724,146 @@ public: /// vxLoadKernels() wrapper void loadKernels(const std::string& module) { IVX_CHECK_STATUS( vxLoadKernels(ref, module.c_str()) ); } + + /// vxQueryContext() wrapper + template + void query(vx_enum att, T& value) const + { IVX_CHECK_STATUS(vxQueryContext(ref, att, &value, sizeof(value))); } + +#ifndef VX_VERSION_1_1 + static const vx_enum + VX_CONTEXT_VENDOR_ID = VX_CONTEXT_ATTRIBUTE_VENDOR_ID, + VX_CONTEXT_VERSION = VX_CONTEXT_ATTRIBUTE_VERSION, + VX_CONTEXT_UNIQUE_KERNELS = VX_CONTEXT_ATTRIBUTE_UNIQUE_KERNELS, + VX_CONTEXT_MODULES = VX_CONTEXT_ATTRIBUTE_MODULES, + VX_CONTEXT_REFERENCES = VX_CONTEXT_ATTRIBUTE_REFERENCES, + VX_CONTEXT_IMPLEMENTATION = VX_CONTEXT_ATTRIBUTE_IMPLEMENTATION, + VX_CONTEXT_EXTENSIONS_SIZE = VX_CONTEXT_ATTRIBUTE_EXTENSIONS_SIZE, + VX_CONTEXT_EXTENSIONS = VX_CONTEXT_ATTRIBUTE_EXTENSIONS, + VX_CONTEXT_CONVOLUTION_MAX_DIMENSION = VX_CONTEXT_ATTRIBUTE_CONVOLUTION_MAXIMUM_DIMENSION, + VX_CONTEXT_OPTICAL_FLOW_MAX_WINDOW_DIMENSION = VX_CONTEXT_ATTRIBUTE_OPTICAL_FLOW_WINDOW_MAXIMUM_DIMENSION, + VX_CONTEXT_IMMEDIATE_BORDER = VX_CONTEXT_ATTRIBUTE_IMMEDIATE_BORDER_MODE, + VX_CONTEXT_UNIQUE_KERNEL_TABLE = VX_CONTEXT_ATTRIBUTE_UNIQUE_KERNEL_TABLE; +#endif + + /// vxQueryContext(VX_CONTEXT_VENDOR_ID) wrapper + vx_uint16 vendorID() const + { + vx_uint16 v; + query(VX_CONTEXT_VENDOR_ID, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_VERSION) wrapper + vx_uint16 version() const + { + vx_uint16 v; + query(VX_CONTEXT_VERSION, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_UNIQUE_KERNELS) wrapper + vx_uint32 uniqueKernels() const + { + vx_uint32 v; + query(VX_CONTEXT_UNIQUE_KERNELS, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_MODULES) wrapper + vx_uint32 modules() const + { + vx_uint32 v; + query(VX_CONTEXT_MODULES, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_REFERENCES) wrapper + vx_uint32 references() const + { + vx_uint32 v; + query(VX_CONTEXT_REFERENCES, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_EXTENSIONS_SIZE) wrapper + vx_size extensionsSize() const + { + vx_size v; + query(VX_CONTEXT_EXTENSIONS_SIZE, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_CONVOLUTION_MAX_DIMENSION) wrapper + vx_size convolutionMaxDimension() const + { + vx_size v; + query(VX_CONTEXT_CONVOLUTION_MAX_DIMENSION, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_OPTICAL_FLOW_MAX_WINDOW_DIMENSION) wrapper + vx_size opticalFlowMaxWindowSize() const + { + vx_size v; + query(VX_CONTEXT_OPTICAL_FLOW_MAX_WINDOW_DIMENSION, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_IMMEDIATE_BORDER) wrapper + vx_border_t borderMode() const + { + vx_border_t v; + query(VX_CONTEXT_IMMEDIATE_BORDER, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_IMPLEMENTATION) wrapper + std::string implementation() const + { + std::vector v(VX_MAX_IMPLEMENTATION_NAME); + IVX_CHECK_STATUS(vxQueryContext(ref, VX_CONTEXT_IMPLEMENTATION, &v[0], v.size() * sizeof(vx_char))); + return std::string(v.data()); + } + + /// vxQueryContext(VX_CONTEXT_EXTENSIONS) wrapper + std::string extensions() const + { + std::vector v(extensionsSize()); + IVX_CHECK_STATUS(vxQueryContext(ref, VX_CONTEXT_EXTENSIONS, &v[0], v.size() * sizeof(vx_char))); + return std::string(v.data()); + } + + /// vxQueryContext(VX_CONTEXT_UNIQUE_KERNEL_TABLE) wrapper + std::vector kernelTable() const + { + std::vector v(uniqueKernels()); + 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 v; + query(VX_CONTEXT_IMMEDIATE_BORDER_POLICY, v); + return v; + } + + /// vxQueryContext(VX_CONTEXT_NONLINEAR_MAX_DIMENSION) wrapper + vx_size nonlinearMaxDimension() const + { + vx_size v; + query(VX_CONTEXT_NONLINEAR_MAX_DIMENSION, v); + return v; + } +#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))); } + }; /// vx_graph wrapper @@ -1752,6 +1897,9 @@ public: return VX_TYPE_INT16; } + void setScale(vx_uint32 newScale) + { IVX_CHECK_STATUS( vxSetConvolutionAttribute(ref, VX_CONVOLUTION_SCALE, &newScale, sizeof(newScale)) ); } + void copyTo(void* data) { if (!data) throw WrapperError(std::string(__func__) + "(): output pointer is 0"); From 6c524e8444c079807032e9065554eefa38612306 Mon Sep 17 00:00:00 2001 From: Simmo Saan Date: Tue, 29 Nov 2016 21:31:34 +0200 Subject: [PATCH 146/409] Allow V4L, V4L2 to be used as preferred capture API --- modules/videoio/src/cap.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/videoio/src/cap.cpp b/modules/videoio/src/cap.cpp index f61cc4c3ff..f1218fdaa0 100644 --- a/modules/videoio/src/cap.cpp +++ b/modules/videoio/src/cap.cpp @@ -186,11 +186,11 @@ CV_IMPL CvCapture * cvCreateCameraCapture (int index) #ifdef HAVE_VFW TRY_OPEN(capture, cvCreateCameraCapture_VFW(index)) #endif - if (pref) break; // CV_CAP_VFW #if defined HAVE_LIBV4L || defined HAVE_CAMV4L || defined HAVE_CAMV4L2 || defined HAVE_VIDEOIO TRY_OPEN(capture, cvCreateCameraCapture_V4L(index)) #endif + if (pref) break; // CV_CAP_VFW #ifdef HAVE_GSTREAMER TRY_OPEN(capture, cvCreateCapture_GStreamer(CV_CAP_GSTREAMER_V4L2, reinterpret_cast(index))) @@ -302,15 +302,15 @@ CV_IMPL CvCapture * cvCreateFileCaptureWithPreference (const char * filename, in if (apiPreference) break; #endif -#ifdef HAVE_VFW case CV_CAP_VFW: +#ifdef HAVE_VFW TRY_OPEN(result, cvCreateFileCapture_VFW (filename)) - if (apiPreference) break; #endif + #if defined HAVE_LIBV4L || defined HAVE_CAMV4L || defined HAVE_CAMV4L2 || defined HAVE_VIDEOIO TRY_OPEN(result, cvCreateCameraCapture_V4L(filename)) - if (apiPreference) break; #endif + if (apiPreference) break; case CV_CAP_MSMF: #ifdef HAVE_MSMF From 71b2409df0b4cae6ac3a1fbebaf0c7f111f20b45 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 29 Nov 2016 20:28:59 +0300 Subject: [PATCH 147/409] ts: added findDataFile() utility function and SkipTestException --- CMakeLists.txt | 14 +-- cmake/OpenCVUtils.cmake | 18 +++ modules/ts/CMakeLists.txt | 20 ++++ modules/ts/include/opencv2/ts.hpp | 58 +++++++-- modules/ts/include/opencv2/ts/ts_ext.hpp | 24 +++- modules/ts/src/ts.cpp | 144 +++++++++++++++++++++-- modules/ts/src/ts_perf.cpp | 4 + 7 files changed, 252 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8337ec9fb9..5ac27a2242 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -362,14 +362,12 @@ if (OPENCV_TEST_DATA_PATH) get_filename_component(OPENCV_TEST_DATA_PATH ${OPENCV_TEST_DATA_PATH} ABSOLUTE) endif() -if(OPENCV_TEST_DATA_PATH AND NOT OPENCV_TEST_DATA_INSTALL_PATH) - if(ANDROID) - ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "sdk/etc/testdata") - elseif(WIN32) - ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "testdata") - else() - ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "share/OpenCV/testdata") - endif() +if(ANDROID) + ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "sdk/etc/testdata") +elseif(WIN32) + ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "testdata") +else() + ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "share/OpenCV/testdata") endif() if(ANDROID) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 34b4565cf8..8969a50903 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -1035,3 +1035,21 @@ function(ocv_add_test_from_target test_name test_kind the_target) endif() endif() endfunction() + +macro(ocv_add_testdata basedir dest_subdir) + if(BUILD_TESTS) + cmake_parse_arguments(__TESTDATA "" "COMPONENT" "" ${ARGN}) + if(NOT CMAKE_CROSSCOMPILING AND NOT INSTALL_TESTS) + file(COPY ${basedir}/ + DESTINATION ${CMAKE_BINARY_DIR}/${OPENCV_TEST_DATA_INSTALL_PATH}/${dest_subdir} + ${__TESTDATA_UNPARSED_ARGUMENTS} + ) + endif() + if(INSTALL_TESTS) + install(DIRECTORY ${basedir}/ + DESTINATION ${OPENCV_TEST_DATA_INSTALL_PATH}/contrib/text + ${ARGN} + ) + endif() + endif() +endmacro() diff --git a/modules/ts/CMakeLists.txt b/modules/ts/CMakeLists.txt index 8d625f8d00..f95bed0793 100644 --- a/modules/ts/CMakeLists.txt +++ b/modules/ts/CMakeLists.txt @@ -21,3 +21,23 @@ ocv_add_module(ts INTERNAL opencv_core opencv_imgproc opencv_imgcodecs opencv_vi ocv_glob_module_sources() ocv_module_include_directories() ocv_create_module() + +# generate config file +set(OPENCV_TESTS_CONFIG_FILE "${CMAKE_BINARY_DIR}/opencv_tests_config.hpp") +set(OPENCV_TESTS_CONFIG_STR "") +if(CMAKE_INSTALL_PREFIX) + set(OPENCV_TESTS_CONFIG_STR "${OPENCV_TESTS_CONFIG_STR} +#define OPENCV_INSTALL_PREFIX \"${CMAKE_INSTALL_PREFIX}\" +") +endif() +if(OPENCV_TEST_DATA_INSTALL_PATH) + set(OPENCV_TESTS_CONFIG_STR "${OPENCV_TESTS_CONFIG_STR} +#define OPENCV_TEST_DATA_INSTALL_PATH \"${OPENCV_TEST_DATA_INSTALL_PATH}\" +") +endif() +if(EXISTS "${OPENCV_TESTS_CONFIG_FILE}") + file(READ "${OPENCV_TESTS_CONFIG_FILE}" __content) +endif() +if(NOT OPENCV_TESTS_CONFIG_STR STREQUAL "${__content}") + file(WRITE "${OPENCV_TESTS_CONFIG_FILE}" "${OPENCV_TESTS_CONFIG_STR}") +endif() diff --git a/modules/ts/include/opencv2/ts.hpp b/modules/ts/include/opencv2/ts.hpp index 5f5dec65a2..28a071a565 100644 --- a/modules/ts/include/opencv2/ts.hpp +++ b/modules/ts/include/opencv2/ts.hpp @@ -1,5 +1,5 @@ -#ifndef OPENCV_GTESTCV_HPP -#define OPENCV_GTESTCV_HPP +#ifndef OPENCV_TS_HPP +#define OPENCV_TS_HPP #include "opencv2/core/cvdef.h" #include // for va_list @@ -55,6 +55,14 @@ using cv::Rect; using cv::InputArray; using cv::noArray; +class SkipTestException: public cv::Exception +{ +public: + int dummy; // workaround for MacOSX Xcode 7.3 bug (don't make class "empty") + SkipTestException() : dummy(0) {} + SkipTestException(const cv::String& message) : dummy(0) { this->msg = message; } +}; + class CV_EXPORTS TS; CV_EXPORTS int64 readSeed(const char* str); @@ -420,6 +428,8 @@ public: // returns textual description of failure code static string str_from_code( const TS::FailureCode code ); + std::vector data_search_path; + std::vector data_search_subdir; protected: // these are allocated within a test to try keep them valid in case of stack corruption @@ -539,17 +549,37 @@ struct CV_EXPORTS DefaultRngAuto DefaultRngAuto& operator=(const DefaultRngAuto&); }; -} - -namespace cvtest -{ // test images generation functions CV_EXPORTS void fillGradient(Mat& img, int delta = 5); CV_EXPORTS void smoothBorder(Mat& img, const Scalar& color, int delta = 3); CV_EXPORTS void printVersionInfo(bool useStdOut = true); -} //namespace cvtest + + +// Utility functions + +CV_EXPORTS void addDataSearchPath(const std::string& path); +CV_EXPORTS void addDataSearchSubDirectory(const std::string& subdir); + +/*! @brief Try to find requested data file + + Search directories: + + 0. TS::data_search_path (search sub-directories are not used) + 1. OPENCV_TEST_DATA_PATH environment variable + 2. One of these: + a. OpenCV testdata based on build location: "./" + "share/OpenCV/testdata" + b. OpenCV testdata at install location: CMAKE_INSTALL_PREFIX + "share/OpenCV/testdata" + + Search sub-directories: + + - addDataSearchSubDirectory() + - modulename from TS::init() + + */ +CV_EXPORTS std::string findDataFile(const std::string& relative_path, bool required = true); + #ifndef __CV_TEST_EXEC_ARGS #if defined(_MSC_VER) && (_MSC_VER <= 1400) @@ -562,9 +592,9 @@ CV_EXPORTS void printVersionInfo(bool useStdOut = true); #endif #ifdef HAVE_OPENCL -namespace cvtest { namespace ocl { +namespace ocl { void dumpOpenCLDevice(); -} } +} #define TEST_DUMP_OCL_INFO cvtest::ocl::dumpOpenCLDevice(); #else #define TEST_DUMP_OCL_INFO @@ -575,11 +605,13 @@ void parseCustomOptions(int argc, char **argv); #define CV_TEST_MAIN(resourcesubdir, ...) \ int main(int argc, char **argv) \ { \ - __CV_TEST_EXEC_ARGS(__VA_ARGS__) \ - cvtest::TS::ptr()->init(resourcesubdir); \ + using namespace cvtest; \ + TS* ts = TS::ptr(); \ + ts->init(resourcesubdir); \ ::testing::InitGoogleTest(&argc, argv); \ cvtest::printVersionInfo(); \ TEST_DUMP_OCL_INFO \ + __CV_TEST_EXEC_ARGS(__VA_ARGS__) \ parseCustomOptions(argc, argv); \ return RUN_ALL_TESTS(); \ } @@ -591,7 +623,9 @@ int main(int argc, char **argv) \ FAIL() << "No equivalent implementation."; \ } while (0) -#endif +} //namespace cvtest + +#endif // OPENCV_TS_HPP #include "opencv2/ts/ts_perf.hpp" diff --git a/modules/ts/include/opencv2/ts/ts_ext.hpp b/modules/ts/include/opencv2/ts/ts_ext.hpp index ed6009ccd8..781b61e4dd 100644 --- a/modules/ts/include/opencv2/ts/ts_ext.hpp +++ b/modules/ts/include/opencv2/ts/ts_ext.hpp @@ -8,7 +8,25 @@ #ifndef OPENCV_TS_EXT_HPP #define OPENCV_TS_EXT_HPP +namespace cvtest { void checkIppStatus(); +} + +#define CV_TEST_INIT cv::ipp::setIppStatus(0); +#define CV_TEST_CLEANUP ::cvtest::checkIppStatus(); +#define CV_TEST_BODY_IMPL \ + { \ + try { \ + CV_TEST_INIT \ + Body(); \ + CV_TEST_CLEANUP \ + } \ + catch (cvtest::SkipTestException& e) \ + { \ + printf("[ SKIP ] %s\n", e.what()); \ + } \ + } \ + #undef TEST #define TEST(test_case_name, test_name) \ @@ -33,7 +51,7 @@ void checkIppStatus(); ::testing::Test::TearDownTestCase, \ new ::testing::internal::TestFactoryImpl<\ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\ - void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() { cv::ipp::setIppStatus(0); Body(); checkIppStatus(); } \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() CV_TEST_BODY_IMPL \ void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::Body() #undef TEST_F @@ -59,7 +77,7 @@ void checkIppStatus(); test_fixture::TearDownTestCase, \ new ::testing::internal::TestFactoryImpl<\ GTEST_TEST_CLASS_NAME_(test_fixture, test_name)>);\ - void GTEST_TEST_CLASS_NAME_(test_fixture, test_name)::TestBody() { cv::ipp::setIppStatus(0); Body(); checkIppStatus(); } \ + void GTEST_TEST_CLASS_NAME_(test_fixture, test_name)::TestBody() CV_TEST_BODY_IMPL \ void GTEST_TEST_CLASS_NAME_(test_fixture, test_name)::Body() #undef TEST_P @@ -91,7 +109,7 @@ void checkIppStatus(); int GTEST_TEST_CLASS_NAME_(test_case_name, \ test_name)::gtest_registering_dummy_ = \ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ - void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() { cv::ipp::setIppStatus(0); Body(); checkIppStatus(); } \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() CV_TEST_BODY_IMPL \ void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::Body() #endif // OPENCV_TS_EXT_HPP diff --git a/modules/ts/src/ts.cpp b/modules/ts/src/ts.cpp index c02822ee08..c60e5d635a 100644 --- a/modules/ts/src/ts.cpp +++ b/modules/ts/src/ts.cpp @@ -66,9 +66,32 @@ #include #endif +// isDirectory +#if defined WIN32 || defined _WIN32 || defined WINCE +# include +#else +# include +# include +#endif + + +#include "opencv_tests_config.hpp" + namespace cvtest { +static std::string path_join(const std::string& prefix, const std::string& subpath) +{ + CV_Assert(subpath.empty() || subpath[0] != '/'); + if (prefix.empty()) + return subpath; + bool skipSlash = prefix.size() > 0 ? (prefix[prefix.size()-1] == '/' || prefix[prefix.size()-1] == '\\') : false; + std::string path = prefix + (skipSlash ? "" : "/") + subpath; + return path; +} + + + /*****************************************************************************************\ * Exception and memory handlers * \*****************************************************************************************/ @@ -449,6 +472,7 @@ static int tsErrorCallback( int status, const char* func_name, const char* err_m void TS::init( const string& modulename ) { + data_search_subdir.push_back(modulename); #ifndef WINRT char* datapath_dir = getenv("OPENCV_TEST_DATA_PATH"); #else @@ -457,11 +481,7 @@ void TS::init( const string& modulename ) if( datapath_dir ) { - char buf[1024]; - size_t l = strlen(datapath_dir); - bool haveSlash = l > 0 && (datapath_dir[l-1] == '/' || datapath_dir[l-1] == '\\'); - sprintf( buf, "%s%s%s/", datapath_dir, haveSlash ? "" : "/", modulename.c_str() ); - data_path = string(buf); + data_path = path_join(path_join(datapath_dir, modulename), ""); } cv::redirectError((cv::ErrorCallback)tsErrorCallback, this); @@ -583,7 +603,7 @@ void TS::printf( int streams, const char* fmt, ... ) } -TS ts; +static TS ts; TS* TS::ptr() { return &ts; } void fillGradient(Mat& img, int delta) @@ -659,7 +679,6 @@ void smoothBorder(Mat& img, const Scalar& color, int delta) } } -} //namespace cvtest bool test_ipp_check = false; @@ -694,4 +713,115 @@ void parseCustomOptions(int argc, char **argv) #endif } + +static bool isDirectory(const std::string& path) +{ +#if defined WIN32 || defined _WIN32 || defined WINCE + WIN32_FILE_ATTRIBUTE_DATA all_attrs; +#ifdef WINRT + wchar_t wpath[MAX_PATH]; + size_t copied = mbstowcs(wpath, path.c_str(), MAX_PATH); + CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1)); + BOOL status = ::GetFileAttributesExW(wpath, GetFileExInfoStandard, &all_attrs); +#else + BOOL status = ::GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &all_attrs); +#endif + DWORD attributes = all_attrs.dwFileAttributes; + return status && ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0); +#else + struct stat s; + if (0 != stat(path.c_str(), &s)) + return false; + return S_ISDIR(s.st_mode); +#endif +} + +CV_EXPORTS void addDataSearchPath(const std::string& path) +{ + if (isDirectory(path)) + TS::ptr()->data_search_path.push_back(path); +} +CV_EXPORTS void addDataSearchSubDirectory(const std::string& subdir) +{ + TS::ptr()->data_search_subdir.push_back(subdir); +} + +std::string findDataFile(const std::string& relative_path, bool required) +{ +#define TEST_TRY_FILE_WITH_PREFIX(prefix) \ +{ \ + std::string path = path_join(prefix, relative_path); \ + /*printf("Trying %s\n", path.c_str());*/ \ + FILE* f = fopen(path.c_str(), "rb"); \ + if(f) { \ + fclose(f); \ + return path; \ + } \ +} + + const std::vector& search_path = TS::ptr()->data_search_path; + for(size_t i = search_path.size(); i > 0; i--) + { + const std::string& prefix = search_path[i - 1]; + TEST_TRY_FILE_WITH_PREFIX(prefix); + } + + const std::vector& search_subdir = TS::ptr()->data_search_subdir; + +#ifndef WINRT + char* datapath_dir = getenv("OPENCV_TEST_DATA_PATH"); +#else + char* datapath_dir = OPENCV_TEST_DATA_PATH; +#endif + + std::string datapath; + if (datapath_dir) + { + datapath = datapath_dir; + //CV_Assert(isDirectory(datapath) && "OPENCV_TEST_DATA_PATH is specified but it doesn't exist"); + if (isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const std::string& subdir = search_subdir[i - 1]; + std::string prefix = path_join(datapath, subdir); + TEST_TRY_FILE_WITH_PREFIX(prefix); + } + } + } +#ifdef OPENCV_TEST_DATA_INSTALL_PATH + datapath = path_join("./", OPENCV_TEST_DATA_INSTALL_PATH); + if (isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const std::string& subdir = search_subdir[i - 1]; + std::string prefix = path_join(datapath, subdir); + TEST_TRY_FILE_WITH_PREFIX(prefix); + } + } +#ifdef OPENCV_INSTALL_PREFIX + else + { + datapath = path_join(OPENCV_INSTALL_PREFIX, OPENCV_TEST_DATA_INSTALL_PATH); + if (isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const std::string& subdir = search_subdir[i - 1]; + std::string prefix = path_join(datapath, subdir); + TEST_TRY_FILE_WITH_PREFIX(prefix); + } + } + } +#endif +#endif + if (required) + CV_ErrorNoReturn(cv::Error::StsError, cv::format("OpenCV tests: Can't find required data file: %s", relative_path.c_str())); + throw SkipTestException(cv::format("OpenCV tests: Can't find data file: %s", relative_path.c_str())); +} + + +} //namespace cvtest + /* End of file. */ diff --git a/modules/ts/src/ts_perf.cpp b/modules/ts/src/ts_perf.cpp index 1c40fb1ed4..f4967748ee 100644 --- a/modules/ts/src/ts_perf.cpp +++ b/modules/ts/src/ts_perf.cpp @@ -19,6 +19,7 @@ # include #endif +using namespace cvtest; using namespace perf; int64 TestBase::timeLimitDefault = 0; @@ -48,7 +49,10 @@ static bool param_collect_impl; #ifdef ENABLE_INSTRUMENTATION static int param_instrument; #endif + +namespace cvtest { extern bool test_ipp_check; +} #ifdef HAVE_CUDA static int param_cuda_device; From 2ca5a7e862b9f7c4de90ddef73ae7c12c8981866 Mon Sep 17 00:00:00 2001 From: Li Peng Date: Tue, 29 Nov 2016 15:28:53 +0800 Subject: [PATCH 148/409] more optimization for warpAffine and warpPerspective Add new OpenCL kernels for bicubic interploation, it is 20% faster than current warp image kernel with bicubic interploation. Signed-off-by: Li Peng --- modules/imgproc/src/imgwarp.cpp | 6 +- modules/imgproc/src/opencl/warp_transform.cl | 194 +++++++++++++++++++ modules/imgproc/test/ocl/test_warp.cpp | 4 +- 3 files changed, 199 insertions(+), 5 deletions(-) diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 943a515fbd..b027b9c39a 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -5662,15 +5662,15 @@ static bool ocl_warpTransform_cols4(InputArray _src, OutputArray _dst, InputArra if ( !dev.isIntel() || !(type == CV_8UC1) || !(dtype == CV_8UC1) || !(_dst.cols() % 4 == 0) || !(borderType == cv::BORDER_CONSTANT && - (interpolation == cv::INTER_NEAREST || interpolation == cv::INTER_LINEAR))) + (interpolation == cv::INTER_NEAREST || interpolation == cv::INTER_LINEAR || interpolation == cv::INTER_CUBIC))) return false; const char * const warp_op[2] = { "Affine", "Perspective" }; - const char * const interpolationMap[3] = { "nearest", "linear", NULL }; + const char * const interpolationMap[3] = { "nearest", "linear", "cubic" }; ocl::ProgramSource program = ocl::imgproc::warp_transform_oclsrc; String kernelName = format("warp%s_%s_8u", warp_op[op_type], interpolationMap[interpolation]); - bool is32f = (interpolation == INTER_LINEAR); + bool is32f = (interpolation == INTER_CUBIC || interpolation == INTER_LINEAR) && op_type == OCL_OP_AFFINE; int wdepth = interpolation == INTER_NEAREST ? depth : std::max(is32f ? CV_32F : CV_32S, depth); int sctype = CV_MAKETYPE(wdepth, cn); diff --git a/modules/imgproc/src/opencl/warp_transform.cl b/modules/imgproc/src/opencl/warp_transform.cl index bfe35f6fc5..738613f7c9 100644 --- a/modules/imgproc/src/opencl/warp_transform.cl +++ b/modules/imgproc/src/opencl/warp_transform.cl @@ -117,6 +117,115 @@ __kernel void warpAffine_linear_8u(__global const uchar * src, int src_step, int vstore4(convert_uchar4_sat_rte(pixel), 0, dst + dst_index); } +__constant float coeffs[128] = + { 0.000000f, 1.000000f, 0.000000f, 0.000000f, -0.021996f, 0.997841f, 0.024864f, -0.000710f, -0.041199f, 0.991516f, 0.052429f, -0.002747f, + -0.057747f, 0.981255f, 0.082466f, -0.005974f, -0.071777f, 0.967285f, 0.114746f, -0.010254f, -0.083427f, 0.949837f, 0.149040f, -0.015450f, + -0.092834f, 0.929138f, 0.185120f, -0.021423f, -0.100136f, 0.905418f, 0.222755f, -0.028038f, -0.105469f, 0.878906f, 0.261719f, -0.035156f, + -0.108971f, 0.849831f, 0.301781f, -0.042641f, -0.110779f, 0.818420f, 0.342712f, -0.050354f, -0.111031f, 0.784904f, 0.384285f, -0.058159f, + -0.109863f, 0.749512f, 0.426270f, -0.065918f, -0.107414f, 0.712471f, 0.468437f, -0.073494f, -0.103821f, 0.674011f, 0.510559f, -0.080750f, + -0.099220f, 0.634361f, 0.552406f, -0.087547f, -0.093750f, 0.593750f, 0.593750f, -0.093750f, -0.087547f, 0.552406f, 0.634361f, -0.099220f, + -0.080750f, 0.510559f, 0.674011f, -0.103821f, -0.073494f, 0.468437f, 0.712471f, -0.107414f, -0.065918f, 0.426270f, 0.749512f, -0.109863f, + -0.058159f, 0.384285f, 0.784904f, -0.111031f, -0.050354f, 0.342712f, 0.818420f, -0.110779f, -0.042641f, 0.301781f, 0.849831f, -0.108971f, + -0.035156f, 0.261719f, 0.878906f, -0.105469f, -0.028038f, 0.222755f, 0.905418f, -0.100136f, -0.021423f, 0.185120f, 0.929138f, -0.092834f, + -0.015450f, 0.149040f, 0.949837f, -0.083427f, -0.010254f, 0.114746f, 0.967285f, -0.071777f, -0.005974f, 0.082466f, 0.981255f, -0.057747f, + -0.002747f, 0.052429f, 0.991516f, -0.041199f, -0.000710f, 0.024864f, 0.997841f, -0.021996f }; + +uchar4 read_pixels_cubic(__global const uchar * src, int tx, int ty, + int src_offset, int src_step, int src_cols, int src_rows, uchar scalar) +{ + uchar4 pix; + + if (tx >= 0 && (tx + 3) < src_cols && ty >= 0 && ty < src_rows) + { + pix = vload4(0, src + src_offset + ty * src_step + tx); + } + else + { + pix.s0 = GET_VAL((tx + 0), ty); + pix.s1 = GET_VAL((tx + 1), ty); + pix.s2 = GET_VAL((tx + 2), ty); + pix.s3 = GET_VAL((tx + 3), ty); + } + + return pix; +} + +__kernel void warpAffine_cubic_8u(__global const uchar * src, int src_step, int src_offset, int src_rows, int src_cols, + __global uchar * dst, int dst_step, int dst_offset, int dst_rows, int dst_cols, + __constant float * M, ST scalar_) +{ + int x = get_global_id(0) * 4; + int y = get_global_id(1); + uchar scalar = convert_uchar_sat_rte(scalar_); + + if (x >= dst_cols || y >= dst_rows) return; + + /* { M0, M1, M2 } + * { M3, M4, M5 } + */ + + float4 nx, ny; + nx = M[0] * convert_float4((vec_offset + (short4)x)) + M[1] * convert_float4((short4)y) + M[2]; + ny = M[3] * convert_float4((vec_offset + (short4)x)) + M[4] * convert_float4((short4)y) + M[5]; + + int4 ax, ay; + ax = convert_int4_sat_rte((nx - floor(nx)) * 32.0f) & 31; + ay = convert_int4_sat_rte((ny - floor(ny)) * 32.0f) & 31; + + int4 tx, ty; + int4 delta_x, delta_y; + + delta_x = select((int4)1, (int4)0, ((nx - floor(nx))) * 64 > 63); + delta_y = select((int4)1, (int4)0, ((ny - floor(ny))) * 64 > 63); + + tx = convert_int4_sat_rtn(nx) - delta_x; + ty = convert_int4_sat_rtn(ny) - delta_y; + + __constant float * coeffs_x, * coeffs_y; + float4 sum = (float4)0.0f; + uchar4 pix; + float xsum; + + coeffs_x = coeffs + (ax.s0 << 2); + coeffs_y = coeffs + (ay.s0 << 2); + for (int i = 0; i < 4; i++) + { + pix = read_pixels_cubic(src, tx.s0, ty.s0 + i, src_offset, src_step, src_cols, src_rows, scalar); + xsum = dot(convert_float4(pix), (float4)(coeffs_x[0], coeffs_x[1], coeffs_x[2], coeffs_x[3])); + sum.s0 = fma(xsum, coeffs_y[i], sum.s0); + } + + coeffs_x = coeffs + (ax.s1 << 2); + coeffs_y = coeffs + (ay.s1 << 2); + for (int i = 0; i < 4; i++) + { + pix = read_pixels_cubic(src, tx.s1, ty.s1 + i, src_offset, src_step, src_cols, src_rows, scalar); + xsum = dot(convert_float4(pix), (float4)(coeffs_x[0], coeffs_x[1], coeffs_x[2], coeffs_x[3])); + sum.s1 = fma(xsum, coeffs_y[i], sum.s1); + } + + coeffs_x = coeffs + (ax.s2 << 2); + coeffs_y = coeffs + (ay.s2 << 2); + for (int i = 0; i < 4; i++) + { + pix = read_pixels_cubic(src, tx.s2, ty.s2 + i, src_offset, src_step, src_cols, src_rows, scalar); + xsum = dot(convert_float4(pix), (float4)(coeffs_x[0], coeffs_x[1], coeffs_x[2], coeffs_x[3])); + sum.s2 = fma(xsum, coeffs_y[i], sum.s2); + } + + coeffs_x = coeffs + (ax.s3 << 2); + coeffs_y = coeffs + (ay.s3 << 2); + for (int i = 0; i < 4; i++) + { + pix = read_pixels_cubic(src, tx.s3, ty.s3 + i, src_offset, src_step, src_cols, src_rows, scalar); + xsum = dot(convert_float4(pix), (float4)(coeffs_x[0], coeffs_x[1], coeffs_x[2], coeffs_x[3])); + sum.s3 = fma(xsum, coeffs_y[i], sum.s3); + } + + int dst_index = x + y * dst_step + dst_offset; + vstore4(convert_uchar4_sat_rte(sum), 0, dst + dst_index); +} + __kernel void warpPerspective_nearest_8u(__global const uchar * src, int src_step, int src_offset, int src_rows, int src_cols, __global uchar * dst, int dst_step, int dst_offset, int dst_rows, int dst_cols, __constant float * M, ST scalar_) @@ -212,3 +321,88 @@ __kernel void warpPerspective_linear_8u(__global const uchar * src, int src_step int dst_index = x + y * dst_step + dst_offset; vstore4(convert_uchar4_sat_rte(pixel), 0, dst + dst_index); } + +__kernel void warpPerspective_cubic_8u(__global const uchar * src, int src_step, int src_offset, int src_rows, int src_cols, + __global uchar * dst, int dst_step, int dst_offset, int dst_rows, int dst_cols, + __constant float * M, ST scalar_) +{ + int x = get_global_id(0) * 4; + int y = get_global_id(1); + uchar scalar = convert_uchar_sat_rte(scalar_); + + if (x >= dst_cols || y >= dst_rows) return; + + /* { M0, M1, M2 } + * { M3, M4, M5 } + * { M6, M7, M8 } + */ + + float4 nx, ny, nz; + nx = M[0] * convert_float4(vec_offset + (short4)(x)) + M[1] * convert_float4((short4)y) + M[2]; + + ny = M[3] * convert_float4(vec_offset + (short4)(x)) + M[4] * convert_float4((short4)y) + M[5]; + + nz = M[6] * convert_float4(vec_offset + (short4)(x)) + M[7] * convert_float4((short4)y) + M[8]; + + float4 fz = select((float4)(0.0f), (float4)(1.0f / nz), nz != 0.0f); + + nx = nx * fz; + ny = ny * fz; + + int4 ax, ay; + ax = convert_int4_sat_rte((nx - floor(nx)) * 32.0f) & 31; + ay = convert_int4_sat_rte((ny - floor(ny)) * 32.0f) & 31; + + int4 tx, ty; + int4 delta_x, delta_y; + + delta_x = select((int4)1, (int4)0, ((nx - floor(nx))) * 64 > 63); + delta_y = select((int4)1, (int4)0, ((ny - floor(ny))) * 64 > 63); + + tx = convert_int4_sat_rtn(nx) - delta_x; + ty = convert_int4_sat_rtn(ny) - delta_y; + + __constant float * coeffs_x, * coeffs_y; + float4 sum = (float4)0.0f; + uchar4 pix; + float xsum; + + coeffs_x = coeffs + (ax.s0 << 2); + coeffs_y = coeffs + (ay.s0 << 2); + for (int i = 0; i < 4; i++) + { + pix = read_pixels_cubic(src, tx.s0, ty.s0 + i, src_offset, src_step, src_cols, src_rows, scalar); + xsum = dot(convert_float4(pix), (float4)(coeffs_x[0], coeffs_x[1], coeffs_x[2], coeffs_x[3])); + sum.s0 = fma(xsum, coeffs_y[i], sum.s0); + } + + coeffs_x = coeffs + (ax.s1 << 2); + coeffs_y = coeffs + (ay.s1 << 2); + for (int i = 0; i < 4; i++) + { + pix = read_pixels_cubic(src, tx.s1, ty.s1 + i, src_offset, src_step, src_cols, src_rows, scalar); + xsum = dot(convert_float4(pix), (float4)(coeffs_x[0], coeffs_x[1], coeffs_x[2], coeffs_x[3])); + sum.s1 = fma(xsum, coeffs_y[i], sum.s1); + } + + coeffs_x = coeffs + (ax.s2 << 2); + coeffs_y = coeffs + (ay.s2 << 2); + for (int i = 0; i < 4; i++) + { + pix = read_pixels_cubic(src, tx.s2, ty.s2 + i, src_offset, src_step, src_cols, src_rows, scalar); + xsum = dot(convert_float4(pix), (float4)(coeffs_x[0], coeffs_x[1], coeffs_x[2], coeffs_x[3])); + sum.s2 = fma(xsum, coeffs_y[i], sum.s2); + } + + coeffs_x = coeffs + (ax.s3 << 2); + coeffs_y = coeffs + (ay.s3 << 2); + for (int i = 0; i < 4; i++) + { + pix = read_pixels_cubic(src, tx.s3, ty.s3 + i, src_offset, src_step, src_cols, src_rows, scalar); + xsum = dot(convert_float4(pix), (float4)(coeffs_x[0], coeffs_x[1], coeffs_x[2], coeffs_x[3])); + sum.s3 = fma(xsum, coeffs_y[i], sum.s3); + } + + int dst_index = x + y * dst_step + dst_offset; + vstore4(convert_uchar4_sat_rte(sum), 0, dst + dst_index); +} diff --git a/modules/imgproc/test/ocl/test_warp.cpp b/modules/imgproc/test/ocl/test_warp.cpp index 8534658278..c69d597f21 100644 --- a/modules/imgproc/test/ocl/test_warp.cpp +++ b/modules/imgproc/test/ocl/test_warp.cpp @@ -437,7 +437,7 @@ OCL_INSTANTIATE_TEST_CASE_P(ImgprocWarp, WarpAffine, Combine( OCL_INSTANTIATE_TEST_CASE_P(ImgprocWarp, WarpAffine_cols4, Combine( Values((MatType)CV_8UC1), - Values((Interpolation)INTER_NEAREST, (Interpolation)INTER_LINEAR), + Values((Interpolation)INTER_NEAREST, (Interpolation)INTER_LINEAR, (Interpolation)INTER_CUBIC), Bool(), Bool())); @@ -449,7 +449,7 @@ OCL_INSTANTIATE_TEST_CASE_P(ImgprocWarp, WarpPerspective, Combine( OCL_INSTANTIATE_TEST_CASE_P(ImgprocWarp, WarpPerspective_cols4, Combine( Values((MatType)CV_8UC1), - Values((Interpolation)INTER_NEAREST, (Interpolation)INTER_LINEAR), + Values((Interpolation)INTER_NEAREST, (Interpolation)INTER_LINEAR, (Interpolation)INTER_CUBIC), Bool(), Bool())); From 179a6e0ccadb521fc97143b2bbfffafc71f34a82 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Wed, 30 Nov 2016 11:25:45 +0300 Subject: [PATCH 149/409] OpenVX wrappers linkage fix --- 3rdparty/openvx/include/ivx.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index f47831d1c8..b01731c0e1 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -2151,7 +2151,7 @@ public: namespace nodes { /// Creates a Gaussian Filter 3x3 Node (vxGaussian3x3Node) -Node gaussian3x3(vx_graph graph, vx_image inImg, vx_image outImg) +inline Node gaussian3x3(vx_graph graph, vx_image inImg, vx_image outImg) { return Node(vxGaussian3x3Node(graph, inImg, outImg)); } } // namespace nodes From 6d55e9929171f0b53b74946e18d7da1e1191377b Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Thu, 13 Oct 2016 14:05:07 +0300 Subject: [PATCH 150/409] Added OpenVX based processing to boxFilter --- modules/imgproc/src/smooth.cpp | 115 +++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/modules/imgproc/src/smooth.cpp b/modules/imgproc/src/smooth.cpp index e7860b8807..9527b4563d 100644 --- a/modules/imgproc/src/smooth.cpp +++ b/modules/imgproc/src/smooth.cpp @@ -44,6 +44,11 @@ #include "precomp.hpp" #include "opencl_kernels_imgproc.hpp" +#ifdef HAVE_OPENVX +#define IVX_HIDE_INFO_WARNINGS +#define IVX_USE_OPENCV +#include "ivx.hpp" +#endif /* * This file includes the code, contributed by Simon Perreault * (the function icvMedianBlur_8u_O1) @@ -1634,6 +1639,111 @@ cv::Ptr cv::createBoxFilter( int srcType, int dstType, Size ks srcType, dstType, sumType, borderType ); } +#ifdef HAVE_OPENVX +namespace cv +{ + static bool openvx_boxfilter(InputArray _src, OutputArray _dst, int ddepth, + Size ksize, Point anchor, + bool normalize, int borderType) + { + int stype = _src.type(); + if (ddepth < 0) + ddepth = CV_8UC1; + if (stype != CV_8UC1 || (ddepth != CV_8U && ddepth != CV_16S) || + (anchor.x >= 0 && anchor.x != ksize.width / 2) || + (anchor.y >= 0 && anchor.y != ksize.height / 2) || + ksize.width % 2 != 1 || ksize.height % 2 != 1 || + ksize.width < 3 || ksize.height < 3) + return false; + + Mat src = _src.getMat(); + _dst.create(src.size(), CV_MAKETYPE(ddepth, 1)); + Mat dst = _dst.getMat(); + + if (src.cols < ksize.width || src.rows < ksize.height) + return false; + + if ((borderType & BORDER_ISOLATED) == 0 && src.isSubmatrix()) + return false; //Process isolated borders only + vx_border_t border; + switch (borderType & ~BORDER_ISOLATED) + { + case BORDER_CONSTANT: + border.mode = VX_BORDER_CONSTANT; +#if VX_VERSION > VX_VERSION_1_0 + border.constant_value.U8 = (vx_uint8)(0); +#else + border.constant_value = (vx_uint32)(0); +#endif + break; + case BORDER_REPLICATE: + border.mode = VX_BORDER_REPLICATE; + break; + default: + return false; + } + + try + { + ivx::Context ctx = ivx::Context::create(); + if ((vx_size)(ksize.width) > ctx.convolutionMaxDimension() || (vx_size)(ksize.height) > ctx.convolutionMaxDimension()) + return false; + + Mat a; + if (dst.data != src.data) + a = src; + else + src.copyTo(a); + + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(a.cols, a.rows, 1, (vx_int32)(a.step)), a.data), + ib = ivx::Image::createFromHandle(ctx, ddepth == CV_16S ? VX_DF_IMAGE_S16 : VX_DF_IMAGE_U8, + ivx::Image::createAddressing(dst.cols, dst.rows, ddepth == CV_16S ? 2 : 1, (vx_int32)(dst.step)), dst.data); + + //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); + if (ddepth == CV_8U && ksize.width == 3 && ksize.height == 3 && normalize) + { + ivx::IVX_CHECK_STATUS(vxuBox3x3(ctx, ia, ib)); + } + else + { +#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); + return false; + } +#endif + Mat convData(ksize, CV_16SC1); + convData = normalize ? (1 << 15) / (ksize.width * ksize.height) : 1; + ivx::Convolution cnv = ivx::Convolution::create(ctx, convData.cols, convData.rows); + cnv.copyFrom(convData); + if (normalize) + cnv.setScale(1 << 15); + ivx::IVX_CHECK_STATUS(vxuConvolve(ctx, ia, cnv, ib)); + } + ctx.setBorderMode(prevBorder); + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + + return true; + } +} +#endif + // TODO: IPP performance regression #if defined(HAVE_IPP) && IPP_DISABLE_BLOCK namespace cv @@ -1745,6 +1855,11 @@ void cv::boxFilter( InputArray _src, OutputArray _dst, int ddepth, CV_OCL_RUN(_dst.isUMat(), ocl_boxFilter(_src, _dst, ddepth, ksize, anchor, borderType, normalize)) +#ifdef HAVE_OPENVX + if (openvx_boxfilter(_src, _dst, ddepth, ksize, anchor, normalize, borderType)) + return; +#endif + Mat src = _src.getMat(); int stype = src.type(), sdepth = CV_MAT_DEPTH(stype), cn = CV_MAT_CN(stype); if( ddepth < 0 ) From e4985f604a411eb3e953182a0f72030654de3329 Mon Sep 17 00:00:00 2001 From: Tsukasa Sugiura Date: Thu, 1 Dec 2016 00:55:00 +0900 Subject: [PATCH 151/409] Add support image save parameters in VideoWriter Add support image save parameters in cv::VideoWriter. This change will become available setting same parameters as cv::imwrite() to cv::VideoWriter::set( cv::IMWRITE_*, value ). --- modules/videoio/src/cap_images.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/videoio/src/cap_images.cpp b/modules/videoio/src/cap_images.cpp index e379561593..922a5767b3 100644 --- a/modules/videoio/src/cap_images.cpp +++ b/modules/videoio/src/cap_images.cpp @@ -332,18 +332,20 @@ public: virtual bool open( const char* _filename ); virtual void close(); + virtual bool setProperty( int, double ); virtual bool writeFrame( const IplImage* ); protected: char* filename; unsigned currentframe; + std::vector params; }; bool CvVideoWriter_Images::writeFrame( const IplImage* image ) { char str[_MAX_PATH]; sprintf(str, filename, currentframe); - int ret = cvSaveImage(str, image); + int ret = cvSaveImage(str, image, ¶ms[0]); currentframe++; @@ -358,6 +360,7 @@ void CvVideoWriter_Images::close() filename = 0; } currentframe = 0; + params.clear(); } @@ -380,6 +383,15 @@ bool CvVideoWriter_Images::open( const char* _filename ) } currentframe = offset; + params.clear(); + return true; +} + + +bool CvVideoWriter_Images::setProperty( int id, double value ) +{ + params.push_back( id ); + params.push_back( static_cast( value ) ); return true; } From 47f53b675d4e0d430b8c4caea31ae13ab4bf5216 Mon Sep 17 00:00:00 2001 From: JR Heard Date: Wed, 30 Nov 2016 16:52:28 -0800 Subject: [PATCH 152/409] fix a typo in the canny docs --- .../imgproc/imgtrans/canny_detector/canny_detector.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.markdown b/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.markdown index e86b16daf6..f9f2a2a2b7 100644 --- a/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.markdown +++ b/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.markdown @@ -12,7 +12,7 @@ Theory ------ The *Canny Edge detector* was developed by John F. Canny in 1986. Also known to many as the -*optimal detector*, Canny algorithm aims to satisfy three main criteria: +*optimal detector*, the Canny algorithm aims to satisfy three main criteria: - **Low error rate:** Meaning a good detection of only existent edges. - **Good localization:** The distance between edge pixels detected and real edge pixels have to be minimized. From c560ff23ade974d3d365bf4c2f14c2fc39025765 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 1 Dec 2016 15:24:36 +0300 Subject: [PATCH 153/409] videoio: images: fix buffer overflow problem --- modules/videoio/src/cap_images.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/videoio/src/cap_images.cpp b/modules/videoio/src/cap_images.cpp index 922a5767b3..455418624b 100644 --- a/modules/videoio/src/cap_images.cpp +++ b/modules/videoio/src/cap_images.cpp @@ -345,7 +345,10 @@ bool CvVideoWriter_Images::writeFrame( const IplImage* image ) { char str[_MAX_PATH]; sprintf(str, filename, currentframe); - int ret = cvSaveImage(str, image, ¶ms[0]); + std::vector image_params = params; + image_params.push_back(0); // append parameters 'stop' mark + image_params.push_back(0); + int ret = cvSaveImage(str, image, &image_params[0]); currentframe++; From e547bbbc3f66064d5aaf497de6eeeb54d5a9f5b1 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 1 Dec 2016 15:17:33 +0300 Subject: [PATCH 154/409] videoio: preserve conflict with common properties Usage: writer.set(cv::CAP_PROP_IMAGES_BASE + cv::IMWRITE_JPEG_QUALITY, 80); --- modules/videoio/include/opencv2/videoio.hpp | 14 ++++++++++++++ modules/videoio/src/cap_images.cpp | 10 +++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index c38875f7d2..458e63d843 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -556,6 +556,20 @@ enum { CAP_PROP_GPHOTO2_PREVIEW = 17001, //!< Capture only preview fro //! @} gPhoto2 + +/** @name Images backend + @{ +*/ + +/** @brief Images backend properties + +*/ +enum { CAP_PROP_IMAGES_BASE = 18000, + CAP_PROP_IMAGES_LAST = 19000 // excluding + }; + +//! @} Images + //! @} videoio_flags_others diff --git a/modules/videoio/src/cap_images.cpp b/modules/videoio/src/cap_images.cpp index 455418624b..f87c5544d2 100644 --- a/modules/videoio/src/cap_images.cpp +++ b/modules/videoio/src/cap_images.cpp @@ -393,9 +393,13 @@ bool CvVideoWriter_Images::open( const char* _filename ) bool CvVideoWriter_Images::setProperty( int id, double value ) { - params.push_back( id ); - params.push_back( static_cast( value ) ); - return true; + if (id >= cv::CAP_PROP_IMAGES_BASE && id < cv::CAP_PROP_IMAGES_LAST) + { + params.push_back( id - cv::CAP_PROP_IMAGES_BASE ); + params.push_back( static_cast( value ) ); + return true; + } + return false; // not supported } From 695c518384c2cdfe15053968652da657068b1ab9 Mon Sep 17 00:00:00 2001 From: mshabunin Date: Wed, 23 Nov 2016 12:44:44 +0300 Subject: [PATCH 155/409] Updated TBB search script and code checks --- 3rdparty/tbb/CMakeLists.txt | 30 +--- apps/traincascade/boost.cpp | 14 +- cmake/OpenCVDetectTBB.cmake | 161 +++++++++--------- cmake/OpenCVUtils.cmake | 6 +- modules/core/include/opencv2/core/private.hpp | 13 +- modules/core/src/parallel.cpp | 53 +++--- modules/videoio/src/cap_openni.cpp | 4 - modules/videoio/src/cap_openni2.cpp | 4 - samples/gpu/driver_api_multi.cpp | 13 +- samples/gpu/driver_api_stereo_multi.cpp | 13 +- samples/gpu/multi.cpp | 13 +- 11 files changed, 130 insertions(+), 194 deletions(-) diff --git a/3rdparty/tbb/CMakeLists.txt b/3rdparty/tbb/CMakeLists.txt index eddeaef56a..f6d59695d5 100644 --- a/3rdparty/tbb/CMakeLists.txt +++ b/3rdparty/tbb/CMakeLists.txt @@ -70,20 +70,6 @@ ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow) #set(tbb_md5 "4669e7d4adee018de7a7b8b972987218") #set(tbb_version_file "version_string.tmp") -# 4.0 update 2 - works fine -#set(tbb_ver "tbb40_20111130oss") -#set(tbb_url "http://threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb40_20111130oss_src.tgz") -#set(tbb_md5 "1e6926b21e865e79772119cd44fc3ad8") -#set(tbb_version_file "version_string.tmp") -#set(tbb_need_GENERIC_DWORD_LOAD_STORE TRUE) - -# 4.0 update 1 - works fine -#set(tbb_ver "tbb40_20111003oss") -#set(tbb_url "http://threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb40_20111003oss_src.tgz") -#set(tbb_md5 "7b5d94eb35a563b29ef402e0fd8f15c9") -#set(tbb_version_file "version_string.tmp") -#set(tbb_need_GENERIC_DWORD_LOAD_STORE TRUE) - set(tbb_tarball "${CMAKE_CURRENT_SOURCE_DIR}/${tbb_ver}_src.tgz") set(tbb_src_dir "${CMAKE_CURRENT_BINARY_DIR}/${tbb_ver}") @@ -127,8 +113,6 @@ if(NOT EXISTS "${tbb_src_dir}") endif() endif() -set(TBB_INCLUDE_DIRS "${tbb_src_dir}/include" PARENT_SCOPE) - ocv_include_directories("${tbb_src_dir}/include" "${tbb_src_dir}/src/" "${tbb_src_dir}/src/rml/include" @@ -173,6 +157,9 @@ endif() if (CMAKE_COMPILER_IS_GNUCXX) add_definitions(-DTBB_USE_GCC_BUILTINS=1) #required for ARM GCC + if (NOT CMAKE_OPENCV_GCC_VERSION_NUM LESS 600) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flifetime-dse=1") # workaround for GCC 6.x + endif() endif() if(ANDROID_COMPILER_IS_CLANG) @@ -180,12 +167,6 @@ if(ANDROID_COMPILER_IS_CLANG) ocv_warnings_disable(CMAKE_CXX_FLAGS -Wmissing-prototypes) endif() -if(tbb_need_GENERIC_DWORD_LOAD_STORE) - #needed by TBB 4.0 update 1,2; fixed in TBB 4.0 update 3 but it has 2 new problems - add_definitions(-D__TBB_USE_GENERIC_DWORD_LOAD_STORE=1) - set(tbb_need_GENERIC_DWORD_LOAD_STORE ON PARENT_SCOPE) -endif() - set(TBB_SOURCE_FILES ${lib_srcs} ${lib_hdrs}) if (ARM AND NOT WIN32) @@ -199,6 +180,8 @@ endif() set(TBB_SOURCE_FILES ${TBB_SOURCE_FILES} "${CMAKE_CURRENT_SOURCE_DIR}/${tbb_version_file}") add_library(tbb ${TBB_SOURCE_FILES}) +target_compile_definitions(tbb PRIVATE TBB_USE_GCC_BUILTINS=1 __TBB_GCC_BUILTIN_ATOMICS_PRESENT=1) +target_include_directories(tbb SYSTEM PUBLIC $) if (WIN32) if (ARM) @@ -251,5 +234,4 @@ ocv_install_target(tbb EXPORT OpenCVModules ARCHIVE DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev ) -# get TBB version -ocv_parse_header("${tbb_src_dir}/include/tbb/tbb_stddef.h" TBB_VERSION_LINES TBB_VERSION_MAJOR TBB_VERSION_MINOR TBB_INTERFACE_VERSION CACHE) +ocv_tbb_read_version("${tbb_src_dir}/include") diff --git a/apps/traincascade/boost.cpp b/apps/traincascade/boost.cpp index c2e7fb7d6a..f53caad049 100644 --- a/apps/traincascade/boost.cpp +++ b/apps/traincascade/boost.cpp @@ -33,16 +33,12 @@ using cv::ParallelLoopBody; #include "cxmisc.h" #include "cvconfig.h" + #ifdef HAVE_TBB -# include "tbb/tbb_stddef.h" -# if TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 -# include "tbb/tbb.h" -# include "tbb/task.h" -# undef min -# undef max -# else -# undef HAVE_TBB -# endif +# include "tbb/tbb.h" +# include "tbb/task.h" +# undef min +# undef max #endif #ifdef HAVE_TBB diff --git a/cmake/OpenCVDetectTBB.cmake b/cmake/OpenCVDetectTBB.cmake index 2acf3477f3..89e4970b28 100644 --- a/cmake/OpenCVDetectTBB.cmake +++ b/cmake/OpenCVDetectTBB.cmake @@ -1,94 +1,85 @@ +# Search TBB library (4.1 - 4.4, 2017) +# +# Own TBB (3rdparty/tbb): +# - set cmake option BUILD_TBB to ON +# +# External TBB (from system): +# - Fedora: install 'tbb-devel' package +# - Ubuntu: install 'libtbb-dev' package +# +# External TBB (from official site): +# - Linux/OSX: +# - in tbbvars.sh replace 'SUBSTITUTE_INSTALL_DIR_HERE' with absolute path to TBB dir +# - in terminal run 'source tbbvars.sh intel64 linux' ('source tbbvars.sh' in OSX) +# - Windows: +# - in terminal run 'tbbvars.bat intel64 vs2015' +# +# Return: +# - HAVE_TBB set to TRUE +# - "tbb" target exists and added to OPENCV_LINKER_LIBS + +function(ocv_tbb_verify) + if (NOT "$ENV{TBBROOT}" STREQUAL "") + # check that library and include dir are inside TBBROOT location + get_filename_component(_root "$ENV{TBBROOT}" ABSOLUTE) + get_filename_component(_lib "${TBB_ENV_LIB}" ABSOLUTE) + get_filename_component(_inc "${TBB_ENV_INCLUDE}" ABSOLUTE) + string(FIND "${_lib}" "${_root}" _lib_pos) + string(FIND "${_inc}" "${_root}" _inc_pos) + if (NOT (_lib_pos EQUAL 0 AND _inc_pos EQUAL 0)) + message(SEND_ERROR + "Possible issue with TBB detection - TBBROOT is set, " + "but library/include path is not inside it:\n " + "TBBROOT: $ENV{TBBROOT}\n " + "(absolute): ${_root}\n " + "INCLUDE: ${_inc}\n " + "LIBRARY: ${_lib}\n") + endif() + endif() +endfunction() + +function(ocv_tbb_env_guess _found) + find_path(TBB_ENV_INCLUDE NAMES "tbb/tbb.h" PATHS ENV CPATH NO_DEFAULT_PATH) + find_path(TBB_ENV_INCLUDE NAMES "tbb/tbb.h") + find_library(TBB_ENV_LIB NAMES "tbb" PATHS ENV LIBRARY_PATH ENV LD_LIBRARY_PATH NO_DEFAULT_PATH) + find_library(TBB_ENV_LIB NAMES "tbb") + find_library(TBB_ENV_LIB_DEBUG NAMES "tbb_debug" PATHS ENV LIBRARY_PATH ENV LD_LIBRARY_PATH NO_DEFAULT_PATH) + find_library(TBB_ENV_LIB_DEBUG NAMES "tbb_debug") + if (TBB_ENV_INCLUDE AND TBB_ENV_LIB) + ocv_tbb_verify() + ocv_tbb_read_version("${TBB_ENV_INCLUDE}") + add_library(tbb UNKNOWN IMPORTED) + set_target_properties(tbb PROPERTIES + IMPORTED_LOCATION "${TBB_ENV_LIB}" + IMPORTED_LOCATION_DEBUG "${TBB_ENV_LIB_DEBUG}" + INTERFACE_INCLUDE_DIRECTORIES "${TBB_ENV_INCLUDE}" + ) + message(STATUS "Found TBB: ${TBB_ENV_LIB}") + set(${_found} TRUE PARENT_SCOPE) + endif() +endfunction() + +function(ocv_tbb_read_version _path) + find_file(TBB_VER_FILE tbb/tbb_stddef.h "${_path}" NO_DEFAULT_PATH CMAKE_FIND_ROOT_PATH_BOTH) + ocv_parse_header("${TBB_VER_FILE}" TBB_VERSION_LINES TBB_VERSION_MAJOR TBB_VERSION_MINOR TBB_INTERFACE_VERSION CACHE) +endfunction() + +#===================================================================== + if(BUILD_TBB) add_subdirectory("${OpenCV_SOURCE_DIR}/3rdparty/tbb") - include_directories(SYSTEM ${TBB_INCLUDE_DIRS}) - set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} tbb) - add_definitions(-DTBB_USE_GCC_BUILTINS=1 -D__TBB_GCC_BUILTIN_ATOMICS_PRESENT=1) - if(tbb_need_GENERIC_DWORD_LOAD_STORE) - add_definitions(-D__TBB_USE_GENERIC_DWORD_LOAD_STORE=1) - endif() - set(HAVE_TBB 1) -elseif(UNIX AND NOT APPLE) - PKG_CHECK_MODULES(TBB tbb) - - if(TBB_FOUND) - set(HAVE_TBB 1) - if(NOT ${TBB_INCLUDE_DIRS} STREQUAL "") - ocv_include_directories(${TBB_INCLUDE_DIRS}) - endif() - link_directories(${TBB_LIBRARY_DIRS}) - set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} ${TBB_LIBRARIES}) - endif() + message(STATUS "Found TBB: build") + set(HAVE_TBB TRUE) endif() if(NOT HAVE_TBB) - set(TBB_DEFAULT_INCLUDE_DIRS - "/opt/intel/tbb/include" "/usr/local/include" "/usr/include" - "C:/Program Files/Intel/TBB" "C:/Program Files (x86)/Intel/TBB" - "C:/Program Files/tbb/include" - "C:/Program Files (x86)/tbb/include" - "${CMAKE_INSTALL_PREFIX}/include") + ocv_tbb_env_guess(HAVE_TBB) +endif() - find_path(TBB_INCLUDE_DIRS "tbb/tbb.h" PATHS ${TBB_INCLUDE_DIR} ${TBB_DEFAULT_INCLUDE_DIRS} DOC "The path to TBB headers") - if(TBB_INCLUDE_DIRS) - if(UNIX) - set(TBB_LIB_DIR "${TBB_INCLUDE_DIRS}/../lib" CACHE PATH "Full path of TBB library directory") - link_directories("${TBB_LIB_DIR}") - endif() - if(APPLE) - set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} libtbb.dylib) - elseif(ANDROID) - set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} tbb) - add_definitions(-DTBB_USE_GCC_BUILTINS) - elseif (UNIX) - set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} tbb) - elseif (WIN32) - if(CMAKE_COMPILER_IS_GNUCXX) - set(TBB_LIB_DIR "${TBB_INCLUDE_DIRS}/../lib" CACHE PATH "Full path of TBB library directory") - link_directories("${TBB_LIB_DIR}") - set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} tbb) - else() - get_filename_component(_TBB_LIB_PATH "${TBB_INCLUDE_DIRS}/../lib" ABSOLUTE) +if(TBB_INTERFACE_VERSION LESS 6000) # drop support of versions < 4.0 + set(HAVE_TBB FALSE) +endif() - if(CMAKE_SYSTEM_PROCESSOR MATCHES amd64*|x86_64* OR MSVC64) - set(_TBB_LIB_PATH "${_TBB_LIB_PATH}/intel64") - else() - set(_TBB_LIB_PATH "${_TBB_LIB_PATH}/ia32") - endif() - - if(MSVC80) - set(_TBB_LIB_PATH "${_TBB_LIB_PATH}/vc8") - elseif(MSVC90) - set(_TBB_LIB_PATH "${_TBB_LIB_PATH}/vc9") - elseif(MSVC10) - set(_TBB_LIB_PATH "${_TBB_LIB_PATH}/vc10") - elseif(MSVC11) - set(_TBB_LIB_PATH "${_TBB_LIB_PATH}/vc11") - elseif(MSVC12) - set(_TBB_LIB_PATH "${_TBB_LIB_PATH}/vc12") - elseif(MSVC14) - set(_TBB_LIB_PATH "${_TBB_LIB_PATH}/vc14") - endif() - set(TBB_LIB_DIR "${_TBB_LIB_PATH}" CACHE PATH "Full path of TBB library directory") - link_directories("${TBB_LIB_DIR}") - endif() - endif() - - set(HAVE_TBB 1) - if(NOT "${TBB_INCLUDE_DIRS}" STREQUAL "") - ocv_include_directories("${TBB_INCLUDE_DIRS}") - endif() - endif(TBB_INCLUDE_DIRS) -endif(NOT HAVE_TBB) - -# get TBB version if(HAVE_TBB) - find_file(TBB_STDDEF_PATH tbb/tbb_stddef.h "${TBB_INCLUDE_DIRS}") - mark_as_advanced(TBB _STDDEF_PATH) -endif() -if(HAVE_TBB AND TBB_STDDEF_PATH) - ocv_parse_header("${TBB_STDDEF_PATH}" TBB_VERSION_LINES TBB_VERSION_MAJOR TBB_VERSION_MINOR TBB_INTERFACE_VERSION) -else() - unset(TBB_VERSION_MAJOR) - unset(TBB_VERSION_MINOR) - unset(TBB_INTERFACE_VERSION) + list(APPEND OPENCV_LINKER_LIBS tbb) endif() diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 34b4565cf8..b63a99b3f3 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -687,9 +687,9 @@ macro(ocv_parse_header FILENAME FILE_VAR) set(__parnet_scope OFF) set(__add_cache OFF) foreach(name ${ARGN}) - if("${name}" STREQUAL "PARENT_SCOPE") + if(${name} STREQUAL "PARENT_SCOPE") set(__parnet_scope ON) - elseif("${name}" STREQUAL "CACHE") + elseif(${name} STREQUAL "CACHE") set(__add_cache ON) elseif(vars_regex) set(vars_regex "${vars_regex}|${name}") @@ -703,7 +703,7 @@ macro(ocv_parse_header FILENAME FILE_VAR) unset(${FILE_VAR}) endif() foreach(name ${ARGN}) - if(NOT "${name}" STREQUAL "PARENT_SCOPE" AND NOT "${name}" STREQUAL "CACHE") + if(NOT ${name} STREQUAL "PARENT_SCOPE" AND NOT ${name} STREQUAL "CACHE") if(${FILE_VAR}) if(${FILE_VAR} MATCHES ".+[ \t]${name}[ \t]+([0-9]+).*") string(REGEX REPLACE ".+[ \t]${name}[ \t]+([0-9]+).*" "\\1" ${name} "${${FILE_VAR}}") diff --git a/modules/core/include/opencv2/core/private.hpp b/modules/core/include/opencv2/core/private.hpp index 4e5ba788e5..e428ecf8ad 100644 --- a/modules/core/include/opencv2/core/private.hpp +++ b/modules/core/include/opencv2/core/private.hpp @@ -60,15 +60,10 @@ #endif #ifdef HAVE_TBB -# include "tbb/tbb_stddef.h" -# if TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 -# include "tbb/tbb.h" -# include "tbb/task.h" -# undef min -# undef max -# else -# undef HAVE_TBB -# endif +# include "tbb/tbb.h" +# include "tbb/task.h" +# undef min +# undef max #endif #if defined HAVE_FP16 && (defined __F16C__ || (defined _MSC_VER && _MSC_VER >= 1700)) diff --git a/modules/core/src/parallel.cpp b/modules/core/src/parallel.cpp index ebf3907674..71ac94eec8 100644 --- a/modules/core/src/parallel.cpp +++ b/modules/core/src/parallel.cpp @@ -84,37 +84,30 @@ */ #if defined HAVE_TBB + #include "tbb/tbb.h" + #include "tbb/task.h" #include "tbb/tbb_stddef.h" - #if TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 - #include "tbb/tbb.h" - #include "tbb/task.h" - #if TBB_INTERFACE_VERSION >= 6100 - #include "tbb/task_arena.h" - #endif - #undef min - #undef max - #else - #undef HAVE_TBB - #endif // end TBB version -#endif - -#ifndef HAVE_TBB - #if defined HAVE_CSTRIPES - #include "C=.h" - #undef shared - #elif defined HAVE_OPENMP - #include - #elif defined HAVE_GCD - #include - #include - #elif defined WINRT && _MSC_VER < 1900 - #include - #elif defined HAVE_CONCURRENCY - #include + #if TBB_INTERFACE_VERSION >= 8000 + #include "tbb/task_arena.h" #endif + #undef min + #undef max +#elif defined HAVE_CSTRIPES + #include "C=.h" + #undef shared +#elif defined HAVE_OPENMP + #include +#elif defined HAVE_GCD + #include + #include +#elif defined WINRT && _MSC_VER < 1900 + #include +#elif defined HAVE_CONCURRENCY + #include #endif -#if defined HAVE_TBB && TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 + +#if defined HAVE_TBB # define CV_PARALLEL_FRAMEWORK "tbb" #elif defined HAVE_CSTRIPES # define CV_PARALLEL_FRAMEWORK "cstripes" @@ -491,8 +484,10 @@ void cv::setNumThreads( int threads ) int cv::getThreadNum(void) { #if defined HAVE_TBB - #if TBB_INTERFACE_VERSION >= 6100 && defined TBB_PREVIEW_TASK_ARENA && TBB_PREVIEW_TASK_ARENA - return tbb::task_arena::current_slot(); + #if TBB_INTERFACE_VERSION >= 9100 + return tbb::this_task_arena::current_thread_index(); + #elif TBB_INTERFACE_VERSION >= 8000 + return tbb::task_arena::current_thread_index(); #else return 0; #endif diff --git a/modules/videoio/src/cap_openni.cpp b/modules/videoio/src/cap_openni.cpp index 6c91df9346..50cab02fd9 100644 --- a/modules/videoio/src/cap_openni.cpp +++ b/modules/videoio/src/cap_openni.cpp @@ -44,10 +44,6 @@ #ifdef HAVE_OPENNI -#if defined TBB_INTERFACE_VERSION && TBB_INTERFACE_VERSION < 5000 -# undef HAVE_TBB -#endif - #include #ifndef i386 diff --git a/modules/videoio/src/cap_openni2.cpp b/modules/videoio/src/cap_openni2.cpp index 775038e93f..c9a8dbaab8 100644 --- a/modules/videoio/src/cap_openni2.cpp +++ b/modules/videoio/src/cap_openni2.cpp @@ -44,10 +44,6 @@ #ifdef HAVE_OPENNI2 -#if defined TBB_INTERFACE_VERSION && TBB_INTERFACE_VERSION < 5000 -# undef HAVE_TBB -#endif - #include #ifndef i386 diff --git a/samples/gpu/driver_api_multi.cpp b/samples/gpu/driver_api_multi.cpp index 933368ae99..6d49ee9990 100644 --- a/samples/gpu/driver_api_multi.cpp +++ b/samples/gpu/driver_api_multi.cpp @@ -12,15 +12,10 @@ #include "opencv2/cudaarithm.hpp" #ifdef HAVE_TBB -# include "tbb/tbb_stddef.h" -# if TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 -# include "tbb/tbb.h" -# include "tbb/task.h" -# undef min -# undef max -# else -# undef HAVE_TBB -# endif +# include "tbb/tbb.h" +# include "tbb/task.h" +# undef min +# undef max #endif #if !defined(HAVE_CUDA) || !defined(HAVE_TBB) || defined(__arm__) diff --git a/samples/gpu/driver_api_stereo_multi.cpp b/samples/gpu/driver_api_stereo_multi.cpp index 7cb0f5d51b..e487c31176 100644 --- a/samples/gpu/driver_api_stereo_multi.cpp +++ b/samples/gpu/driver_api_stereo_multi.cpp @@ -14,15 +14,10 @@ #include "opencv2/cudastereo.hpp" #ifdef HAVE_TBB -# include "tbb/tbb_stddef.h" -# if TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 -# include "tbb/tbb.h" -# include "tbb/task.h" -# undef min -# undef max -# else -# undef HAVE_TBB -# endif +# include "tbb/tbb.h" +# include "tbb/task.h" +# undef min +# undef max #endif #if !defined(HAVE_CUDA) || !defined(HAVE_TBB) || defined(__arm__) diff --git a/samples/gpu/multi.cpp b/samples/gpu/multi.cpp index 11e33b3300..9c0e15f213 100644 --- a/samples/gpu/multi.cpp +++ b/samples/gpu/multi.cpp @@ -12,15 +12,10 @@ #include "opencv2/cudaarithm.hpp" #ifdef HAVE_TBB -# include "tbb/tbb_stddef.h" -# if TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 -# include "tbb/tbb.h" -# include "tbb/task.h" -# undef min -# undef max -# else -# undef HAVE_TBB -# endif +# include "tbb/tbb.h" +# include "tbb/task.h" +# undef min +# undef max #endif #if !defined(HAVE_CUDA) || !defined(HAVE_TBB) From 19959cbfc92fa489a5ac96602bc9fd42a954516f Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Thu, 1 Dec 2016 16:41:21 +0300 Subject: [PATCH 156/409] Fixed Intel C++ 17 build in VS2015 --- 3rdparty/openexr/CMakeLists.txt | 5 +++++ cmake/OpenCVDetectCXXCompiler.cmake | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/3rdparty/openexr/CMakeLists.txt b/3rdparty/openexr/CMakeLists.txt index 65d43ae71a..5611b06f99 100644 --- a/3rdparty/openexr/CMakeLists.txt +++ b/3rdparty/openexr/CMakeLists.txt @@ -52,6 +52,11 @@ if(UNIX AND (CMAKE_COMPILER_IS_GNUCXX OR CV_ICC)) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") endif() +if(MSVC AND CV_ICC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Qrestrict") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Qrestrict") +endif() + add_library(IlmImf STATIC ${lib_hdrs} ${lib_srcs}) target_link_libraries(IlmImf ${ZLIB_LIBRARIES}) diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake index ce669bbc85..a53746b62d 100644 --- a/cmake/OpenCVDetectCXXCompiler.cmake +++ b/cmake/OpenCVDetectCXXCompiler.cmake @@ -41,7 +41,7 @@ if(UNIX) endif() endif() -if(MSVC AND CMAKE_C_COMPILER MATCHES "icc") +if(MSVC AND CMAKE_C_COMPILER MATCHES "icc|icl") set(CV_ICC __INTEL_COMPILER_FOR_WINDOWS) endif() From 39ff8a0d267d3b21c97991de43be19e9470b6388 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sun, 27 Nov 2016 23:31:04 +0300 Subject: [PATCH 157/409] cmake: cleanup ffmpeg detection --- 3rdparty/ffmpeg/ffmpeg.cmake | 13 ++++ CMakeLists.txt | 15 ++-- cmake/OpenCVFindLibsVideo.cmake | 92 +++++++------------------ cmake/OpenCVUtils.cmake | 38 ++++++++++ cmake/checks/ffmpeg_test.cpp | 24 +++++++ cmake/templates/cvconfig.h.in | 6 -- modules/videoio/src/cap_ffmpeg_impl.hpp | 44 +----------- modules/videoio/src/ffmpeg_codecs.hpp | 19 +---- 8 files changed, 109 insertions(+), 142 deletions(-) create mode 100644 cmake/checks/ffmpeg_test.cpp diff --git a/3rdparty/ffmpeg/ffmpeg.cmake b/3rdparty/ffmpeg/ffmpeg.cmake index d89b0a573a..259aea2b10 100644 --- a/3rdparty/ffmpeg/ffmpeg.cmake +++ b/3rdparty/ffmpeg/ffmpeg.cmake @@ -23,3 +23,16 @@ ocv_download(PACKAGE ffmpeg_version.cmake DESTINATION_DIR ${CMAKE_CURRENT_LIST_DIR}) include(${CMAKE_CURRENT_LIST_DIR}/ffmpeg_version.cmake) + +# Compatibility glue code +set(FFMPEG_libavcodec_FOUND 1) +set(FFMPEG_libavformat_FOUND 1) +set(FFMPEG_libavutil_FOUND 1) +set(FFMPEG_libswscale_FOUND 1) +set(FFMPEG_libavresample_FOUND 1) + +set(FFMPEG_libavcodec_VERSION ${ALIASOF_libavcodec_VERSION}) +set(FFMPEG_libavformat_VERSION ${ALIASOF_libavformat_VERSION}) +set(FFMPEG_libavutil_VERSION ${ALIASOF_libavutil_VERSION}) +set(FFMPEG_libswscale_VERSION ${ALIASOF_libswscale_VERSION}) +set(FFMPEG_libavresample_VERSION ${ALIASOF_libavresample_VERSION}) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8337ec9fb9..f399036353 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1070,19 +1070,18 @@ if(DEFINED WITH_1394) status(" DC1394 2.x:" HAVE_DC1394_2 THEN "YES (ver ${ALIASOF_libdc1394-2_VERSION})" ELSE NO) endif(DEFINED WITH_1394) -if(DEFINED WITH_FFMPEG) +if(DEFINED WITH_FFMPEG OR HAVE_FFMPEG) if(WIN32) status(" FFMPEG:" WITH_FFMPEG THEN "YES (prebuilt binaries)" ELSE NO) else() status(" FFMPEG:" HAVE_FFMPEG THEN YES ELSE NO) endif() - status(" codec:" HAVE_FFMPEG_CODEC THEN "YES (ver ${ALIASOF_libavcodec_VERSION})" ELSE NO) - status(" format:" HAVE_FFMPEG_FORMAT THEN "YES (ver ${ALIASOF_libavformat_VERSION})" ELSE NO) - status(" util:" HAVE_FFMPEG_UTIL THEN "YES (ver ${ALIASOF_libavutil_VERSION})" ELSE NO) - status(" swscale:" HAVE_FFMPEG_SWSCALE THEN "YES (ver ${ALIASOF_libswscale_VERSION})" ELSE NO) - status(" resample:" HAVE_FFMPEG_RESAMPLE THEN "YES (ver ${ALIASOF_libavresample_VERSION})" ELSE NO) - status(" gentoo-style:" HAVE_GENTOO_FFMPEG THEN YES ELSE NO) -endif(DEFINED WITH_FFMPEG) + status(" avcodec:" FFMPEG_libavcodec_FOUND THEN "YES (ver ${FFMPEG_libavcodec_VERSION})" ELSE NO) + status(" avformat:" FFMPEG_libavformat_FOUND THEN "YES (ver ${FFMPEG_libavformat_VERSION})" ELSE NO) + status(" avutil:" FFMPEG_libavutil_FOUND THEN "YES (ver ${FFMPEG_libavutil_VERSION})" ELSE NO) + status(" swscale:" FFMPEG_libswscale_FOUND THEN "YES (ver ${FFMPEG_libswscale_VERSION})" ELSE NO) + status(" avresample:" FFMPEG_libavresample_FOUND THEN "YES (ver ${FFMPEG_libavresample_VERSION})" ELSE NO) +endif() if(DEFINED WITH_GSTREAMER) status(" GStreamer:" HAVE_GSTREAMER THEN "" ELSE NO) diff --git a/cmake/OpenCVFindLibsVideo.cmake b/cmake/OpenCVFindLibsVideo.cmake index 3f43717a0f..b2b74b7a3b 100644 --- a/cmake/OpenCVFindLibsVideo.cmake +++ b/cmake/OpenCVFindLibsVideo.cmake @@ -213,79 +213,35 @@ if(WITH_XIMEA) endif(WITH_XIMEA) # --- FFMPEG --- -ocv_clear_vars(HAVE_FFMPEG HAVE_FFMPEG_CODEC HAVE_FFMPEG_FORMAT HAVE_FFMPEG_UTIL HAVE_FFMPEG_SWSCALE HAVE_FFMPEG_RESAMPLE HAVE_GENTOO_FFMPEG HAVE_FFMPEG_FFMPEG) +ocv_clear_vars(HAVE_FFMPEG) if(WITH_FFMPEG) if(WIN32 AND NOT ARM) include("${OpenCV_SOURCE_DIR}/3rdparty/ffmpeg/ffmpeg.cmake") - elseif(UNIX) - CHECK_MODULE(libavcodec HAVE_FFMPEG_CODEC) - CHECK_MODULE(libavformat HAVE_FFMPEG_FORMAT) - CHECK_MODULE(libavutil HAVE_FFMPEG_UTIL) - CHECK_MODULE(libswscale HAVE_FFMPEG_SWSCALE) - CHECK_MODULE(libavresample HAVE_FFMPEG_RESAMPLE) - - CHECK_INCLUDE_FILE(libavformat/avformat.h HAVE_GENTOO_FFMPEG) - CHECK_INCLUDE_FILE(ffmpeg/avformat.h HAVE_FFMPEG_FFMPEG) - if(NOT HAVE_GENTOO_FFMPEG AND NOT HAVE_FFMPEG_FFMPEG) - if((NOT CMAKE_CROSSCOMPILING AND EXISTS /usr/include/ffmpeg/libavformat/avformat.h) OR HAVE_FFMPEG_SWSCALE) - set(HAVE_GENTOO_FFMPEG TRUE) + set(HAVE_FFMPEG TRUE) + elseif(PKG_CONFIG_FOUND) + ocv_check_modules(FFMPEG libavcodec libavformat libavutil libswscale) + ocv_check_modules(FFMPEG_libavresample libavresample) + if(FFMPEG_libavresample_FOUND) + ocv_append_build_options(FFMPEG FFMPEG_libavresample) + endif() + if(HAVE_FFMPEG) + try_compile(__VALID_FFMPEG + "${OpenCV_BINARY_DIR}" + "${OpenCV_SOURCE_DIR}/cmake/checks/ffmpeg_test.cpp" + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${FFMPEG_INCLUDE_DIRS}" + "-DLINK_DIRECTORIES:STRING=${FFMPEG_LIBRARY_DIRS}" + "-DLINK_LIBRARIES:STRING=${FFMPEG_LIBRARIES}" + OUTPUT_VARIABLE TRY_OUT + ) + if(NOT __VALID_FFMPEG) + #message(FATAL_ERROR "FFMPEG: test check build log:\n${TRY_OUT}") + message(STATUS "WARNING: Can't build ffmpeg test code") + else() + ocv_append_build_options(VIDEOIO FFMPEG) endif() endif() - if(HAVE_FFMPEG_CODEC AND HAVE_FFMPEG_FORMAT AND HAVE_FFMPEG_UTIL AND HAVE_FFMPEG_SWSCALE) - set(HAVE_FFMPEG TRUE) - endif() - - if(HAVE_FFMPEG) - # Find the bzip2 library because it is required on some systems - FIND_LIBRARY(BZIP2_LIBRARIES NAMES bz2 bzip2 libbz2.so.1) - else() - find_path(FFMPEG_INCLUDE_DIR "libavformat/avformat.h" - PATHS /usr/local /usr /opt - PATH_SUFFIXES include - DOC "The path to FFMPEG headers") - if(FFMPEG_INCLUDE_DIR) - set(HAVE_GENTOO_FFMPEG TRUE) - set(FFMPEG_LIB_DIR "${FFMPEG_INCLUDE_DIR}/../lib" CACHE PATH "Full path of FFMPEG library directory") - find_library(FFMPEG_CODEC_LIB "avcodec" HINTS "${FFMPEG_LIB_DIR}") - find_library(FFMPEG_FORMAT_LIB "avformat" HINTS "${FFMPEG_LIB_DIR}") - find_library(FFMPEG_UTIL_LIB "avutil" HINTS "${FFMPEG_LIB_DIR}") - find_library(FFMPEG_SWSCALE_LIB "swscale" HINTS "${FFMPEG_LIB_DIR}") - find_library(FFMPEG_RESAMPLE_LIB "avresample" HINTS "${FFMPEG_LIB_DIR}") - if(FFMPEG_CODEC_LIB) - set(HAVE_FFMPEG_CODEC 1) - endif() - if(FFMPEG_FORMAT_LIB) - set(HAVE_FFMPEG_FORMAT 1) - endif() - if(FFMPEG_UTIL_LIB) - set(HAVE_FFMPEG_UTIL 1) - endif() - if(FFMPEG_SWSCALE_LIB) - set(HAVE_FFMPEG_SWSCALE 1) - endif() - if(FFMPEG_CODEC_LIB AND FFMPEG_FORMAT_LIB AND - FFMPEG_UTIL_LIB AND FFMPEG_SWSCALE_LIB) - set(ALIASOF_libavcodec_VERSION "Unknown") - set(ALIASOF_libavformat_VERSION "Unknown") - set(ALIASOF_libavutil_VERSION "Unknown") - set(ALIASOF_libswscale_VERSION "Unknown") - set(HAVE_FFMPEG 1) - if(FFMPEG_RESAMPLE_LIB) - set(HAVE_FFMPEG_RESAMPLE 1) - set(ALIASOF_libavresample_VERSION "Unknown") - endif() - endif() - endif(FFMPEG_INCLUDE_DIR) - if(HAVE_FFMPEG) - set(VIDEOIO_LIBRARIES ${VIDEOIO_LIBRARIES} "${FFMPEG_LIB_DIR}/libavcodec.a" - "${FFMPEG_LIB_DIR}/libavformat.a" "${FFMPEG_LIB_DIR}/libavutil.a" - "${FFMPEG_LIB_DIR}/libswscale.a") - if(HAVE_FFMPEG_RESAMPLE) - set(VIDEOIO_LIBRARIES ${VIDEOIO_LIBRARIES} "${FFMPEG_LIB_DIR}/libavresample.a") - endif() - ocv_include_directories(${FFMPEG_INCLUDE_DIR}) - endif(HAVE_FFMPEG) - endif() + else() + message(STATUS "Can't find ffmpeg - 'pkg-config' utility is missing") endif() endif(WITH_FFMPEG) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 34b4565cf8..9fc602bf68 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -398,6 +398,44 @@ macro(OCV_OPTION variable description value) unset(__value) endmacro() +# Usage: ocv_append_build_options(HIGHGUI FFMPEG) +macro(ocv_append_build_options var_prefix pkg_prefix) + foreach(suffix INCLUDE_DIRS LIBRARIES LIBRARY_DIRS) + if(${pkg_prefix}_${suffix}) + list(APPEND ${var_prefix}_${suffix} ${${pkg_prefix}_${suffix}}) + list(REMOVE_DUPLICATES ${var_prefix}_${suffix}) + endif() + endforeach() +endmacro() + +# Usage is similar to CMake 'pkg_check_modules' command +# It additionally controls HAVE_${define} and ${define}_${modname}_FOUND variables +macro(ocv_check_modules define) + unset(HAVE_${define}) + foreach(m ${ARGN}) + if (m MATCHES "(.*[^><])(>=|=|<=)(.*)") + set(__modname "${CMAKE_MATCH_1}") + else() + set(__modname "${m}") + endif() + unset(${define}_${__modname}_FOUND) + endforeach() + pkg_check_modules(${define} ${ARGN}) + if(${define}_FOUND) + set(HAVE_${define} 1) + endif() + foreach(m ${ARGN}) + if (m MATCHES "(.*[^><])(>=|=|<=)(.*)") + set(__modname "${CMAKE_MATCH_1}") + else() + set(__modname "${m}") + endif() + if(NOT DEFINED ${define}_${__modname}_FOUND AND ${define}_FOUND) + set(${define}_${__modname}_FOUND 1) + endif() + endforeach() +endmacro() + # Macros that checks if module have been installed. # After it adds module to build and define diff --git a/cmake/checks/ffmpeg_test.cpp b/cmake/checks/ffmpeg_test.cpp new file mode 100644 index 0000000000..7b49c38c7a --- /dev/null +++ b/cmake/checks/ffmpeg_test.cpp @@ -0,0 +1,24 @@ +#include + +extern "C" { +#include +#include +#include +} + +#define CALC_FFMPEG_VERSION(a,b,c) ( a<<16 | b<<8 | c ) + +static void test() +{ + AVFormatContext* c = 0; + AVCodec* avcodec = 0; + AVFrame* frame = 0; + +#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(52, 111, 0) + int err = avformat_open_input(&c, "", NULL, NULL); +#else + int err = av_open_input_file(&c, "", NULL, 0, NULL); +#endif +} + +int main() { test(); return 0; } diff --git a/cmake/templates/cvconfig.h.in b/cmake/templates/cvconfig.h.in index c4b147f8a4..514d1f5c70 100644 --- a/cmake/templates/cvconfig.h.in +++ b/cmake/templates/cvconfig.h.in @@ -71,12 +71,6 @@ /* FFMpeg video library */ #cmakedefine HAVE_FFMPEG -/* ffmpeg's libswscale */ -#cmakedefine HAVE_FFMPEG_SWSCALE - -/* ffmpeg in Gentoo */ -#cmakedefine HAVE_GENTOO_FFMPEG - /* Geospatial Data Abstraction Library */ #cmakedefine HAVE_GDAL diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index daa235e588..fc5141e09a 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -75,48 +75,8 @@ extern "C" { #include #endif -#ifdef WIN32 - #define HAVE_FFMPEG_SWSCALE 1 - #include - #include -#else - -#ifndef HAVE_FFMPEG_SWSCALE - #error "libswscale is necessary to build the newer OpenCV ffmpeg wrapper" -#endif - -// if the header path is not specified explicitly, let's deduce it -#if !defined HAVE_FFMPEG_AVCODEC_H && !defined HAVE_LIBAVCODEC_AVCODEC_H - -#if defined(HAVE_GENTOO_FFMPEG) - #define HAVE_LIBAVCODEC_AVCODEC_H 1 - #if defined(HAVE_FFMPEG_SWSCALE) - #define HAVE_LIBSWSCALE_SWSCALE_H 1 - #endif -#elif defined HAVE_FFMPEG - #define HAVE_FFMPEG_AVCODEC_H 1 - #if defined(HAVE_FFMPEG_SWSCALE) - #define HAVE_FFMPEG_SWSCALE_H 1 - #endif -#endif - -#endif - -#if defined(HAVE_FFMPEG_AVCODEC_H) - #include -#endif -#if defined(HAVE_FFMPEG_SWSCALE_H) - #include -#endif - -#if defined(HAVE_LIBAVCODEC_AVCODEC_H) - #include -#endif -#if defined(HAVE_LIBSWSCALE_SWSCALE_H) - #include -#endif - -#endif +#include +#include #ifdef __cplusplus } diff --git a/modules/videoio/src/ffmpeg_codecs.hpp b/modules/videoio/src/ffmpeg_codecs.hpp index 42eded7003..a2a7a3b349 100644 --- a/modules/videoio/src/ffmpeg_codecs.hpp +++ b/modules/videoio/src/ffmpeg_codecs.hpp @@ -60,24 +60,7 @@ extern "C" { #include #endif -// if the header path is not specified explicitly, let's deduce it -#if !defined HAVE_FFMPEG_AVCODEC_H && !defined HAVE_LIBAVCODEC_AVCODEC_H - -#if defined(HAVE_GENTOO_FFMPEG) - #define HAVE_LIBAVFORMAT_AVFORMAT_H 1 -#elif defined HAVE_FFMPEG - #define HAVE_FFMPEG_AVFORMAT_H 1 -#endif - -#if defined(HAVE_FFMPEG_AVFORMAT_H) - #include -#endif - -#if defined(HAVE_LIBAVFORMAT_AVFORMAT_H) || defined(WIN32) - #include -#endif - -#endif +#include #ifdef __cplusplus } From b69cdb2434a29d9aa213774536461cb8b7e66dce Mon Sep 17 00:00:00 2001 From: Li Peng Date: Tue, 8 Nov 2016 10:21:47 +0800 Subject: [PATCH 158/409] Image pyramids upsampling optimization Add new ocl kernel for image pyramids upsampling, It is 35% faster than current OCL kernel in perf test. Signed-off-by: Li Peng --- modules/imgproc/src/opencl/pyramid_up.cl | 114 +++++++++++++++++++++ modules/imgproc/src/pyramids.cpp | 13 ++- modules/imgproc/test/ocl/test_pyramids.cpp | 25 +++++ 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 modules/imgproc/src/opencl/pyramid_up.cl diff --git a/modules/imgproc/src/opencl/pyramid_up.cl b/modules/imgproc/src/opencl/pyramid_up.cl new file mode 100644 index 0000000000..4c3cde8f11 --- /dev/null +++ b/modules/imgproc/src/opencl/pyramid_up.cl @@ -0,0 +1,114 @@ +// 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. + +__constant float kx[] = { 0.125, 0.5, 0.75, 0.5, 0.125 }; +__constant float ky[] = { 0.125, 0.5, 0.75, 0.5, 0.125 }; + +#define OP(delta, y, x) (convert_float4(arr[(y + delta) * 5 + x]) * ky[y] * kx[x]) + +__kernel void pyrUp_cols2(__global const uchar * src, int src_step, int src_offset, int src_rows, int src_cols, + __global uchar * dst, int dst_step, int dst_offset, int dst_rows, int dst_cols) +{ + int block_x = get_global_id(0); + int y = get_global_id(1) * 2; + + if ((block_x * 4) >= dst_cols || y >= dst_rows) return; + + uchar8 line[6]; + uchar4 line_out; + + int offset, src_index; + src_index = block_x * 2 + (y / 2 - 1) * src_step - 1 + src_offset; + + uchar4 tmp; + + line[0] = line[2] = line[4] = (uchar8)0; + line[1] = line[3] = line[5] = (uchar8)0; + + offset = max(0, src_index + 1 * src_step); + tmp = vload4(0, src + offset); + if (offset == 0) tmp = (uchar4)(0, tmp.s012); + line[2].even = tmp; + + offset = max(0, src_index + ((y == 0) ? 2 : 0) * src_step); + tmp = vload4(0, src + offset); + if (offset == 0) tmp = (uchar4)(0, tmp.s012); + line[0].even = tmp; + + if (y == (dst_rows - 2)) + line[4] = line[2]; + else + line[4].even = vload4(0, src + src_index + 2 * src_step); + + bool row_s = (block_x == 0); + bool row_e = ((block_x + 1) * 4 == dst_cols); + uchar4 arr[30]; + uchar s, e; + + s = line[0].s4; + e = line[0].s3; + arr[0] = row_s ? (uchar4)(s, e, line[0].s23) : (uchar4)(line[0].s0123); + arr[1] = row_s ? (uchar4)(e, line[0].s234) : (uchar4)(line[0].s1234); + arr[2] = (uchar4)(line[0].s2345); + arr[3] = row_e ? (uchar4)(line[0].s345, s) : (uchar4)(line[0].s3456); + arr[4] = row_e ? (uchar4)(line[0].s45, s, e) : (uchar4)(line[0].s4567); + + s = line[1].s4; + e = line[1].s3; + arr[5] = row_s ? (uchar4)(s, e, line[1].s23) : (uchar4)(line[1].s0123); + arr[6] = row_s ? (uchar4)(e, line[1].s234) : (uchar4)(line[1].s1234); + arr[7] = (uchar4)(line[1].s2345); + arr[8] = row_e ? (uchar4)(line[1].s345, s) : (uchar4)(line[1].s3456); + arr[9] = row_e ? (uchar4)(line[1].s45, s, e) : (uchar4)(line[1].s4567); + + s = line[2].s4; + e = line[2].s3; + arr[10] = row_s ? (uchar4)(s, e, line[2].s23) : (uchar4)(line[2].s0123); + arr[11] = row_s ? (uchar4)(e, line[2].s234) : (uchar4)(line[2].s1234); + arr[12] = (uchar4)(line[2].s2345); + arr[13] = row_e ? (uchar4)(line[2].s345, s) : (uchar4)(line[2].s3456); + arr[14] = row_e ? (uchar4)(line[2].s45, s, e) : (uchar4)(line[2].s4567); + + s = line[3].s4; + e = line[3].s3; + arr[15] = row_s ? (uchar4)(s, e, line[3].s23) : (uchar4)(line[3].s0123); + arr[16] = row_s ? (uchar4)(e, line[3].s234) : (uchar4)(line[3].s1234); + arr[17] = (uchar4)(line[3].s2345); + arr[18] = row_e ? (uchar4)(line[3].s345, s) : (uchar4)(line[3].s3456); + arr[19] = row_e ? (uchar4)(line[3].s45, s, e) : (uchar4)(line[3].s4567); + + s = line[4].s4; + e = line[4].s3; + arr[20] = row_s ? (uchar4)(s, e, line[4].s23) : (uchar4)(line[4].s0123); + arr[21] = row_s ? (uchar4)(e, line[4].s234) : (uchar4)(line[4].s1234); + arr[22] = (uchar4)(line[4].s2345); + arr[23] = row_e ? (uchar4)(line[4].s345, s) : (uchar4)(line[4].s3456); + arr[24] = row_e ? (uchar4)(line[4].s45, s, e) : (uchar4)(line[4].s4567); + + s = line[5].s4; + e = line[5].s3; + arr[25] = row_s ? (uchar4)(s, e, line[5].s23) : (uchar4)(line[5].s0123); + arr[26] = row_s ? (uchar4)(e, line[5].s234) : (uchar4)(line[5].s1234); + arr[27] = (uchar4)(line[5].s2345); + arr[28] = row_e ? (uchar4)(line[5].s345, s) : (uchar4)(line[5].s3456); + arr[29] = row_e ? (uchar4)(line[5].s45, s, e) : (uchar4)(line[5].s4567); + + float4 sum[2]; + + sum[0] = OP(0, 0, 0) + OP(0, 0, 1) + OP(0, 0, 2) + OP(0, 0, 3) + OP(0, 0, 4) + + OP(0, 1, 0) + OP(0, 1, 1) + OP(0, 1, 2) + OP(0, 1, 3) + OP(0, 1, 4) + + OP(0, 2, 0) + OP(0, 2, 1) + OP(0, 2, 2) + OP(0, 2, 3) + OP(0, 2, 4) + + OP(0, 3, 0) + OP(0, 3, 1) + OP(0, 3, 2) + OP(0, 3, 3) + OP(0, 3, 4) + + OP(0, 4, 0) + OP(0, 4, 1) + OP(0, 4, 2) + OP(0, 4, 3) + OP(0, 4, 4); + + sum[1] = OP(1, 0, 0) + OP(1, 0, 1) + OP(1, 0, 2) + OP(1, 0, 3) + OP(1, 0, 4) + + OP(1, 1, 0) + OP(1, 1, 1) + OP(1, 1, 2) + OP(1, 1, 3) + OP(1, 1, 4) + + OP(1, 2, 0) + OP(1, 2, 1) + OP(1, 2, 2) + OP(1, 2, 3) + OP(1, 2, 4) + + OP(1, 3, 0) + OP(1, 3, 1) + OP(1, 3, 2) + OP(1, 3, 3) + OP(1, 3, 4) + + OP(1, 4, 0) + OP(1, 4, 1) + OP(1, 4, 2) + OP(1, 4, 3) + OP(1, 4, 4); + + int dst_index = block_x * 4 + y * dst_step + dst_offset; + vstore4(convert_uchar4_sat_rte(sum[0]), 0, dst + dst_index); + vstore4(convert_uchar4_sat_rte(sum[1]), 0, dst + dst_index + dst_step); +} diff --git a/modules/imgproc/src/pyramids.cpp b/modules/imgproc/src/pyramids.cpp index 7d1cb67b05..d774396509 100644 --- a/modules/imgproc/src/pyramids.cpp +++ b/modules/imgproc/src/pyramids.cpp @@ -1165,8 +1165,17 @@ static bool ocl_pyrUp( InputArray _src, OutputArray _dst, const Size& _dsz, int ocl::Kernel k; if (ocl::Device::getDefault().isIntel() && channels == 1) { - k.create("pyrUp_unrolled", ocl::imgproc::pyr_up_oclsrc, buildOptions); - globalThreads[0] = dst.cols/2; globalThreads[1] = dst.rows/2; + if (type == CV_8UC1 && src.cols % 2 == 0) + { + buildOptions.clear(); + k.create("pyrUp_cols2", ocl::imgproc::pyramid_up_oclsrc, buildOptions); + globalThreads[0] = dst.cols/4; globalThreads[1] = dst.rows/2; + } + else + { + k.create("pyrUp_unrolled", ocl::imgproc::pyr_up_oclsrc, buildOptions); + globalThreads[0] = dst.cols/2; globalThreads[1] = dst.rows/2; + } } else k.create("pyrUp", ocl::imgproc::pyr_up_oclsrc, buildOptions); diff --git a/modules/imgproc/test/ocl/test_pyramids.cpp b/modules/imgproc/test/ocl/test_pyramids.cpp index 5ac88411df..07a95bdba2 100644 --- a/modules/imgproc/test/ocl/test_pyramids.cpp +++ b/modules/imgproc/test/ocl/test_pyramids.cpp @@ -134,6 +134,24 @@ OCL_TEST_P(PyrUp, Mat) } } +typedef PyrTestBase PyrUp_cols2; + +OCL_TEST_P(PyrUp_cols2, Mat) +{ + for (int j = 0; j < test_loop_times; j++) + { + Size src_roiSize = randomSize(1, MAX_VALUE); + src_roiSize.width += (src_roiSize.width % 2); + Size dst_roiSize = Size(2 * src_roiSize.width, 2 * src_roiSize.height); + generateTestData(src_roiSize, dst_roiSize); + + OCL_OFF(pyrUp(src_roi, dst_roi, dst_roiSize, borderType)); + OCL_ON(pyrUp(usrc_roi, udst_roi, dst_roiSize, borderType)); + + Near(depth == CV_32F ? 1e-4f : 1.0f); + } +} + OCL_INSTANTIATE_TEST_CASE_P(ImgprocPyr, PyrUp, Combine( Values(CV_8U, CV_16U, CV_16S, CV_32F, CV_64F), Values(1, 2, 3, 4), @@ -141,6 +159,13 @@ OCL_INSTANTIATE_TEST_CASE_P(ImgprocPyr, PyrUp, Combine( Bool() )); +OCL_INSTANTIATE_TEST_CASE_P(ImgprocPyr, PyrUp_cols2, Combine( + Values((MatDepth)CV_8U), + Values((Channels)1), + Values((BorderType)BORDER_REFLECT_101), + Bool() + )); + } } // namespace cvtest::ocl #endif // HAVE_OPENCL From 2e8b9b1891bef4f4290d06356da841e9dcd05e68 Mon Sep 17 00:00:00 2001 From: mshabunin Date: Fri, 2 Dec 2016 12:24:42 +0300 Subject: [PATCH 159/409] Fixed TBB build on Android armeabi --- 3rdparty/tbb/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/tbb/CMakeLists.txt b/3rdparty/tbb/CMakeLists.txt index f6d59695d5..950896309f 100644 --- a/3rdparty/tbb/CMakeLists.txt +++ b/3rdparty/tbb/CMakeLists.txt @@ -180,7 +180,7 @@ endif() set(TBB_SOURCE_FILES ${TBB_SOURCE_FILES} "${CMAKE_CURRENT_SOURCE_DIR}/${tbb_version_file}") add_library(tbb ${TBB_SOURCE_FILES}) -target_compile_definitions(tbb PRIVATE TBB_USE_GCC_BUILTINS=1 __TBB_GCC_BUILTIN_ATOMICS_PRESENT=1) +target_compile_definitions(tbb PUBLIC TBB_USE_GCC_BUILTINS=1 __TBB_GCC_BUILTIN_ATOMICS_PRESENT=1) target_include_directories(tbb SYSTEM PUBLIC $) if (WIN32) From 49194023b53449459b817025d112f33ecced94a9 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 1 Dec 2016 19:01:38 +0300 Subject: [PATCH 160/409] ffmpeg: remove compatibility glue code --- 3rdparty/ffmpeg/ffmpeg.cmake | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/3rdparty/ffmpeg/ffmpeg.cmake b/3rdparty/ffmpeg/ffmpeg.cmake index 259aea2b10..d89b0a573a 100644 --- a/3rdparty/ffmpeg/ffmpeg.cmake +++ b/3rdparty/ffmpeg/ffmpeg.cmake @@ -23,16 +23,3 @@ ocv_download(PACKAGE ffmpeg_version.cmake DESTINATION_DIR ${CMAKE_CURRENT_LIST_DIR}) include(${CMAKE_CURRENT_LIST_DIR}/ffmpeg_version.cmake) - -# Compatibility glue code -set(FFMPEG_libavcodec_FOUND 1) -set(FFMPEG_libavformat_FOUND 1) -set(FFMPEG_libavutil_FOUND 1) -set(FFMPEG_libswscale_FOUND 1) -set(FFMPEG_libavresample_FOUND 1) - -set(FFMPEG_libavcodec_VERSION ${ALIASOF_libavcodec_VERSION}) -set(FFMPEG_libavformat_VERSION ${ALIASOF_libavformat_VERSION}) -set(FFMPEG_libavutil_VERSION ${ALIASOF_libavutil_VERSION}) -set(FFMPEG_libswscale_VERSION ${ALIASOF_libswscale_VERSION}) -set(FFMPEG_libavresample_VERSION ${ALIASOF_libavresample_VERSION}) From ced81f72bc83650b16bc6eec5584e369caa78c93 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Tue, 1 Nov 2016 21:14:21 +0300 Subject: [PATCH 161/409] Added OpenVX based processing to LUT --- 3rdparty/openvx/include/ivx.hpp | 166 ++++++++++++++++++++++++++++++++ modules/core/src/convert.cpp | 40 ++++++++ 2 files changed, 206 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index a578b140a8..8c982c401f 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -322,6 +322,15 @@ template <> struct RefTypeTraits static vx_status release(vxType& ref) { return vxReleaseMatrix(&ref); } }; +class LUT; +template <> struct RefTypeTraits +{ + typedef vx_lut vxType; + typedef LUT wrapperType; + static const vx_enum vxTypeEnum = VX_TYPE_LUT; + static vx_status release(vxType& ref) { return vxReleaseLUT(&ref); } +}; + #ifdef IVX_USE_CXX98 /// Casting to vx_reference with compile-time check @@ -2321,6 +2330,163 @@ public: #endif //IVX_USE_OPENCV }; +/* +* LUT +*/ +class LUT : public RefWrapper +{ +public: + IVX_REF_STD_CTORS_AND_ASSIGNMENT(LUT); + +#ifdef VX_VERSION_1_1 + static LUT create(vx_context context, vx_enum dataType = VX_TYPE_UINT8, vx_size count = 256) + { +#else + static LUT create(vx_context context) + { + vx_enum dataType = VX_TYPE_UINT8; + vx_size count = 256; +#endif + return LUT(vxCreateLUT(context, dataType, count)); + } + +#ifndef VX_VERSION_1_1 + static const vx_enum VX_MEMORY_TYPE_HOST = VX_IMPORT_TYPE_HOST; +#endif + + template + void query(vx_enum att, T& value) const + { + IVX_CHECK_STATUS(vxQueryLUT(ref, att, &value, sizeof(value))); + } + +#ifndef VX_VERSION_1_1 + static const vx_enum + VX_LUT_TYPE = VX_LUT_ATTRIBUTE_TYPE, + VX_LUT_COUNT = VX_LUT_ATTRIBUTE_COUNT, + VX_LUT_SIZE = VX_LUT_ATTRIBUTE_SIZE; +#endif + + vx_enum dataType() const + { + vx_enum v; + query(VX_LUT_TYPE, v); + return v; + } + + vx_size count() const + { + vx_size v; + query(VX_LUT_COUNT, v); + return v; + } + + vx_size size() const + { + vx_size v; + query(VX_LUT_SIZE, v); + return v; + } + +#ifdef VX_VERSION_1_1 + vx_uint32 offset() const + { + vx_enum v; + query(VX_LUT_OFFSET, v); + return v; + } +#endif // VX_VERSION_1_1 + + void copyTo(void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): output pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyLUT(ref, data, VX_READ_ONLY, VX_MEMORY_TYPE_HOST)); +#else + IVX_CHECK_STATUS(vxAccessLUT(ref, &data, VX_READ_ONLY)); + IVX_CHECK_STATUS(vxCommitLUT(ref, data)); +#endif + } + + void copyFrom(const void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): input pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyLUT(ref, const_cast(data), VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST)); +#else + IVX_CHECK_STATUS(vxAccessLUT(ref, const_cast(&data), VX_WRITE_ONLY)); + IVX_CHECK_STATUS(vxCommitLUT(ref, data)); +#endif + } + + void copy(void* data, vx_enum usage, vx_enum memType = VX_MEMORY_TYPE_HOST) + { +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyLUT(ref, data, usage, memType)); +#else + IVX_CHECK_STATUS(vxAccessLUT(ref, const_cast(&data), usage)); + IVX_CHECK_STATUS(vxCommitLUT(ref, data)); + (void)memType; +#endif + } + + template void copyTo(std::vector& data) + { + if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (data.size() != count()) + { + if (data.size() == 0) + data.resize(count()); + else + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + } + copyTo(&data[0]); + } + + template void copyFrom(const std::vector& data) + { + if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + if (data.size() != count()) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + copyFrom(&data[0]); + } + +#ifdef IVX_USE_OPENCV + void copyTo(cv::Mat& m) + { + if (m.type() != enumToCVType(dataType())) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (!( + ((vx_size)(m.rows) == count() && m.cols == 1) || + ((vx_size)(m.cols) == count() && m.rows == 1) + ) && !m.empty()) + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + + if (m.isContinuous() && (vx_size)(m.total()) == count()) + { + copyTo(m.ptr()); + } + else + { + cv::Mat tmp(1, (int)count(), enumToCVType(dataType())); + copyTo(tmp.ptr()); + if (m.empty()) + m = tmp; + else + tmp.copyTo(m); + } + } + + void copyFrom(const cv::Mat& m) + { + if (!( + ((vx_size)(m.rows) == count() && m.cols == 1) || + ((vx_size)(m.cols) == count() && m.rows == 1) + )) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + if (m.type() != enumToCVType(dataType())) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + copyFrom(m.isContinuous() ? m.ptr() : m.clone().ptr()); + } +#endif //IVX_USE_OPENCV +}; + /// Standard nodes namespace nodes { diff --git a/modules/core/src/convert.cpp b/modules/core/src/convert.cpp index 7dc8e898a8..f02ca05720 100644 --- a/modules/core/src/convert.cpp +++ b/modules/core/src/convert.cpp @@ -5374,6 +5374,41 @@ static bool ocl_LUT(InputArray _src, InputArray _lut, OutputArray _dst) #endif +#ifdef HAVE_OPENVX +static bool openvx_LUT(Mat src, Mat dst, Mat _lut) +{ + if (src.type() != CV_8UC1 || dst.type() != src.type() || _lut.type() != src.type() || !_lut.isContinuous()) + return false; + + try + { + ivx::Context ctx = ivx::Context::create(); + + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(src.cols, src.rows, 1, (vx_int32)(src.step)), src.data), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(dst.cols, dst.rows, 1, (vx_int32)(dst.step)), dst.data); + + ivx::LUT lut = ivx::LUT::create(ctx); + lut.copyFrom(_lut); + ivx::IVX_CHECK_STATUS(vxuTableLookup(ctx, ia, lut, ib)); + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + + return true; +} +#endif + #if defined(HAVE_IPP) namespace ipp { @@ -5639,6 +5674,11 @@ void cv::LUT( InputArray _src, InputArray _lut, OutputArray _dst ) _dst.create(src.dims, src.size, CV_MAKETYPE(_lut.depth(), cn)); Mat dst = _dst.getMat(); +#ifdef HAVE_OPENVX + if (openvx_LUT(src, dst, lut)) + return; +#endif + CV_IPP_RUN(_src.dims() <= 2, ipp_lut(src, lut, dst)); if (_src.dims() <= 2) From afc73969755a0820a7bf55a255cb66d64c12c0ff Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Thu, 27 Oct 2016 21:00:47 +0300 Subject: [PATCH 162/409] Added OpenVX based processing to gaussianBlur --- modules/imgproc/src/smooth.cpp | 112 +++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/modules/imgproc/src/smooth.cpp b/modules/imgproc/src/smooth.cpp index 54535f714a..2d3c9843c8 100644 --- a/modules/imgproc/src/smooth.cpp +++ b/modules/imgproc/src/smooth.cpp @@ -2191,6 +2191,113 @@ static bool ocl_GaussianBlur3x3_8UC1(InputArray _src, OutputArray _dst, int ddep #endif +#ifdef HAVE_OPENVX + +static bool openvx_gaussianBlur(InputArray _src, OutputArray _dst, Size ksize, + double sigma1, double sigma2, int borderType) +{ + int stype = _src.type(); + if (sigma2 <= 0) + sigma2 = sigma1; + // automatic detection of kernel size from sigma + if (ksize.width <= 0 && sigma1 > 0) + ksize.width = cvRound(sigma1*6 + 1) | 1; + if (ksize.height <= 0 && sigma2 > 0) + ksize.height = cvRound(sigma2*6 + 1) | 1; + + if (stype != CV_8UC1 || + ksize.width < 3 || ksize.height < 3 || + ksize.width % 2 != 1 || ksize.height % 2 != 1) + return false; + + sigma1 = std::max(sigma1, 0.); + sigma2 = std::max(sigma2, 0.); + + Mat src = _src.getMat(); + Mat dst = _dst.getMat(); + + if (src.cols < ksize.width || src.rows < ksize.height) + return false; + + if ((borderType & BORDER_ISOLATED) == 0 && src.isSubmatrix()) + return false; //Process isolated borders only + vx_border_t border; + switch (borderType & ~BORDER_ISOLATED) + { + case BORDER_CONSTANT: + border.mode = VX_BORDER_CONSTANT; +#if VX_VERSION > VX_VERSION_1_0 + border.constant_value.U8 = (vx_uint8)(0); +#else + border.constant_value = (vx_uint32)(0); +#endif + break; + case BORDER_REPLICATE: + border.mode = VX_BORDER_REPLICATE; + break; + default: + return false; + } + + try + { + ivx::Context ctx = ivx::Context::create(); + if ((vx_size)(ksize.width) > ctx.convolutionMaxDimension() || (vx_size)(ksize.height) > ctx.convolutionMaxDimension()) + return false; + + Mat a; + if (dst.data != src.data) + a = src; + else + src.copyTo(a); + + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(a.cols, a.rows, 1, (vx_int32)(a.step)), a.data), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(dst.cols, dst.rows, 1, (vx_int32)(dst.step)), dst.data); + + //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); + if (ksize.width == 3 && ksize.height == 3 && (sigma1 == 0.0 || (sigma1 - 0.8) < DBL_EPSILON) && (sigma2 == 0.0 || (sigma2 - 0.8) < DBL_EPSILON)) + { + ivx::IVX_CHECK_STATUS(vxuGaussian3x3(ctx, ia, ib)); + } + else + { +#if VX_VERSION <= VX_VERSION_1_0 + if (ctx.vendorID() == VX_ID_KHRONOS && ((vx_size)(a.cols) <= ctx.convolutionMaxDimension() || (vx_size)(a.rows) <= ctx.convolutionMaxDimension())) + { + ctx.setBorderMode(prevBorder); + return false; + } +#endif + Mat convData; + cv::Mat(cv::getGaussianKernel(ksize.height, sigma2)*cv::getGaussianKernel(ksize.width, sigma1).t()).convertTo(convData, CV_16SC1, (1 << 15)); + ivx::Convolution cnv = ivx::Convolution::create(ctx, convData.cols, convData.rows); + cnv.copyFrom(convData); + cnv.setScale(1 << 15); + ivx::IVX_CHECK_STATUS(vxuConvolve(ctx, ia, cnv, ib)); + } + ctx.setBorderMode(prevBorder); + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + return true; +} + +#endif + #ifdef HAVE_IPP static bool ipp_GaussianBlur( InputArray _src, OutputArray _dst, Size ksize, @@ -2311,6 +2418,11 @@ void cv::GaussianBlur( InputArray _src, OutputArray _dst, Size ksize, return; } +#ifdef HAVE_OPENVX + if (openvx_gaussianBlur(_src, _dst, ksize, sigma1, sigma2, borderType)) + return; +#endif + #ifdef HAVE_TEGRA_OPTIMIZATION Mat src = _src.getMat(); Mat dst = _dst.getMat(); From 39ca6a013765d8b86d1a76225dd3b3b2222602f0 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 1 Dec 2016 19:07:04 +0300 Subject: [PATCH 163/409] ffmpeg: update Windows binaries Versions: - ffmpeg 3.2.1 (was 3.1.3) - openh264 1.6.0 (was 1.5.0) - libvpx 1.6.0 (was 1.5.0) --- 3rdparty/ffmpeg/ffmpeg.cmake | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/3rdparty/ffmpeg/ffmpeg.cmake b/3rdparty/ffmpeg/ffmpeg.cmake index d89b0a573a..3b9f8ba7bf 100644 --- a/3rdparty/ffmpeg/ffmpeg.cmake +++ b/3rdparty/ffmpeg/ffmpeg.cmake @@ -1,9 +1,9 @@ -# Binary branch name: ffmpeg/master_20160908 -# Binaries were created for OpenCV: 11a65475d8d460a01c8818c5a2d0544ec49d7d68 -set(FFMPEG_BINARIES_COMMIT "03835134465888981e066434dc95009e8328d4ea") -set(FFMPEG_FILE_HASH_BIN32 "32ba7790b0ac7a6dc66be91603637a7d") -set(FFMPEG_FILE_HASH_BIN64 "068ecaa459a5571e7909cff90999a420") -set(FFMPEG_FILE_HASH_CMAKE "f99941d10c1e87bf16b9055e8fc91ab2") +# Binary branch name: ffmpeg/master_20161202 +# Binaries were created for OpenCV: 594c136d1fcbb5816c57516e50f9cbeffbd90835 +set(FFMPEG_BINARIES_COMMIT "2a19d0006415955c79431116e4634f04d5eb5a74") +set(FFMPEG_FILE_HASH_BIN32 "f081abd9d6ca7e425d340ce586f9c090") +set(FFMPEG_FILE_HASH_BIN64 "a423363a6eb76d362ca6c406c96c8db6") +set(FFMPEG_FILE_HASH_CMAKE "5346ae1854fc7aa569a722e85af480ec") set(FFMPEG_DOWNLOAD_URL ${OPENCV_FFMPEG_URL};$ENV{OPENCV_FFMPEG_URL};https://raw.githubusercontent.com/opencv/opencv_3rdparty/${FFMPEG_BINARIES_COMMIT}/ffmpeg/) From a0e771f545333dd0711e16c944f1a585b40d97ab Mon Sep 17 00:00:00 2001 From: mshabunin Date: Fri, 2 Dec 2016 18:41:25 +0300 Subject: [PATCH 164/409] Fixed valgrind issues in persistence functions --- modules/core/src/persistence.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index eacdcc5ef5..40b846ff84 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -714,8 +714,8 @@ cvReleaseFileStorage( CvFileStorage** p_fs ) delete fs->outbuf; delete fs->base64_writer; - delete fs->delayed_struct_key; - delete fs->delayed_type_name; + delete[] fs->delayed_struct_key; + delete[] fs->delayed_type_name; memset( fs, 0, sizeof(*fs) ); cvFree( &fs ); @@ -1218,8 +1218,8 @@ static void check_if_write_struct_is_delayed( CvFileStorage* fs, bool change_typ } /* reset */ - delete fs->delayed_struct_key; - delete fs->delayed_type_name; + delete[] fs->delayed_struct_key; + delete[] fs->delayed_type_name; fs->delayed_struct_key = 0; fs->delayed_struct_flags = 0; fs->delayed_type_name = 0; From 5032dae9edc693954f4a0e38da6957b252ba738b Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 2 Dec 2016 18:53:13 +0300 Subject: [PATCH 165/409] test: refactor test to capture more information on failure Core_ArithmMask.uninitialized --- modules/core/test/test_arithm.cpp | 35 ++++++++----------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index 3264710807..ab140164c8 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -1485,35 +1485,26 @@ INSTANTIATE_TEST_CASE_P(Core_MinMaxLoc, ElemWiseTest, ::testing::Values(ElemWise INSTANTIATE_TEST_CASE_P(Core_CartToPolarToCart, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new cvtest::CartToPolarToCartOp))); -class CV_ArithmMaskTest : public cvtest::BaseTest +TEST(Core_ArithmMask, uninitialized) { -public: - CV_ArithmMaskTest() {} - ~CV_ArithmMaskTest() {} -protected: - void run(int) - { - try - { RNG& rng = theRNG(); const int MAX_DIM=3; int sizes[MAX_DIM]; for( int iter = 0; iter < 100; iter++ ) { - //ts->printf(cvtest::TS::LOG, "."); - - ts->update_context(this, iter, true); - int k, dims = rng.uniform(1, MAX_DIM+1), p = 1; + int dims = rng.uniform(1, MAX_DIM+1); int depth = rng.uniform(CV_8U, CV_64F+1); int cn = rng.uniform(1, 6); int type = CV_MAKETYPE(depth, cn); int op = rng.uniform(0, 5); int depth1 = op <= 1 ? CV_64F : depth; - for( k = 0; k < dims; k++ ) + for (int k = 0; k < MAX_DIM; k++) { - sizes[k] = rng.uniform(1, 30); - p *= sizes[k]; + sizes[k] = k < dims ? rng.uniform(1, 30) : 0; } + SCOPED_TRACE(cv::format("iter=%d dims=%d depth=%d cn=%d type=%d op=%d depth1=%d dims=[%d; %d; %d]", + iter, dims, depth, cn, type, op, depth1, sizes[0], sizes[1], sizes[2])); + Mat a(dims, sizes, type), a1; Mat b(dims, sizes, type), b1; Mat mask(dims, sizes, CV_8U); @@ -1562,7 +1553,7 @@ protected: } Mat d1; d.convertTo(d1, depth); - CV_Assert( cvtest::norm(c, d1, CV_C) <= DBL_EPSILON ); + EXPECT_LE(cvtest::norm(c, d1, CV_C), DBL_EPSILON); } Mat_ tmpSrc(100,100); @@ -1572,15 +1563,7 @@ protected: Mat_ tmpDst(100,100); tmpDst = 2; tmpSrc.copyTo(tmpDst,tmpMask); - } - catch(...) - { - ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); - } - } -}; - -TEST(Core_ArithmMask, uninitialized) { CV_ArithmMaskTest test; test.safe_run(); } +} TEST(Multiply, FloatingPointRounding) { From 380cc520f3a47958061645f2c88537458034f4a3 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sat, 3 Dec 2016 13:17:37 +0300 Subject: [PATCH 166/409] build: fix cuda tests --- modules/cudaarithm/test/test_arithm.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/cudaarithm/test/test_arithm.cpp b/modules/cudaarithm/test/test_arithm.cpp index 0ee4e34699..257f5233cc 100644 --- a/modules/cudaarithm/test/test_arithm.cpp +++ b/modules/cudaarithm/test/test_arithm.cpp @@ -46,6 +46,8 @@ using namespace cvtest; +namespace { + ////////////////////////////////////////////////////////////////////////////// // GEMM @@ -401,4 +403,6 @@ INSTANTIATE_TEST_CASE_P(CUDA_Arithm, Convolve, testing::Combine( #endif // HAVE_CUBLAS +} // namespace + #endif // HAVE_CUDA From 44d9d59f085bbf8c0adde6b713c680332ac566fe Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sun, 4 Dec 2016 02:19:38 +0300 Subject: [PATCH 167/409] ocl: stop using of OpenCL without explicit UMat arguments --- modules/core/include/opencv2/core/ocl.hpp | 3 +++ modules/core/src/ocl.cpp | 12 ++++++++++++ modules/features2d/src/orb.cpp | 2 +- modules/objdetect/src/cascadedetect.cpp | 5 +++-- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/modules/core/include/opencv2/core/ocl.hpp b/modules/core/include/opencv2/core/ocl.hpp index 8bbead49d5..1a9549df4c 100644 --- a/modules/core/include/opencv2/core/ocl.hpp +++ b/modules/core/include/opencv2/core/ocl.hpp @@ -739,6 +739,9 @@ CV_EXPORTS MatAllocator* getOpenCLAllocator(); #ifdef __OPENCV_BUILD namespace internal { +CV_EXPORTS bool isOpenCLForced(); +#define OCL_FORCE_CHECK(condition) (cv::ocl::internal::isOpenCLForced() || (condition)) + CV_EXPORTS bool isPerformanceCheckBypassed(); #define OCL_PERFORMANCE_CHECK(condition) (cv::ocl::internal::isPerformanceCheckBypassed() || (condition)) diff --git a/modules/core/src/ocl.cpp b/modules/core/src/ocl.cpp index 045b57a6ea..60ecc69546 100644 --- a/modules/core/src/ocl.cpp +++ b/modules/core/src/ocl.cpp @@ -6073,6 +6073,18 @@ void* Image2D::ptr() const return p ? p->handle : 0; } +bool internal::isOpenCLForced() +{ + static bool initialized = false; + static bool value = false; + if (!initialized) + { + value = getBoolParameter("OPENCV_OPENCL_FORCE", false); + initialized = true; + } + return value; +} + bool internal::isPerformanceCheckBypassed() { static bool initialized = false; diff --git a/modules/features2d/src/orb.cpp b/modules/features2d/src/orb.cpp index 219e86e109..3b508e4f83 100644 --- a/modules/features2d/src/orb.cpp +++ b/modules/features2d/src/orb.cpp @@ -972,7 +972,7 @@ void ORB_Impl::detectAndCompute( InputArray _image, InputArray _mask, int halfPatchSize = patchSize / 2; int border = std::max(edgeThreshold, std::max(halfPatchSize, HARRIS_BLOCK_SIZE/2))+1; - bool useOCL = ocl::useOpenCL(); + bool useOCL = ocl::useOpenCL() && OCL_FORCE_CHECK(_image.isUMat() || _descriptors.isUMat()); Mat image = _image.getMat(), mask = _mask.getMat(); if( image.type() != CV_8UC1 ) diff --git a/modules/objdetect/src/cascadedetect.cpp b/modules/objdetect/src/cascadedetect.cpp index 1b99a7c28c..1670ddf69f 100644 --- a/modules/objdetect/src/cascadedetect.cpp +++ b/modules/objdetect/src/cascadedetect.cpp @@ -1290,8 +1290,8 @@ void CascadeClassifierImpl::detectMultiScaleNoGrouping( InputArray _image, std:: #ifdef HAVE_OPENCL bool use_ocl = tryOpenCL && ocl::useOpenCL() && + OCL_FORCE_CHECK(_image.isUMat()) && featureEvaluator->getLocalSize().area() > 0 && - ocl::Device::getDefault().type() != ocl::Device::TYPE_CPU && (data.minNodesPerTree == data.maxNodesPerTree) && !isOldFormatCascade() && maskGenerator.empty() && @@ -1316,7 +1316,8 @@ void CascadeClassifierImpl::detectMultiScaleNoGrouping( InputArray _image, std:: // OpenCL code CV_OCL_RUN(use_ocl, ocl_detectMultiScaleNoGrouping( scales, candidates )) - tryOpenCL = false; + if (use_ocl) + tryOpenCL = false; #endif // CPU code From 0724d13bcd8dea429d08c895cdda0e5983982225 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sun, 4 Dec 2016 01:51:28 +0300 Subject: [PATCH 168/409] build: cuda warnings --- cmake/OpenCVDetectCUDA.cmake | 6 ++++- .../core/include/opencv2/core/cuda/reduce.hpp | 4 ++++ modules/cudafilters/test/test_filters.cpp | 5 ----- modules/cudaimgproc/src/generalized_hough.cpp | 22 ------------------- modules/cudev/test/CMakeLists.txt | 15 +------------ 5 files changed, 10 insertions(+), 42 deletions(-) diff --git a/cmake/OpenCVDetectCUDA.cmake b/cmake/OpenCVDetectCUDA.cmake index 72d7a09faa..3116d9fd87 100644 --- a/cmake/OpenCVDetectCUDA.cmake +++ b/cmake/OpenCVDetectCUDA.cmake @@ -164,7 +164,7 @@ if(CUDA_FOUND) mark_as_advanced(CUDA_BUILD_CUBIN CUDA_BUILD_EMULATION CUDA_VERBOSE_BUILD CUDA_SDK_ROOT_DIR) - macro(ocv_cuda_compile VAR) + macro(ocv_cuda_filter_options) foreach(var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG) set(${var}_backup_in_cuda_compile_ "${${var}}") @@ -190,6 +190,10 @@ if(CUDA_FOUND) string(REPLACE "-fvisibility-inlines-hidden" "" ${var} "${${var}}") endforeach() + endmacro() + + macro(ocv_cuda_compile VAR) + ocv_cuda_filter_options() if(BUILD_SHARED_LIBS) set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -Xcompiler -DCVAPI_EXPORTS) diff --git a/modules/core/include/opencv2/core/cuda/reduce.hpp b/modules/core/include/opencv2/core/cuda/reduce.hpp index 5af9df4543..5de3650817 100644 --- a/modules/core/include/opencv2/core/cuda/reduce.hpp +++ b/modules/core/include/opencv2/core/cuda/reduce.hpp @@ -43,6 +43,10 @@ #ifndef OPENCV_CUDA_REDUCE_HPP #define OPENCV_CUDA_REDUCE_HPP +#ifndef THRUST_DEBUG // eliminate -Wundef warning +#define THRUST_DEBUG 0 +#endif + #include #include "detail/reduce.hpp" #include "detail/reduce_key_val.hpp" diff --git a/modules/cudafilters/test/test_filters.cpp b/modules/cudafilters/test/test_filters.cpp index 74bc3b422d..25c2fb9139 100644 --- a/modules/cudafilters/test/test_filters.cpp +++ b/modules/cudafilters/test/test_filters.cpp @@ -61,11 +61,6 @@ namespace cv::Rect roi(ksize.width, ksize.height, m.cols - 2 * ksize.width, m.rows - 2 * ksize.height); return m(roi); } - - cv::Mat getInnerROI(cv::InputArray m, int ksize) - { - return getInnerROI(m, cv::Size(ksize, ksize)); - } } namespace { diff --git a/modules/cudaimgproc/src/generalized_hough.cpp b/modules/cudaimgproc/src/generalized_hough.cpp index 9810bed3ae..75b90afcdb 100644 --- a/modules/cudaimgproc/src/generalized_hough.cpp +++ b/modules/cudaimgproc/src/generalized_hough.cpp @@ -684,28 +684,6 @@ namespace std::vector h_buf_; }; - double toRad(double a) - { - return a * CV_PI / 180.0; - } - - double clampAngle(double a) - { - double res = a; - - while (res > 360.0) - res -= 360.0; - while (res < 0) - res += 360.0; - - return res; - } - - bool angleEq(double a, double b, double eps = 1.0) - { - return (fabs(clampAngle(a - b)) <= eps); - } - GeneralizedHoughGuilImpl::GeneralizedHoughGuilImpl() { maxBufferSize_ = 1000; diff --git a/modules/cudev/test/CMakeLists.txt b/modules/cudev/test/CMakeLists.txt index a7bd6328bc..d348fdcb56 100644 --- a/modules/cudev/test/CMakeLists.txt +++ b/modules/cudev/test/CMakeLists.txt @@ -13,20 +13,7 @@ if(OCV_DEPENDENCIES_FOUND) source_group("Include" FILES ${test_hdrs}) set(OPENCV_TEST_${the_module}_SOURCES ${test_srcs} ${test_hdrs}) - foreach(var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG) - # we remove /EHa as it generates warnings under windows - string(REPLACE "/EHa" "" ${var} "${${var}}") - - # we remove -ggdb3 flag as it leads to preprocessor errors when compiling CUDA files (CUDA 4.1) - string(REPLACE "-ggdb3" "" ${var} "${${var}}") - - # we remove -Wsign-promo as it generates warnings under linux - string(REPLACE "-Wsign-promo" "" ${var} "${${var}}") - - # we remove -fvisibility-inlines-hidden because it's used for C++ compiler - # but NVCC uses C compiler by default - string(REPLACE "-fvisibility-inlines-hidden" "" ${var} "${${var}}") - endforeach() + ocv_cuda_filter_options() CUDA_ADD_EXECUTABLE(${the_target} ${OPENCV_TEST_${the_module}_SOURCES}) ocv_target_link_libraries(${the_target} ${test_deps} ${OPENCV_LINKER_LIBS} ${CUDA_LIBRARIES}) From 10ccd0328c2892534a3ed659d4921f2de90c3fb1 Mon Sep 17 00:00:00 2001 From: Kotrix Date: Sun, 4 Dec 2016 20:28:56 +0100 Subject: [PATCH 169/409] Removed redundant calculations Operations inside for loops are unnecessary. They are repeated anyway just below for loops --- modules/video/src/lkpyramid.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/modules/video/src/lkpyramid.cpp b/modules/video/src/lkpyramid.cpp index 8cf1579822..b32c298240 100644 --- a/modules/video/src/lkpyramid.cpp +++ b/modules/video/src/lkpyramid.cpp @@ -1265,8 +1265,6 @@ getRTMatrix( const Point2f* a, const Point2f* b, sa[1][1] += a[i].y*a[i].y; sa[1][2] += a[i].y; - sa[2][2] += 1; - sb[0] += a[i].x*b[i].x; sb[1] += a[i].y*b[i].x; sb[2] += b[i].x; @@ -1281,7 +1279,7 @@ getRTMatrix( const Point2f* a, const Point2f* b, sa[3][3] = sa[0][0]; sa[4][4] = sa[1][1]; - sa[5][5] = sa[2][2]; + sa[5][5] = sa[2][2] = count; solve( A, B, MM, DECOMP_EIG ); } @@ -1297,14 +1295,6 @@ getRTMatrix( const Point2f* a, const Point2f* b, sa[0][2] += a[i].x; sa[0][3] += a[i].y; - - sa[2][1] += -a[i].y; - sa[2][2] += 1; - - sa[3][0] += a[i].y; - sa[3][1] += a[i].x; - sa[3][3] += 1; - sb[0] += a[i].x*b[i].x + a[i].y*b[i].y; sb[1] += a[i].x*b[i].y - a[i].y*b[i].x; sb[2] += b[i].x; From bd0a8c7e3f9a974b87bba4aeb55c32078478241d Mon Sep 17 00:00:00 2001 From: Patrik Huber Date: Tue, 6 Dec 2016 00:04:24 +0000 Subject: [PATCH 170/409] Added VS2017 support (MSVC 1910) --- cmake/OpenCVDetectCXXCompiler.cmake | 2 ++ cmake/templates/OpenCVConfig.root-WIN32.cmake.in | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake index a53746b62d..8ecf4d82bd 100644 --- a/cmake/OpenCVDetectCXXCompiler.cmake +++ b/cmake/OpenCVDetectCXXCompiler.cmake @@ -160,6 +160,8 @@ if(MSVC) set(OpenCV_RUNTIME vc12) elseif(MSVC_VERSION EQUAL 1900) set(OpenCV_RUNTIME vc14) + elseif(MSVC_VERSION EQUAL 1910) + set(OpenCV_RUNTIME vc15) endif() elseif(MINGW) set(OpenCV_RUNTIME mingw) diff --git a/cmake/templates/OpenCVConfig.root-WIN32.cmake.in b/cmake/templates/OpenCVConfig.root-WIN32.cmake.in index e40140fb75..bd98b713f3 100644 --- a/cmake/templates/OpenCVConfig.root-WIN32.cmake.in +++ b/cmake/templates/OpenCVConfig.root-WIN32.cmake.in @@ -78,6 +78,8 @@ if(MSVC) set(OpenCV_RUNTIME vc12) elseif(MSVC_VERSION EQUAL 1900) set(OpenCV_RUNTIME vc14) + elseif(MSVC_VERSION EQUAL 1910) + set(OpenCV_RUNTIME vc15) endif() elseif(MINGW) set(OpenCV_RUNTIME mingw) From 396921dd239a05738308f07426c81a312e83f28c Mon Sep 17 00:00:00 2001 From: Li Peng Date: Wed, 30 Nov 2016 17:06:05 +0800 Subject: [PATCH 171/409] 5x5 gaussian blur optimization Add new 5x5 gaussian blur kernel for CV_8UC1 format, it is 50% ~ 70% faster than current ocl kernel in the perf test. Signed-off-by: Li Peng --- modules/imgproc/src/opencl/gaussianBlur5x5.cl | 198 ++++++++++++++++++ modules/imgproc/src/smooth.cpp | 32 ++- modules/imgproc/test/ocl/test_filters.cpp | 23 +- 3 files changed, 237 insertions(+), 16 deletions(-) create mode 100644 modules/imgproc/src/opencl/gaussianBlur5x5.cl diff --git a/modules/imgproc/src/opencl/gaussianBlur5x5.cl b/modules/imgproc/src/opencl/gaussianBlur5x5.cl new file mode 100644 index 0000000000..dc0b15f17c --- /dev/null +++ b/modules/imgproc/src/opencl/gaussianBlur5x5.cl @@ -0,0 +1,198 @@ +// 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. + +#define DIG(a) a, +__constant float kx[] = { KERNEL_MATRIX_X }; +__constant float ky[] = { KERNEL_MATRIX_Y }; + +#define OP(y, x) (convert_float4(arr[y * 5 + x]) * ky[y] * kx[x]) + +#define FILL_ARR(s1, s2, n, e1, e2) \ + arr[5 * n + 0] = row_s ? (uchar4)(s1, s2, line[n].s23) : (uchar4)(line[n].s0123); \ + arr[5 * n + 1] = row_s ? (uchar4)(s2, line[n].s234) : (uchar4)(line[n].s1234); \ + arr[5 * n + 2] = (uchar4)(line[n].s2345); \ + arr[5 * n + 3] = row_e ? (uchar4)(line[n].s345, e1) : (uchar4)(line[n].s3456); \ + arr[5 * n + 4] = row_e ? (uchar4)(line[n].s45, e1, e2) : (uchar4)(line[n].s4567); + +__kernel void gaussianBlur5x5_8UC1_cols4(__global const uchar* src, int src_step, + __global uint* dst, int dst_step, int rows, int cols) +{ + int x = get_global_id(0) * 4; + int y = get_global_id(1); + + if (x >= cols || y >= rows) return; + + uchar8 line[5]; + int offset, src_index; + + src_index = x + (y - 2) * src_step - 2; + offset = max(0, src_index + 2 * src_step); + line[2] = vload8(0, src + offset); + if (offset == 0) line[2] = (uchar8)(0, 0, line[2].s0123, line[2].s45); + +#if defined BORDER_CONSTANT || defined BORDER_REPLICATE + uchar8 tmp; +#ifdef BORDER_CONSTANT + tmp = (uchar8)0; +#elif defined BORDER_REPLICATE + tmp = line[2]; +#endif + line[0] = line[1] = tmp; + if (y > 1) + { + offset = max(0, src_index); + line[0] = vload8(0, src + offset); + if (offset == 0) line[0] = (uchar8)(0, 0, line[0].s0123, line[0].s45); + } + + if (y > 0) + { + offset = max(0, src_index + src_step); + line[1] = vload8(0, src + offset); + if (offset == 0) line[1] = (uchar8)(0, 0, line[1].s0123, line[1].s45); + } + + line[3] = (y == (rows - 1)) ? tmp : vload8(0, src + src_index + 3 * src_step); + line[4] = (y >= (rows - 2)) ? tmp : vload8(0, src + src_index + 4 * src_step); +#elif BORDER_REFLECT + int t; + t = (y <= 1) ? (abs(y - 1) - y + 2) : 0; + offset = max(0, src_index + t * src_step); + line[0] = vload8(0, src + offset); + if (offset == 0) line[0] = (uchar8)(0, 0, line[0].s0123, line[0].s45); + + if (y == 0) + line[1] = line[2]; + else + { + offset = max(0, src_index + 1 * src_step); + line[1] = vload8(0, src + offset); + if (offset == 0) line[1] = (uchar8)(0, 0, line[1].s0123, line[0].s45); + } + + line[3] = (y == (rows - 1)) ? line[2] : vload8(0, src + src_index + 3 * src_step); + + t = (y >= (rows - 2)) ? (abs(y - (rows - 1)) - (y - (rows - 2)) + 2) : 4; + line[4] = vload8(0, src + src_index + t * src_step); +#elif BORDER_REFLECT_101 + if (y == 1) + line[0] = line[2]; + else + { + offset = (y == 0) ? (src_index + 4 * src_step) : max(0, src_index); + line[0] = vload8(0, src + offset); + if (offset == 0) line[0] = (uchar8)(0, 0, line[0].s0123, line[0].s45); + } + + offset = (y == 0) ? (src_index + 3 * src_step) : max(0, src_index + 1 * src_step); + line[1] = vload8(0, src + offset); + if (offset == 0) line[1] = (uchar8)(0, 0, line[1].s0123, line[1].s45); + + line[3] = vload8(0, src + src_index + ((y == (rows - 1)) ? 1 : 3) * src_step); + if (y == (rows - 2)) + line[4] = line[2]; + else + { + line[4] = vload8(0, src + src_index + ((y == (rows - 1)) ? 1 : 4) * src_step); + } +#endif + + bool row_s = (x == 0); + bool row_e = ((x + 4) == cols); + uchar4 arr[25]; + uchar s, e; + +#ifdef BORDER_CONSTANT + s = e = 0; + + FILL_ARR(s, s, 0, e, e); + FILL_ARR(s, s, 1, e, e); + FILL_ARR(s, s, 2, e, e); + FILL_ARR(s, s, 3, e, e); + FILL_ARR(s, s, 4, e, e); +#elif defined BORDER_REPLICATE + s = line[0].s2; + e = line[0].s5; + FILL_ARR(s, s, 0, e, e); + + s = line[1].s2; + e = line[1].s5; + FILL_ARR(s, s, 1, e, e); + + s = line[2].s2; + e = line[2].s5; + FILL_ARR(s, s, 2, e, e); + + s = line[3].s2; + e = line[3].s5; + FILL_ARR(s, s, 3, e, e); + + s = line[4].s2; + e = line[4].s5; + FILL_ARR(s, s, 4, e, e); +#elif BORDER_REFLECT + uchar s1, s2; + uchar e1, e2; + + s1 = line[0].s3; + s2 = line[0].s2; + e1 = line[0].s5; + e2 = line[0].s4; + FILL_ARR(s1, s2, 0, e1, e2); + + s1 = line[1].s3; + s2 = line[1].s2; + e1 = line[1].s5; + e2 = line[1].s4; + FILL_ARR(s1, s2, 1, e1, e2); + + s1 = line[2].s3; + s2 = line[2].s2; + e1 = line[2].s5; + e2 = line[2].s4; + FILL_ARR(s1, s2, 2, e1, e2); + + s1 = line[3].s3; + s2 = line[3].s2; + e1 = line[3].s5; + e2 = line[3].s4; + FILL_ARR(s1, s2, 3, e1, e2); + + s1 = line[4].s3; + s2 = line[4].s2; + e1 = line[4].s5; + e2 = line[4].s4; + FILL_ARR(s1, s2, 4, e1, e2); +#elif BORDER_REFLECT_101 + s = line[0].s4; + e = line[0].s3; + FILL_ARR(s, e, 0, s, e); + + s = line[1].s4; + e = line[1].s3; + FILL_ARR(s, e, 1, s, e); + + s = line[2].s4; + e = line[2].s3; + FILL_ARR(s, e, 2, s, e); + + s = line[3].s4; + e = line[3].s3; + FILL_ARR(s, e, 3, s, e); + + s = line[4].s4; + e = line[4].s3; + FILL_ARR(s, e, 4, s, e); +#endif + + float4 sum; + sum = OP(0, 0) + OP(0, 1) + OP(0, 2) + OP(0, 3) + OP(0, 4) + + OP(1, 0) + OP(1, 1) + OP(1, 2) + OP(1, 3) + OP(1, 4) + + OP(2, 0) + OP(2, 1) + OP(2, 2) + OP(2, 3) + OP(2, 4) + + OP(3, 0) + OP(3, 1) + OP(3, 2) + OP(3, 3) + OP(3, 4) + + OP(4, 0) + OP(4, 1) + OP(4, 2) + OP(4, 3) + OP(4, 4); + + int dst_index = (x / 4) + y * (dst_step / 4); + dst[dst_index] = as_uint(convert_uchar4_sat_rte(sum)); +} diff --git a/modules/imgproc/src/smooth.cpp b/modules/imgproc/src/smooth.cpp index 2d3c9843c8..552ced0144 100644 --- a/modules/imgproc/src/smooth.cpp +++ b/modules/imgproc/src/smooth.cpp @@ -2135,15 +2135,16 @@ namespace cv { #ifdef HAVE_OPENCL -static bool ocl_GaussianBlur3x3_8UC1(InputArray _src, OutputArray _dst, int ddepth, - InputArray _kernelX, InputArray _kernelY, int borderType) +static bool ocl_GaussianBlur_8UC1(InputArray _src, OutputArray _dst, Size ksize, int ddepth, + InputArray _kernelX, InputArray _kernelY, int borderType) { const ocl::Device & dev = ocl::Device::getDefault(); int type = _src.type(), sdepth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type); if ( !(dev.isIntel() && (type == CV_8UC1) && (_src.offset() == 0) && (_src.step() % 4 == 0) && - (_src.cols() % 16 == 0) && (_src.rows() % 2 == 0)) ) + ((ksize.width == 5 && (_src.cols() % 4 == 0)) || + (ksize.width == 3 && (_src.cols() % 16 == 0) && (_src.rows() % 2 == 0)))) ) return false; Mat kernelX = _kernelX.getMat().reshape(1, 1); @@ -2160,8 +2161,16 @@ static bool ocl_GaussianBlur3x3_8UC1(InputArray _src, OutputArray _dst, int ddep size_t globalsize[2] = { 0, 0 }; size_t localsize[2] = { 0, 0 }; - globalsize[0] = size.width / 16; - globalsize[1] = size.height / 2; + if (ksize.width == 3) + { + globalsize[0] = size.width / 16; + globalsize[1] = size.height / 2; + } + else if (ksize.width == 5) + { + globalsize[0] = size.width / 4; + globalsize[1] = size.height / 1; + } const char * const borderMap[] = { "BORDER_CONSTANT", "BORDER_REPLICATE", "BORDER_REFLECT", 0, "BORDER_REFLECT_101" }; char build_opts[1024]; @@ -2169,7 +2178,13 @@ static bool ocl_GaussianBlur3x3_8UC1(InputArray _src, OutputArray _dst, int ddep ocl::kernelToStr(kernelX, CV_32F, "KERNEL_MATRIX_X").c_str(), ocl::kernelToStr(kernelY, CV_32F, "KERNEL_MATRIX_Y").c_str()); - ocl::Kernel kernel("gaussianBlur3x3_8UC1_cols16_rows2", cv::ocl::imgproc::gaussianBlur3x3_oclsrc, build_opts); + ocl::Kernel kernel; + + if (ksize.width == 3) + kernel.create("gaussianBlur3x3_8UC1_cols16_rows2", cv::ocl::imgproc::gaussianBlur3x3_oclsrc, build_opts); + else if (ksize.width == 5) + kernel.create("gaussianBlur5x5_8UC1_cols4", cv::ocl::imgproc::gaussianBlur5x5_oclsrc, build_opts); + if (kernel.empty()) return false; @@ -2436,9 +2451,10 @@ void cv::GaussianBlur( InputArray _src, OutputArray _dst, Size ksize, createGaussianKernels(kx, ky, type, ksize, sigma1, sigma2); CV_OCL_RUN(_dst.isUMat() && _src.dims() <= 2 && - ksize.width == 3 && ksize.height == 3 && + ((ksize.width == 3 && ksize.height == 3) || + (ksize.width == 5 && ksize.height == 5)) && (size_t)_src.rows() > ky.total() && (size_t)_src.cols() > kx.total(), - ocl_GaussianBlur3x3_8UC1(_src, _dst, CV_MAT_DEPTH(type), kx, ky, borderType)); + ocl_GaussianBlur_8UC1(_src, _dst, ksize, CV_MAT_DEPTH(type), kx, ky, borderType)); sepFilter2D(_src, _dst, CV_MAT_DEPTH(type), kx, ky, Point(-1,-1), 0, borderType ); } diff --git a/modules/imgproc/test/ocl/test_filters.cpp b/modules/imgproc/test/ocl/test_filters.cpp index 434b776b9a..481edf2327 100644 --- a/modules/imgproc/test/ocl/test_filters.cpp +++ b/modules/imgproc/test/ocl/test_filters.cpp @@ -342,7 +342,7 @@ OCL_TEST_P(GaussianBlurTest, Mat) } } -PARAM_TEST_CASE(GaussianBlur3x3_cols16_rows2_Base, MatType, +PARAM_TEST_CASE(GaussianBlur_multicols_Base, MatType, int, // kernel size Size, // dx, dy BorderType, // border type @@ -372,11 +372,18 @@ PARAM_TEST_CASE(GaussianBlur3x3_cols16_rows2_Base, MatType, void random_roi() { - size = Size(3, 3); + size = Size(ksize, ksize); Size roiSize = randomSize(size.width, MAX_VALUE, size.height, MAX_VALUE); - roiSize.width = std::max(size.width + 13, roiSize.width & (~0xf)); - roiSize.height = std::max(size.height + 1, roiSize.height & (~0x1)); + if (ksize == 3) + { + roiSize.width = std::max((size.width + 15) & 0x10, roiSize.width & (~0xf)); + roiSize.height = std::max(size.height + 1, roiSize.height & (~0x1)); + } + else if (ksize == 5) + { + roiSize.width = std::max((size.width + 3) & 0x4, roiSize.width & (~0x3)); + } Border srcBorder = randomBorder(0, useRoi ? MAX_VALUE : 0); randomSubMat(src, src_roi, roiSize, srcBorder, type, 5, 256); @@ -402,9 +409,9 @@ PARAM_TEST_CASE(GaussianBlur3x3_cols16_rows2_Base, MatType, } }; -typedef GaussianBlur3x3_cols16_rows2_Base GaussianBlur3x3_cols16_rows2; +typedef GaussianBlur_multicols_Base GaussianBlur_multicols; -OCL_TEST_P(GaussianBlur3x3_cols16_rows2, Mat) +OCL_TEST_P(GaussianBlur_multicols, Mat) { Size kernelSize(ksize, ksize); @@ -710,9 +717,9 @@ OCL_INSTANTIATE_TEST_CASE_P(Filter, GaussianBlurTest, Combine( Bool(), Values(1))); // not used -OCL_INSTANTIATE_TEST_CASE_P(Filter, GaussianBlur3x3_cols16_rows2, Combine( +OCL_INSTANTIATE_TEST_CASE_P(Filter, GaussianBlur_multicols, Combine( Values((MatType)CV_8UC1), - Values(3), // kernel size + Values(3, 5), // kernel size Values(Size(0, 0)), // not used FILTER_BORDER_SET_NO_WRAP_NO_ISOLATED, Values(0.0), // not used From 2d28bb41711365c98c8b4b7f60ad979d292b376f Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 29 Nov 2016 00:54:15 +0300 Subject: [PATCH 172/409] ipp: disable compilation with IPP 9+ --- cmake/OpenCVFindIPP.cmake | 17 ++++++++--------- modules/core/include/opencv2/core/internal.hpp | 4 ++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/cmake/OpenCVFindIPP.cmake b/cmake/OpenCVFindIPP.cmake index db02e6acbf..a944b84979 100644 --- a/cmake/OpenCVFindIPP.cmake +++ b/cmake/OpenCVFindIPP.cmake @@ -85,8 +85,6 @@ function(get_ipp_version _ROOT_DIR) message(STATUS "found IPP: ${_MAJOR}.${_MINOR}.${_BUILD} [${_VERSION_STR}]") message(STATUS "at: ${_ROOT_DIR}") - return() - endfunction() @@ -129,8 +127,6 @@ function(set_ipp_old_libraries) ${IPP_LIB_PREFIX}${IPP_PREFIX}${IPPCORE}${IPP_ARCH}${IPP_SUFFIX}${IPP_LIB_SUFFIX} PARENT_SCOPE) - return() - endfunction() @@ -173,7 +169,6 @@ function(set_ipp_new_libraries _LATEST_VERSION) ${IPP_LIB_PREFIX}svml${CMAKE_SHARED_LIBRARY_SUFFIX}) endif() set(IPP_LIBRARIES ${IPP_LIBRARIES} PARENT_SCOPE) - return() endfunction() @@ -208,7 +203,7 @@ function(set_ipp_variables _LATEST_VERSION) set(IPP_LIBRARIES ${IPP_LIBRARIES} PARENT_SCOPE) message(STATUS "IPP libs: ${IPP_LIBRARIES}") - else() + elseif(${_LATEST_VERSION} VERSION_LESS "9.0") # message(STATUS "new") # set INCLUDE and LIB folders @@ -229,7 +224,10 @@ function(set_ipp_variables _LATEST_VERSION) endif() if (UNIX) - get_filename_component(INTEL_COMPILER_LIBRARY_DIR ${IPP_ROOT_DIR}/../lib REALPATH) + get_filename_component(INTEL_COMPILER_LIBRARY_DIR ${IPP_ROOT_DIR}/../compiler/lib REALPATH) + if(NOT EXISTS "${INTEL_COMPILER_LIBRARY_DIR}") + get_filename_component(INTEL_COMPILER_LIBRARY_DIR ${IPP_ROOT_DIR}/../lib REALPATH) + endif() if (IPP_X64) if(NOT EXISTS ${INTEL_COMPILER_LIBRARY_DIR}/intel64) message(SEND_ERROR "Intel compiler EM64T libraries not found") @@ -253,10 +251,11 @@ function(set_ipp_variables _LATEST_VERSION) set(IPP_LIBRARIES ${IPP_LIBRARIES} PARENT_SCOPE) message(STATUS "IPP libs: ${IPP_LIBRARIES}") + else() + message(STATUS "IPP: version ${_LATEST_VERSION} is not supported (${IPP_ROOT_DIR})") + set(IPP_FOUND 0 PARENT_SCOPE) endif() - return() - endfunction() diff --git a/modules/core/include/opencv2/core/internal.hpp b/modules/core/include/opencv2/core/internal.hpp index 4933654735..4ebc3a03ab 100644 --- a/modules/core/include/opencv2/core/internal.hpp +++ b/modules/core/include/opencv2/core/internal.hpp @@ -104,6 +104,10 @@ CV_INLINE IppiSize ippiSize(const cv::Size & _size) return size; } +#if IPP_VERSION_MAJOR >= 9 // IPP 9+ is not supported +#undef HAVE_IPP +#undef IPP_VERSION_MAJOR +#endif #endif #ifndef IPPI_CALL From 2bfcbe14594c0798045113ba940a97547ee2685c Mon Sep 17 00:00:00 2001 From: Pavel Rojtberg Date: Tue, 6 Dec 2016 14:16:37 +0100 Subject: [PATCH 173/409] fisheye::undistortPoints: sanitize theta values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the current camera model is only valid up to 180° FOV for larger FOV the undistort loop does not converge. Clip values so we still get plausible results for super fisheye images > 180°. --- modules/calib3d/src/fisheye.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 32dc15c3f2..df68cf1905 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -377,6 +377,12 @@ void cv::fisheye::undistortPoints( InputArray distorted, OutputArray undistorted double scale = 1.0; double theta_d = sqrt(pw[0]*pw[0] + pw[1]*pw[1]); + + // the current camera model is only valid up to 180° FOV + // for larger FOV the loop below does not converge + // clip values so we still get plausible results for super fisheye images > 180° + theta_d = min(max(-CV_PI/2., theta_d), CV_PI/2.); + if (theta_d > 1e-8) { // compensate distortion iteratively From fef94315af7eba4340c8922e32d54403be7e3b58 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 6 Dec 2016 16:25:10 +0300 Subject: [PATCH 174/409] core: exp/log workaround for MSVS --- modules/core/src/mathfuncs_core.cpp | 58 +++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/modules/core/src/mathfuncs_core.cpp b/modules/core/src/mathfuncs_core.cpp index c021812247..e0cc5b58dd 100644 --- a/modules/core/src/mathfuncs_core.cpp +++ b/modules/core/src/mathfuncs_core.cpp @@ -373,6 +373,62 @@ void sqrt64f(const double* src, double* dst, int len) dst[i] = std::sqrt(src[i]); } +// Workaround for ICE in MSVS 2015 update 3 (issue #7795) +// CV_AVX is not used here, because generated code is faster in non-AVX mode. +// (tested with disabled IPP on i5-6300U) +#if (defined _MSC_VER && _MSC_VER >= 1900) +void exp32f(const float *src, float *dst, int n) +{ + CV_INSTRUMENT_REGION() + + CALL_HAL(exp32f, cv_hal_exp32f, src, dst, n); + CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsExp_32f_A21, src, dst, n) >= 0); + + for (int i = 0; i < n; i++) + { + dst[i] = std::exp(src[i]); + } +} + +void exp64f(const double *src, double *dst, int n) +{ + CV_INSTRUMENT_REGION() + + CALL_HAL(exp64f, cv_hal_exp64f, src, dst, n); + CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsExp_64f_A50, src, dst, n) >= 0); + + for (int i = 0; i < n; i++) + { + dst[i] = std::exp(src[i]); + } +} + +void log32f(const float *src, float *dst, int n) +{ + CV_INSTRUMENT_REGION() + + CALL_HAL(log32f, cv_hal_log32f, src, dst, n); + CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsLn_32f_A21, src, dst, n) >= 0); + + for (int i = 0; i < n; i++) + { + dst[i] = std::log(src[i]); + } +} +void log64f(const double *src, double *dst, int n) +{ + CV_INSTRUMENT_REGION() + + CALL_HAL(log64f, cv_hal_log64f, src, dst, n); + CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsLn_64f_A50, src, dst, n) >= 0); + + for (int i = 0; i < n; i++) + { + dst[i] = std::log(src[i]); + } +} +#else + ////////////////////////////////////// EXP ///////////////////////////////////// typedef union @@ -1466,6 +1522,8 @@ void log64f( const double *x, double *y, int n ) } } +#endif // issue 7795 + //============================================================================= // for compatibility with 3.0 From cb46946d34826824ce06a326f368f59698716e96 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Tue, 6 Dec 2016 20:08:33 +0400 Subject: [PATCH 175/409] Merge pull request #7782 from savuor:fix/ivx_types Several fixes for OpenVX wrappers (#7782) * Several fixes added to ivx.hpp: * `#include ` * `vx_keypoint_t` support added * type check fixed * fixed Node::create() for >6 args in c++98 * fixed Array::copyFrom() if array is empty * type check changed; Array::addItem methods changed --- 3rdparty/openvx/include/ivx.hpp | 171 ++++++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 19 deletions(-) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index 8c982c401f..57eeda34a2 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -20,6 +20,7 @@ Details: TBD #include +#include #ifndef IVX_USE_CXX98 // checking compiler @@ -152,6 +153,7 @@ template<> struct EnumToType { typedef vx_enum type; stat template<> struct EnumToType { typedef vx_size type; static const vx_size bytes = sizeof(type); }; template<> struct EnumToType { typedef vx_df_image type; static const vx_size bytes = sizeof(type); }; template<> struct EnumToType { typedef vx_bool type; static const vx_size bytes = sizeof(type); }; +template<> struct EnumToType { typedef vx_keypoint_t type;static const vx_size bytes = sizeof(type); }; #ifndef IVX_USE_CXX98 template using EnumToType_t = typename EnumToType::type; #endif @@ -176,6 +178,7 @@ inline vx_size enumToTypeSize(vx_enum type) case VX_TYPE_SIZE: return EnumToType::bytes; case VX_TYPE_DF_IMAGE: return EnumToType::bytes; case VX_TYPE_BOOL: return EnumToType::bytes; + case VX_TYPE_KEYPOINT: return EnumToType::bytes; default: throw WrapperError(std::string(__func__) + ": unsupported type enum"); } } @@ -194,11 +197,17 @@ template<> struct TypeToEnum { static const vx_enum value = VX_TYPE template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_FLOAT32; }; template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_FLOAT64; }; template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_BOOL; }; +template<> struct TypeToEnum {static const vx_enum value = VX_TYPE_KEYPOINT; }; // the commented types are aliases (of integral tyes) and have conflicts with the types above //template<> struct TypeToEnum { static const vx_enum val = VX_TYPE_ENUM; }; //template<> struct TypeToEnum { static const vx_enum val = VX_TYPE_SIZE; }; //template<> struct TypeToEnum { static const vx_enum val = VX_TYPE_DF_IMAGE; }; +inline bool areTypesCompatible(const vx_enum a, const vx_enum b) +{ + return enumToTypeSize(a) == enumToTypeSize(b); +} + #ifdef IVX_USE_OPENCV inline int enumToCVType(vx_enum type) { @@ -1021,6 +1030,89 @@ public: return create(g, Kernel::getByEnum(Context::getFrom(g), kernelID), params); } + /// Create node for the kernel ID and set seven parameters + template + static Node create(vx_graph g, vx_enum kernelID, + const T0& arg0, const T1& arg1, const T2& arg2, + const T3& arg3, const T4& arg4, const T5& arg5, + const T6& arg6) + { + std::vector params; + params.push_back(castToReference(arg0)); + params.push_back(castToReference(arg1)); + params.push_back(castToReference(arg2)); + params.push_back(castToReference(arg3)); + params.push_back(castToReference(arg4)); + params.push_back(castToReference(arg5)); + params.push_back(castToReference(arg6)); + return create(g, Kernel::getByEnum(Context::getFrom(g), kernelID), params); + } + + /// Create node for the kernel ID and set eight parameters + template + static Node create(vx_graph g, vx_enum kernelID, + const T0& arg0, const T1& arg1, const T2& arg2, + const T3& arg3, const T4& arg4, const T5& arg5, + const T6& arg6, const T7& arg7) + { + std::vector params; + params.push_back(castToReference(arg0)); + params.push_back(castToReference(arg1)); + params.push_back(castToReference(arg2)); + params.push_back(castToReference(arg3)); + params.push_back(castToReference(arg4)); + params.push_back(castToReference(arg5)); + params.push_back(castToReference(arg6)); + params.push_back(castToReference(arg7)); + return create(g, Kernel::getByEnum(Context::getFrom(g), kernelID), params); + } + + /// Create node for the kernel ID and set nine parameters + template + static Node create(vx_graph g, vx_enum kernelID, + const T0& arg0, const T1& arg1, const T2& arg2, + const T3& arg3, const T4& arg4, const T5& arg5, + const T6& arg6, const T7& arg7, const T8& arg8) + { + std::vector params; + params.push_back(castToReference(arg0)); + params.push_back(castToReference(arg1)); + params.push_back(castToReference(arg2)); + params.push_back(castToReference(arg3)); + params.push_back(castToReference(arg4)); + params.push_back(castToReference(arg5)); + params.push_back(castToReference(arg6)); + params.push_back(castToReference(arg7)); + params.push_back(castToReference(arg8)); + return create(g, Kernel::getByEnum(Context::getFrom(g), kernelID), params); + } + + /// Create node for the kernel ID and set ten parameters + template + static Node create(vx_graph g, vx_enum kernelID, + const T0& arg0, const T1& arg1, const T2& arg2, + const T3& arg3, const T4& arg4, const T5& arg5, + const T6& arg6, const T7& arg7, const T8& arg8, + const T9& arg9) + { + std::vector params; + params.push_back(castToReference(arg0)); + params.push_back(castToReference(arg1)); + params.push_back(castToReference(arg2)); + params.push_back(castToReference(arg3)); + params.push_back(castToReference(arg4)); + params.push_back(castToReference(arg5)); + params.push_back(castToReference(arg6)); + params.push_back(castToReference(arg7)); + params.push_back(castToReference(arg8)); + params.push_back(castToReference(arg9)); + 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)); } @@ -1702,7 +1794,8 @@ static const vx_enum VX_SCALAR_TYPE = VX_SCALAR_ATTRIBUTE_TYPE; template void getValue(T& val) { - if(TypeToEnum::value != type()) throw WrapperError(std::string(__func__)+"(): incompatible types"); + if (!areTypesCompatible(TypeToEnum::value, type())) + throw WrapperError(std::string(__func__)+"(): incompatible types"); #ifdef VX_VERSION_1_1 IVX_CHECK_STATUS( vxCopyScalar(ref, &val, VX_READ_ONLY, VX_MEMORY_TYPE_HOST) ); #else @@ -1724,7 +1817,8 @@ static const vx_enum VX_SCALAR_TYPE = VX_SCALAR_ATTRIBUTE_TYPE; template void setValue(T val) { - if (TypeToEnum::value != type()) throw WrapperError(std::string(__func__)+"(): incompatible types"); + if (!areTypesCompatible(TypeToEnum::value, type())) + throw WrapperError(std::string(__func__)+"(): incompatible types"); #ifdef VX_VERSION_1_1 IVX_CHECK_STATUS(vxCopyScalar(ref, &val, VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST)); #else @@ -1909,6 +2003,11 @@ public: return v; } + void addItems(vx_size count, const void* ptr, vx_size stride) + { + IVX_CHECK_STATUS(vxAddArrayItems(ref, count, ptr, stride)); + } + void copyRangeTo(size_t start, size_t end, void* data) { if (!data) throw WrapperError(std::string(__func__) + "(): output pointer is 0"); @@ -1955,15 +2054,29 @@ public: void copy(void* data, vx_enum usage, vx_enum memType = VX_MEMORY_TYPE_HOST) { copyRange(0, itemCount(), data, usage, memType); } + template void addItem(const T& item) + { + if (!areTypesCompatible(TypeToEnum::value, itemType())) + throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + addItems(1, &item, sizeof(T)); + } + + template void addItems(const std::vector& data) + { + if (!areTypesCompatible(TypeToEnum::value, itemType())) + throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + addItems(data.size(), &data[0], itemSize()); + } + template void copyRangeTo(size_t start, size_t end, std::vector& data) { - if (TypeToEnum::value != itemType()) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); - if (data.size() != (end - start)) + if (!areTypesCompatible(TypeToEnum::value, itemType())) + throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (data.empty()) + data.resize((end - start)); + else if (data.size() != (end - start)) { - if (data.size() == 0) - data.resize((end - start)); - else - throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); } copyRangeTo(start, end, &data[0]); } @@ -1973,7 +2086,8 @@ public: template void copyRangeFrom(size_t start, size_t end, const std::vector& data) { - if (TypeToEnum::value != itemType()) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + if (!areTypesCompatible(TypeToEnum::value, itemType())) + throw WrapperError(std::string(__func__) + "(): source type is wrong"); if (data.size() != (end - start)) throw WrapperError(std::string(__func__) + "(): source size is wrong"); copyRangeFrom(start, end, &data[0]); } @@ -1982,13 +2096,24 @@ public: { copyRangeFrom(0, itemCount(), data); } #ifdef IVX_USE_OPENCV + void addItems(cv::InputArray ia) + { + cv::Mat m = ia.getMat(); + if (m.type() != enumToCVType(itemType())) + throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + addItems(m.total(), m.isContinuous() ? m.ptr() : m.clone().ptr(), + (vx_size)(m.elemSize())); + } + void copyRangeTo(size_t start, size_t end, cv::Mat& m) { - if (m.type() != enumToCVType(itemType())) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (m.type() != enumToCVType(itemType())) + throw WrapperError(std::string(__func__) + "(): destination type is wrong"); if (!( ((vx_size)(m.rows) == (end - start) && m.cols == 1) || ((vx_size)(m.cols) == (end - start) && m.rows == 1) - ) && !m.empty()) throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + ) && !m.empty()) + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); if (m.isContinuous() && (vx_size)(m.total()) == (end - start)) { @@ -2013,8 +2138,10 @@ public: if (!( ((vx_size)(m.rows) == (end - start) && m.cols == 1) || ((vx_size)(m.cols) == (end - start) && m.rows == 1) - )) throw WrapperError(std::string(__func__) + "(): source size is wrong"); - if (m.type() != enumToCVType(itemType())) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + )) + throw WrapperError(std::string(__func__) + "(): source size is wrong"); + if (m.type() != enumToCVType(itemType())) + throw WrapperError(std::string(__func__) + "(): source type is wrong"); copyFrom(m.isContinuous() ? m.ptr() : m.clone().ptr()); } @@ -2121,7 +2248,8 @@ public: template void copyTo(std::vector& data) { - if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (!areTypesCompatible(TypeToEnum::value, dataType())) + throw WrapperError(std::string(__func__) + "(): destination type is wrong"); if (data.size() != size()) { if (data.size() == 0) @@ -2134,7 +2262,8 @@ public: template void copyFrom(const std::vector& data) { - if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + if (!areTypesCompatible(TypeToEnum::value, dataType())) + throw WrapperError(std::string(__func__) + "(): source type is wrong"); if (data.size() != size()) throw WrapperError(std::string(__func__) + "(): source size is wrong"); copyFrom(&data[0]); } @@ -2281,7 +2410,8 @@ public: template void copyTo(std::vector& data) { - if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (!areTypesCompatible(TypeToEnum::value, dataType())) + throw WrapperError(std::string(__func__) + "(): destination type is wrong"); if (data.size() != size()) { if (data.size() == 0) @@ -2294,7 +2424,8 @@ public: template void copyFrom(const std::vector& data) { - if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + if (!areTypesCompatible(TypeToEnum::value, dataType())) + throw WrapperError(std::string(__func__) + "(): source type is wrong"); if (data.size() != size()) throw WrapperError(std::string(__func__) + "(): source size is wrong"); copyFrom(&data[0]); } @@ -2432,7 +2563,8 @@ public: template void copyTo(std::vector& data) { - if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (!areTypesCompatible(TypeToEnum::value, dataType())) + throw WrapperError(std::string(__func__) + "(): destination type is wrong"); if (data.size() != count()) { if (data.size() == 0) @@ -2445,7 +2577,8 @@ public: template void copyFrom(const std::vector& data) { - if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + if (!areTypesCompatible(TypeToEnum::value, dataType())) + throw WrapperError(std::string(__func__) + "(): source type is wrong"); if (data.size() != count()) throw WrapperError(std::string(__func__) + "(): source size is wrong"); copyFrom(&data[0]); } From 695b20172ba991b58c424a73d09b5321103efa22 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Tue, 6 Dec 2016 20:29:44 +0400 Subject: [PATCH 176/409] Merge pull request #7794 from savuor:fix/ovx_cvt_continuous Fixed OpenVX wrapper for Mat::convertTo() (#7794) * fixed for cases of unrolled (w*h x 1) matrices * more error handling --- modules/core/src/convert.cpp | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/modules/core/src/convert.cpp b/modules/core/src/convert.cpp index f02ca05720..aad60ff252 100644 --- a/modules/core/src/convert.cpp +++ b/modules/core/src/convert.cpp @@ -4641,26 +4641,45 @@ cvtScaleHalf_( const short* src, size_t sstep, float* dst, size_t } #ifdef HAVE_OPENVX + +#ifdef _DEBUG +#define VX_DbgThrow(s) CV_Error(cv::Error::StsInternal, (s)) +#else +#define VX_DbgThrow(s) return false; +#endif + template static bool _openvx_cvt(const T* src, size_t sstep, - DT* dst, size_t dstep, Size size) + DT* dst, size_t dstep, Size continuousSize) { using namespace ivx; - if(!(size.width > 0 && size.height > 0)) + if(!(continuousSize.width > 0 && continuousSize.height > 0 && sstep > 0 && dstep > 0)) { return true; } + CV_Assert(sstep / sizeof(T) == dstep / sizeof(DT)); + + //.height is for number of continuous pieces + //.width is for length of one piece + Size imgSize = continuousSize; + if(continuousSize.height == 1) + { + //continuous case + imgSize.width = sstep / sizeof(T); + imgSize.height = continuousSize.width / (sstep / sizeof(T)); + } + try { Context context = Context::create(); Image srcImage = Image::createFromHandle(context, Image::matTypeToFormat(DataType::type), - Image::createAddressing(size.width, size.height, + Image::createAddressing(imgSize.width, imgSize.height, (vx_uint32)sizeof(T), (vx_uint32)sstep), (void*)src); Image dstImage = Image::createFromHandle(context, Image::matTypeToFormat(DataType
::type), - Image::createAddressing(size.width, size.height, + Image::createAddressing(imgSize.width, imgSize.height, (vx_uint32)sizeof(DT), (vx_uint32)dstep), (void*)dst); @@ -4674,13 +4693,11 @@ static bool _openvx_cvt(const T* src, size_t sstep, } catch (RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } return true; From 61792a839f6a7e44d10d8241f28bc5d138c3934d Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 6 Dec 2016 17:42:00 +0300 Subject: [PATCH 177/409] java: fix PATH environment variable --- modules/java/pure_test/build.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/java/pure_test/build.xml b/modules/java/pure_test/build.xml index dac4d4b455..6d7430634f 100644 --- a/modules/java/pure_test/build.xml +++ b/modules/java/pure_test/build.xml @@ -1,4 +1,5 @@ + @@ -39,7 +40,7 @@ - + From d42d155c16ffd532edd89f3810c563539b654959 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 6 Dec 2016 17:42:00 +0300 Subject: [PATCH 178/409] java: fix PATH environment variable --- modules/java/test/build.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/java/test/build.xml b/modules/java/test/build.xml index dac4d4b455..6d7430634f 100644 --- a/modules/java/test/build.xml +++ b/modules/java/test/build.xml @@ -1,4 +1,5 @@ + @@ -39,7 +40,7 @@ - + From a05444618c6888e9e5c0f6ffd24c79f9ee12fb8f Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 10 Nov 2016 15:39:18 +0300 Subject: [PATCH 179/409] test: tvl1 test check update --- modules/video/test/test_tvl1optflow.cpp | 37 +++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/modules/video/test/test_tvl1optflow.cpp b/modules/video/test/test_tvl1optflow.cpp index 67dc7fe973..bf7e835df3 100644 --- a/modules/video/test/test_tvl1optflow.cpp +++ b/modules/video/test/test_tvl1optflow.cpp @@ -115,35 +115,39 @@ namespace return !cvIsNaN(u.x) && !cvIsNaN(u.y) && (fabs(u.x) < 1e9) && (fabs(u.y) < 1e9); } - double calcRMSE(const Mat_& flow1, const Mat_& flow2) + void check(const Mat_& gold, const Mat_& flow, double threshold = 0.1, double expectedAccuracy = 0.95) { - double sum = 0.0; - int counter = 0; + threshold = threshold*threshold; - for (int i = 0; i < flow1.rows; ++i) + size_t gold_counter = 0; + size_t valid_counter = 0; + + for (int i = 0; i < gold.rows; ++i) { - for (int j = 0; j < flow1.cols; ++j) + for (int j = 0; j < gold.cols; ++j) { - const Point2f u1 = flow1(i, j); - const Point2f u2 = flow2(i, j); + const Point2f u1 = gold(i, j); + const Point2f u2 = flow(i, j); - if (isFlowCorrect(u1) && isFlowCorrect(u2)) + if (isFlowCorrect(u1)) { - const Point2f diff = u1 - u2; - sum += diff.ddot(diff); - ++counter; + gold_counter++; + if (isFlowCorrect(u2)) + { + const Point2f diff = u1 - u2; + double err = diff.ddot(diff); + if (err <= threshold) + valid_counter++; + } } } } - - return sqrt(sum / (1e-9 + counter)); + EXPECT_GE(valid_counter, expectedAccuracy * gold_counter); } } TEST(Video_calcOpticalFlowDual_TVL1, Regression) { - const double MAX_RMSE = 0.02; - const string frame1_path = TS::ptr()->get_data_path() + "optflow/RubberWhale1.png"; const string frame2_path = TS::ptr()->get_data_path() + "optflow/RubberWhale2.png"; const string gold_flow_path = TS::ptr()->get_data_path() + "optflow/tvl1_flow.flo"; @@ -167,7 +171,6 @@ TEST(Video_calcOpticalFlowDual_TVL1, Regression) ASSERT_EQ(gold.rows, flow.rows); ASSERT_EQ(gold.cols, flow.cols); - const double err = calcRMSE(gold, flow); - EXPECT_LE(err, MAX_RMSE); + check(gold, flow); #endif } From 10d1b33a808f298a0ae0740968145df3179c2ce2 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 10 Nov 2016 15:41:44 +0300 Subject: [PATCH 180/409] perf: remove tvl1 check for magic numbers, reduce number of samples --- modules/video/perf/perf_tvl1optflow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/video/perf/perf_tvl1optflow.cpp b/modules/video/perf/perf_tvl1optflow.cpp index 36f16d9943..4895ff455e 100644 --- a/modules/video/perf/perf_tvl1optflow.cpp +++ b/modules/video/perf/perf_tvl1optflow.cpp @@ -24,7 +24,7 @@ PERF_TEST_P(ImagePair, OpticalFlowDual_TVL1, testing::Values(impair("cv/optflow/ Ptr tvl1 = createOptFlow_DualTVL1(); - TEST_CYCLE_N(10) tvl1->calc(frame1, frame2, flow); + TEST_CYCLE() tvl1->calc(frame1, frame2, flow); - SANITY_CHECK(flow, 0.5); + SANITY_CHECK_NOTHING(); } From ab260dafd22c54221c925c64f5cc168f9906f1fe Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Thu, 1 Dec 2016 16:41:21 +0300 Subject: [PATCH 181/409] Fixed Intel C++ 17 build in VS2015 --- 3rdparty/openexr/CMakeLists.txt | 5 +++++ cmake/OpenCVDetectCXXCompiler.cmake | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/3rdparty/openexr/CMakeLists.txt b/3rdparty/openexr/CMakeLists.txt index dddf3738c4..194d46f28a 100644 --- a/3rdparty/openexr/CMakeLists.txt +++ b/3rdparty/openexr/CMakeLists.txt @@ -49,6 +49,11 @@ if(UNIX AND (CMAKE_COMPILER_IS_GNUCXX OR CV_ICC)) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") endif() +if(MSVC AND CV_ICC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Qrestrict") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Qrestrict") +endif() + add_library(IlmImf STATIC ${lib_hdrs} ${lib_srcs}) target_link_libraries(IlmImf ${ZLIB_LIBRARIES}) diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake index b53f8d7363..a2c427c9ed 100644 --- a/cmake/OpenCVDetectCXXCompiler.cmake +++ b/cmake/OpenCVDetectCXXCompiler.cmake @@ -38,7 +38,7 @@ if(UNIX) endif() endif() -if(MSVC AND CMAKE_C_COMPILER MATCHES "icc") +if(MSVC AND CMAKE_C_COMPILER MATCHES "icc|icl") set(CV_ICC __INTEL_COMPILER_FOR_WINDOWS) endif() From c6af179afc4e4edc558e31af765caf8da22956b6 Mon Sep 17 00:00:00 2001 From: Patrik Huber Date: Tue, 6 Dec 2016 00:04:24 +0000 Subject: [PATCH 182/409] Added VS2017 support (MSVC 1910) --- cmake/OpenCVConfig.cmake | 2 ++ cmake/OpenCVDetectCXXCompiler.cmake | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cmake/OpenCVConfig.cmake b/cmake/OpenCVConfig.cmake index e2ce60f9fd..9069a72936 100644 --- a/cmake/OpenCVConfig.cmake +++ b/cmake/OpenCVConfig.cmake @@ -81,6 +81,8 @@ if(MSVC) set(OpenCV_RUNTIME vc12) elseif(MSVC_VERSION EQUAL 1900) set(OpenCV_RUNTIME vc14) + elseif(MSVC_VERSION EQUAL 1910) + set(OpenCV_RUNTIME vc15) endif() elseif(MINGW) set(OpenCV_RUNTIME mingw) diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake index b53f8d7363..87f955c8d4 100644 --- a/cmake/OpenCVDetectCXXCompiler.cmake +++ b/cmake/OpenCVDetectCXXCompiler.cmake @@ -138,6 +138,8 @@ if(MSVC) set(OpenCV_RUNTIME vc12) elseif(MSVC_VERSION EQUAL 1900) set(OpenCV_RUNTIME vc14) + elseif(MSVC_VERSION EQUAL 1910) + set(OpenCV_RUNTIME vc15) endif() elseif(MINGW) set(OpenCV_RUNTIME mingw) From 21f3531b17017fd5559ced15e5c41ee2e8635517 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 7 Dec 2016 19:27:19 +0300 Subject: [PATCH 183/409] core: fix inplace support in mulSpectrums --- modules/core/src/dxt.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/core/src/dxt.cpp b/modules/core/src/dxt.cpp index 90eb392f58..b94f65a1e9 100644 --- a/modules/core/src/dxt.cpp +++ b/modules/core/src/dxt.cpp @@ -1905,6 +1905,12 @@ void cv::mulSpectrums( InputArray _srcA, InputArray _srcB, _dst.create( srcA.rows, srcA.cols, type ); Mat dst = _dst.getMat(); + // correct inplace support + if (dst.data == srcA.data) + srcA = srcA.clone(); + if (dst.data == srcB.data) + srcB = srcB.clone(); + bool is_1d = (flags & DFT_ROWS) || (rows == 1 || (cols == 1 && srcA.isContinuous() && srcB.isContinuous() && dst.isContinuous())); From ab429d2e73f5f683e7cb989468f158a52d835d6e Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 7 Dec 2016 19:55:40 +0300 Subject: [PATCH 184/409] test: fix inplace in 'mulComplex' from test_dxt --- modules/core/test/test_dxt.cpp | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/modules/core/test/test_dxt.cpp b/modules/core/test/test_dxt.cpp index 2aaabbf0f8..1b2995a541 100644 --- a/modules/core/test/test_dxt.cpp +++ b/modules/core/test/test_dxt.cpp @@ -419,9 +419,6 @@ static void fixCCS( Mat& mat, int cols, int flags ) } } -#if defined _MSC_VER && _MSC_VER >= 1700 -#pragma optimize("", off) -#endif static void mulComplex( const Mat& src1, const Mat& src2, Mat& dst, int flags ) { dst.create(src1.rows, src1.cols, src1.type()); @@ -430,12 +427,27 @@ static void mulComplex( const Mat& src1, const Mat& src2, Mat& dst, int flags ) CV_Assert( src1.size == src2.size && src1.type() == src2.type() && (src1.type() == CV_32FC2 || src1.type() == CV_64FC2) ); + const Mat* src1_ = &src1; + Mat src1_tmp; + if (dst.data == src1.data) + { + src1_tmp = src1.clone(); + src1_ = &src1_tmp; + } + const Mat* src2_ = &src2; + Mat src2_tmp; + if (dst.data == src2.data) + { + src2_tmp = src2.clone(); + src2_ = &src2_tmp; + } + for( i = 0; i < dst.rows; i++ ) { if( depth == CV_32F ) { - const float* a = src1.ptr(i); - const float* b = src2.ptr(i); + const float* a = src1_->ptr(i); + const float* b = src2_->ptr(i); float* c = dst.ptr(i); if( !(flags & CV_DXT_MUL_CONJ) ) @@ -459,8 +471,8 @@ static void mulComplex( const Mat& src1, const Mat& src2, Mat& dst, int flags ) } else { - const double* a = src1.ptr(i); - const double* b = src2.ptr(i); + const double* a = src1_->ptr(i); + const double* b = src2_->ptr(i); double* c = dst.ptr(i); if( !(flags & CV_DXT_MUL_CONJ) ) @@ -484,9 +496,6 @@ static void mulComplex( const Mat& src1, const Mat& src2, Mat& dst, int flags ) } } } -#if defined _MSC_VER && _MSC_VER >= 1700 -#pragma optimize("", on) -#endif } From 62c9ff25e59a32a740751bcbd1577565ab23bab7 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 7 Dec 2016 19:59:16 +0300 Subject: [PATCH 185/409] core: added inplace check in 'divSpectrums' --- modules/imgproc/src/phasecorr.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/imgproc/src/phasecorr.cpp b/modules/imgproc/src/phasecorr.cpp index 87e4d3080e..48d19fa3c1 100644 --- a/modules/imgproc/src/phasecorr.cpp +++ b/modules/imgproc/src/phasecorr.cpp @@ -167,6 +167,9 @@ static void divSpectrums( InputArray _srcA, InputArray _srcB, OutputArray _dst, _dst.create( srcA.rows, srcA.cols, type ); Mat dst = _dst.getMat(); + CV_Assert(dst.data != srcA.data); // non-inplace check + CV_Assert(dst.data != srcB.data); // non-inplace check + bool is_1d = (flags & DFT_ROWS) || (rows == 1 || (cols == 1 && srcA.isContinuous() && srcB.isContinuous() && dst.isContinuous())); From c4d4158bd87af88667369ef35169d3deb425d6ee Mon Sep 17 00:00:00 2001 From: catree Date: Wed, 7 Dec 2016 18:08:18 +0100 Subject: [PATCH 186/409] Add imread documentation note when EXIF orientation is embedded. --- modules/imgcodecs/include/opencv2/imgcodecs.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 0b9cafa278..6359de6e51 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -158,6 +158,8 @@ Currently, the following file formats are supported: then [GDAL](http://www.gdal.org) driver will be used in order to decode the image by supporting the following formats: [Raster](http://www.gdal.org/formats_list.html), [Vector](http://www.gdal.org/ogr_formats.html). +- If EXIF information are embedded in the image file, the EXIF orientation will be taken into account + and thus the image will be rotated accordingly except if the flag @ref IMREAD_IGNORE_ORIENTATION is passed. @param filename Name of file to be loaded. @param flags Flag that can take values of cv::ImreadModes */ From 79857f1e388e2cbc35dbb51d2c554ccc0bd66304 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 7 Dec 2016 21:39:29 +0300 Subject: [PATCH 187/409] test: fix Core_ArithmMask.uninitialized test Don't run binary operations for floating-point numbers norm() will fail with NAN result. --- modules/core/test/test_arithm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index ab140164c8..0781fd1a3f 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -1496,7 +1496,7 @@ TEST(Core_ArithmMask, uninitialized) int depth = rng.uniform(CV_8U, CV_64F+1); int cn = rng.uniform(1, 6); int type = CV_MAKETYPE(depth, cn); - int op = rng.uniform(0, 5); + int op = rng.uniform(0, depth < CV_32F ? 5 : 2); // don't run binary operations between floating-point values int depth1 = op <= 1 ? CV_64F : depth; for (int k = 0; k < MAX_DIM; k++) { From ee265962b2ea9abef5111f64f76f617554f7b473 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 7 Dec 2016 22:04:25 +0300 Subject: [PATCH 188/409] test: move test_drawing into imgproc module --- modules/{imgcodecs => imgproc}/test/test_drawing.cpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/{imgcodecs => imgproc}/test/test_drawing.cpp (100%) diff --git a/modules/imgcodecs/test/test_drawing.cpp b/modules/imgproc/test/test_drawing.cpp similarity index 100% rename from modules/imgcodecs/test/test_drawing.cpp rename to modules/imgproc/test/test_drawing.cpp From 036dd8d48f68edd8fe229e9d2a91de255e7b4c29 Mon Sep 17 00:00:00 2001 From: "Randy J. Ray" Date: Thu, 8 Dec 2016 00:50:59 -0800 Subject: [PATCH 189/409] Contribute a tutorial on building for NVIDIA Tegra (#7814) * Contribute a tutorial on building for NVIDIA Tegra * Clean-up and fixes based on feedback. --- .../building_tegra_cuda.markdown | 636 ++++++++++++++++++ .../table_of_content_introduction.markdown | 8 + 2 files changed, 644 insertions(+) create mode 100644 doc/tutorials/introduction/building_tegra_cuda/building_tegra_cuda.markdown diff --git a/doc/tutorials/introduction/building_tegra_cuda/building_tegra_cuda.markdown b/doc/tutorials/introduction/building_tegra_cuda/building_tegra_cuda.markdown new file mode 100644 index 0000000000..b93f23220b --- /dev/null +++ b/doc/tutorials/introduction/building_tegra_cuda/building_tegra_cuda.markdown @@ -0,0 +1,636 @@ +Building OpenCV for Tegra with CUDA {#tutorial_building_tegra_cuda} +=================================== + +@tableofcontents + +OpenCV with CUDA for Tegra +========================== + +This document is a basic guide to building the OpenCV libraries with CUDA support for use in the Tegra environment. It covers the basic elements of building the version 3.1.0 libraries from source code for three (3) different types of platforms: + +* NVIDIA DRIVE™ PX 2 (V4L) +* NVIDIA® Tegra® Linux Driver Package (L4T) +* Desktop Linux (Ubuntu 14.04 LTS and 16.04 LTS) + +This document is not an exhaustive guide to all of the options available when building OpenCV. Specifically, it covers the basic options used when building each platform but does not cover any options that are not needed (or are unchanged from their default values). Additionally, the installation of the CUDA toolkit is not covered here. + +This document is focused on building the 3.1.0 version of OpenCV, but the guidelines here may also work for building from the master branch of the git repository. There are differences in some of the CMake options for builds of the 2.4.13 version of OpenCV, which are summarized below in the @ref tutorial_building_tegra_cuda_opencv_24X section. + +Most of the configuration commands are based on the system having CUDA 8.0 installed. In the case of the Jetson TK1, an older CUDA is used because 8.0 is not supported for that platform. These instructions may also work with older versions of CUDA, but are only tested with 8.0. + +### A Note on Native Compilation vs. Cross-Compilation + +The OpenCV build system supports native compilation for all the supported platforms, as well as cross-compilation for platforms such as ARM and others. The native compilation process is simpler, whereas the cross-compilation is generally faster. + +At the present time, this document focuses only on native compilation. + +Getting the Source Code {#tutorial_building_tegra_cuda_getting_the_code} +======================= + +There are two (2) ways to get the OpenCV source code: + +* Direct download from the [OpenCV downloads](http://opencv.org/downloads.html) page +* Cloning the git repositories hosted on [GitHub](https://github.com/opencv) + +For this guide, the focus is on using the git repositories. This is because the 3.1.0 version of OpenCV will not build with CUDA 8.0 without applying a small upstream change from the git repository. + +OpenCV +------ + +Start with the `opencv` repository: + + # Clone the opencv repository locally: + $ git clone https://github.com/opencv/opencv.git + +To build the 3.1.0 version (as opposed to building the most-recent source), you must check out a branch based on the `3.1.0` tag: + + $ cd opencv + $ git checkout -b v3.1.0 3.1.0 + +__Note:__ This operation creates a new local branch in your clone's repository. + +If you are building OpenCV with CUDA 8.0, you must execute one additional git command. This is to apply a fix for building specifically with the 8.0 version of CUDA that was not part of the 3.1.0 release. To do this, use the "git cherry-pick" command: + + # While still in the opencv directory: + $ git cherry-pick 10896 + +You will see the following output from the command: + + [v3.1.0 d6d69a7] GraphCut deprecated in CUDA 7.5 and removed in 8.0 + Author: Vladislav Vinogradov + 1 file changed, 2 insertions(+), 1 deletion(-) + +This step is not needed if you are building with CUDA 7.0 or 7.5. + +OpenCV Extra +------------ + +The `opencv_extra` repository contains extra data for the OpenCV library, including the data files used by the tests and demos. It must be cloned separately: + + # In the same base directory from which you cloned OpenCV: + $ git clone https://github.com/opencv/opencv_extra.git + +As with the OpenCV source, you must use the same method as above to set the source tree to the 3.1.0 version. When you are building from a specific tag, both repositories must be checked out at that tag. + + $ cd opencv_extra + $ git checkout -b v3.1.0 3.1.0 + +You may opt to not fetch this repository if you do not plan on running the tests or installing the test-data along with the samples and example programs. If it is not referenced in the invocation of CMake, it will not be used. + +__Note:__ If you plan to run the tests, some tests expect the data to be present and will fail without it. + +Preparation and Prerequisites {#tutorial_building_tegra_cuda_preparation} +============================= + +To build OpenCV, you need a directory to create the configuration and build the libraries. You also need a number of 3rd-party libraries upon which OpenCV depends. + +Prerequisites for Ubuntu Linux +------------------------------ + +These are the basic requirements for building OpenCV for Tegra on Linux: + +* CMake 2.8.10 or newer +* CUDA toolkit 8.0 (7.0 or 7.5 may also be used) +* Build tools (make, gcc, g++) +* Python 2.6 or greater + +These are the same regardless of the platform (DRIVE PX 2, Desktop, etc.). + +A number of development packages are required for building on Linux: + +* libglew-dev +* libtiff5-dev +* zlib1g-dev +* libjpeg-dev +* libpng12-dev +* libjasper-dev +* libavcodec-dev +* libavformat-dev +* libavutil-dev +* libpostproc-dev +* libswscale-dev +* libeigen3-dev +* libtbb-dev +* libgtk2.0-dev +* pkg-config + +Some of the packages above are in the `universe` repository for Ubuntu Linux systems. If you have not already enabled that repository, you need to do the following before trying to install all of the packages listed above: + + $ sudo apt-add-repository universe + $ sudo apt-get update + +The following command can be pasted into a shell in order to install the required packages: + + $ sudo apt-get install \ + libglew-dev \ + libtiff5-dev \ + zlib1g-dev \ + libjpeg-dev \ + libpng12-dev \ + libjasper-dev \ + libavcodec-dev \ + libavformat-dev \ + libavutil-dev \ + libpostproc-dev \ + libswscale-dev \ + libeigen3-dev \ + libtbb-dev \ + libgtk2.0-dev \ + pkg-config + +(Line-breaks and continuation characters are added for readability.) + +If you want the Python bindings to be built, you will also need the appropriate packages for either or both of Python 2 and Python 3: + +* python-dev / python3-dev +* python-numpy / python3-numpy +* python-py / python3-py +* python-pytest / python3-pytest + +The commands that will do this: + + $ sudo apt-get install python-dev python-numpy python-py python-pytest + # And, optionally: + $ sudo apt-get install python3-dev python3-numpy python3-py python3-pytest + +Once all the necessary packages are installed, you can configure the build. + +Preparing the Build Area +------------------------ + +Software projects that use the CMake system for configuring their builds expect the actual builds to be done outside of the source tree itself. For configuring and building OpenCV, create a directory called "build" in the same base directory into which you cloned the git repositories: + + $ mkdir build + $ cd build + +You are now ready to configure and build OpenCV. + +Configuring OpenCV for Building {#tutorial_building_tegra_cuda_configuring} +=============================== + +The CMake configuration options given below for the different platforms are targeted towards the functionality needed for Tegra. They are based on the original configuration options used for building OpenCV 2.4.13. + +The build of OpenCV is configured with CMake. If run with no parameters, it detects what it needs to know about your system. However, it may have difficulty finding the CUDA files if they are not in a standard location, and it may try to build some options that you might otherwise not want included, so the following invocations of CMake are recommended. + +In each `cmake` command listed in the following sub-sections, line-breaks and indentation are added for readability. Continuation characters are also added in examples for Linux-based platforms, allowing you to copy and paste the examples directly into a shell. When entering these commands by hand, enter the command and options as a single line. For a detailed explanation of the parameters passed to `cmake`, see the "CMake Parameter Reference" section. + +For the Linux-based platforms, the shown value for the `CMAKE_INSTALL_PREFIX` parameter is `/usr`. You can set this to whatever you want, based on the layout of your system. + +In each of the `cmake` invocations below, the last parameter, `OPENCV_TEST_DATA_PATH`, tells the build system where to find the test-data that is provided by the `opencv_extra` repository. When this is included, a `make install` installs this test-data alongside the libraries and example code, and a `make test` automatically provides this path to the tests that have to load data from it. If you did not clone the `opencv_extra` repository, do not include this parameter. + +Vibrante V4L Configuration +-------------------------- + +Supported platform: Drive PX 2 + + $ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_PNG=OFF \ + -DBUILD_TIFF=OFF \ + -DBUILD_TBB=OFF \ + -DBUILD_JPEG=OFF \ + -DBUILD_JASPER=OFF \ + -DBUILD_ZLIB=OFF \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_opencv_java=OFF \ + -DBUILD_opencv_python2=ON \ + -DBUILD_opencv_python3=OFF \ + -DENABLE_NEON=ON \ + -DWITH_OPENCL=OFF \ + -DWITH_OPENMP=OFF \ + -DWITH_FFMPEG=ON \ + -DWITH_GSTREAMER=OFF \ + -DWITH_GSTREAMER_0_10=OFF \ + -DWITH_CUDA=ON \ + -DWITH_GTK=ON \ + -DWITH_VTK=OFF \ + -DWITH_TBB=ON \ + -DWITH_1394=OFF \ + -DWITH_OPENEXR=OFF \ + -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-8.0 \ + -DCUDA_ARCH_BIN=6.2 \ + -DCUDA_ARCH_PTX="" \ + -DINSTALL_C_EXAMPLES=ON \ + -DINSTALL_TESTS=OFF \ + -DOPENCV_TEST_DATA_PATH=../opencv_extra/testdata \ + ../opencv + +The configuration provided above builds the Python bindings for Python 2 (but not Python 3) as part of the build process. If you want the Python 3 bindings (or do not want the Python 2 bindings), change the values of `BUILD_opencv_python2` and/or `BUILD_opencv_python3` as needed. To enable bindings, set the value to `ON`, to disable them set it to `OFF`: + + -DBUILD_opencv_python2=OFF + +Jetson L4T Configuration +------------------------ + +Supported platforms: + +* Jetson TK1 +* Jetson TX1 + +Configuration is slightly different for the Jetson TK1 and the Jetson TX1 systems. + +### Jetson TK1 + + $ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_CXX_FLAGS=-Wa,-mimplicit-it=thumb \ + -DBUILD_PNG=OFF \ + -DBUILD_TIFF=OFF \ + -DBUILD_TBB=OFF \ + -DBUILD_JPEG=OFF \ + -DBUILD_JASPER=OFF \ + -DBUILD_ZLIB=OFF \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_opencv_java=OFF \ + -DBUILD_opencv_python2=ON \ + -DBUILD_opencv_python3=OFF \ + -DENABLE_NEON=ON \ + -DWITH_OPENCL=OFF \ + -DWITH_OPENMP=OFF \ + -DWITH_FFMPEG=ON \ + -DWITH_GSTREAMER=OFF \ + -DWITH_GSTREAMER_0_10=OFF \ + -DWITH_CUDA=ON \ + -DWITH_GTK=ON \ + -DWITH_VTK=OFF \ + -DWITH_TBB=ON \ + -DWITH_1394=OFF \ + -DWITH_OPENEXR=OFF \ + -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-6.5 \ + -DCUDA_ARCH_BIN=3.2 \ + -DCUDA_ARCH_PTX="" \ + -DINSTALL_C_EXAMPLES=ON \ + -DINSTALL_TESTS=OFF \ + -DOPENCV_TEST_DATA_PATH=../opencv_extra/testdata \ + ../opencv + +__Note:__ This uses CUDA 6.5, not 8.0. + +### Jetson TX1 + + $ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_PNG=OFF \ + -DBUILD_TIFF=OFF \ + -DBUILD_TBB=OFF \ + -DBUILD_JPEG=OFF \ + -DBUILD_JASPER=OFF \ + -DBUILD_ZLIB=OFF \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_opencv_java=OFF \ + -DBUILD_opencv_python2=ON \ + -DBUILD_opencv_python3=OFF \ + -DENABLE_PRECOMPILED_HEADERS=OFF \ + -DWITH_OPENCL=OFF \ + -DWITH_OPENMP=OFF \ + -DWITH_FFMPEG=ON \ + -DWITH_GSTREAMER=OFF \ + -DWITH_GSTREAMER_0_10=OFF \ + -DWITH_CUDA=ON \ + -DWITH_GTK=ON \ + -DWITH_VTK=OFF \ + -DWITH_TBB=ON \ + -DWITH_1394=OFF \ + -DWITH_OPENEXR=OFF \ + -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-8.0 \ + -DCUDA_ARCH_BIN=5.3 \ + -DCUDA_ARCH_PTX="" \ + -DINSTALL_C_EXAMPLES=ON \ + -DINSTALL_TESTS=OFF \ + -DOPENCV_TEST_DATA_PATH=../opencv_extra/testdata \ + ../opencv + +__Note:__ This configuration does not set the `ENABLE_NEON` parameter. + +Ubuntu Desktop Linux Configuration +---------------------------------- + +Supported platforms: + +* Ubuntu Desktop Linux 14.04 LTS +* Ubuntu Desktop Linux 16.04 LTS + +The configuration options given to `cmake` below are targeted towards the functionality needed for Tegra. For a desktop system, you may wish to adjust some options to enable (or disable) certain features. The features enabled below are based on the building of OpenCV 2.4.13. + + $ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_PNG=OFF \ + -DBUILD_TIFF=OFF \ + -DBUILD_TBB=OFF \ + -DBUILD_JPEG=OFF \ + -DBUILD_JASPER=OFF \ + -DBUILD_ZLIB=OFF \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_opencv_java=OFF \ + -DBUILD_opencv_python2=ON \ + -DBUILD_opencv_python3=OFF \ + -DWITH_OPENCL=OFF \ + -DWITH_OPENMP=OFF \ + -DWITH_FFMPEG=ON \ + -DWITH_GSTREAMER=OFF \ + -DWITH_GSTREAMER_0_10=OFF \ + -DWITH_CUDA=ON \ + -DWITH_GTK=ON \ + -DWITH_VTK=OFF \ + -DWITH_TBB=ON \ + -DWITH_1394=OFF \ + -DWITH_OPENEXR=OFF \ + -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-8.0 \ + -DCUDA_ARCH_BIN='3.0 3.5 5.0 6.0 6.2' \ + -DCUDA_ARCH_PTX="" \ + -DINSTALL_C_EXAMPLES=ON \ + -DINSTALL_TESTS=OFF \ + -DOPENCV_TEST_DATA_PATH=../opencv_extra/testdata \ + ../opencv + +This configuration is nearly identical to that for V4L and L4T, except that the `CUDA_ARCH_BIN` parameter specifies multiple architectures so as to support a variety of GPU boards. For a desktop, you have the option of omitting this parameter, and CMake will instead run a small test program that probes for the supported architectures. However, the libraries produced might not work on Ubuntu systems with different cards. + +As with previous examples, the configuration given above builds the Python bindings for Python 2 (but not Python 3) as part of the build process. + +Building OpenCV {#tutorial_building_tegra_cuda_building} +=============== + +Once `cmake` finishes configuring OpenCV, building is done using the standard `make` utility. + +Building with `make` +-------------------- + +The only parameter that is needed for the invocation of `make` is the `-j` parameter for specifying how many parallel threads to use. This varies depending on the system and how much memory is available, other running processes, etc. The following table offers suggested values for this parameter: + +|Platform|Suggested value|Notes| +|--------|---------------|-----| +|DRIVE PX 2|6| | +|Jetson TK1|3|If the build fails due to a compiler-related error, try again with a smaller number of threads. Also consider rebooting the system if it has been running for a long time since the last reboot.| +|Jetson TX1|4| | +|Ubuntu Desktop|7|The actual value will vary with the number of cores you have and the amount of physical memory. Because of the resource requirements of compiling the CUDA code, it is not recommended to go above 7.| + +Based on the value you select, build (assuming you selected 6): + + $ make -j6 + +By default, CMake hides the details of the build steps. If you need to see more detail about each compilation unit, etc., you can enable verbose output: + + $ make -j6 VERBOSE=1 + +Testing OpenCV {#tutorial_building_tegra_cuda_testing} +============== + +Once the build completes successfully, you have the option of running the extensive set of tests that OpenCV provides. If you did not clone the `opencv_extra` repository and specify the path to `testdata` in the `cmake` invocation, then testing is not recommended. + +Testing under Linux +------------------- + +To run the basic tests under Linux, execute: + + $ make test + +This executes `ctest` to carry out the tests, as specified in CTest syntax within the OpenCV repository. The `ctest` harness takes many different parameters (too many to list here, see the manual page for CTest to see the full set), and if you wish to pass any of them, you can do so by specifying them in a `make` command-line parameter called `ARGS`: + + $ make test ARGS="--verbose --parallel 3" + +In this example, there are two (2) arguments passed to `ctest`: `--verbose` and `--parallel 3`. The first argument causes the output from `ctest` to be more detailed, and the second causes `ctest` to run as many as three (3) tests in parallel. As with choosing a thread count for building, base any choice for testing on the available number of processor cores, physical memory, etc. Some of the tests do attempt to allocate significant amounts of memory. + +### Known Issues with Tests + +At present, not all of the tests in the OpenCV test suite pass. There are tests that fail whether or not CUDA is compiled, and there are tests that are only specific to CUDA that also do not currently pass. + +__Note:__ There are no tests that pass without CUDA but fail only when CUDA is included. + +As the full lists of failing tests vary based on platform, it is impractical to list them here. + +Installing OpenCV {#tutorial_building_tegra_cuda_installing} +================= + +Installing OpenCV is very straightforward. For the Linux-based platforms, the command is: + + $ make install + +Depending on the chosen installation location, you may need root privilege to install. + +Building OpenCV 2.4.X {#tutorial_building_tegra_cuda_opencv_24X} +===================== + +If you wish to build your own version of the 2.4 version of OpenCV, there are only a few adjustments that must be made. At the time of this writing, the latest version on the 2.4 tree is 2.4.13. These instructions may work for later versions of 2.4, though they have not been tested for any earlier versions. + +__Note:__ The 2.4.X OpenCV source does not have the extra modules and code for Tegra that was upstreamed into the 3.X versions of OpenCV. This part of the guide is only for cases where you want to build a vanilla version of OpenCV 2.4. + +Selecting the 2.4 Source +------------------------ + +First you must select the correct source branch or tag. If you want a specific version such as 2.4.13, you want to make a local branch based on the tag, as was done with the 3.1.0 tag above: + + # Within the opencv directory: + $ git checkout -b v2.4.13 2.4.13 + + # Within the opencv_extra directory: + $ git checkout -b v2.4.13 2.4.13 + +If you simply want the newest code from the 2.4 line of OpenCV, there is a `2.4` branch already in the repository. You can check that out instead of a specific tag: + + $ git checkout 2.4 + +There is no need for the `git cherry-pick` commands used with 3.1.0 when building the 2.4.13 source. + +Configuring +----------- + +Configuring is done with CMake as before. The primary difference is that OpenCV 2.4 only provides Python bindings for Python 2, and thus does not distinguish between Python 2 and Python 3 in the CMake parameters. There is only one parameter, `BUILD_opencv_python`. In addition, there is a build-related parameter that controls features in 2.4 that are not in 3.1.0. This parameter is `BUILD_opencv_nonfree`. + +Configuration still takes place in a separate directory that must be a sibling to the `opencv` and `opencv_extra` directories. + +### Configuring Vibrante V4L + +For DRIVE PX 2: + + $ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_PNG=OFF \ + -DBUILD_TIFF=OFF \ + -DBUILD_TBB=OFF \ + -DBUILD_JPEG=OFF \ + -DBUILD_JASPER=OFF \ + -DBUILD_ZLIB=OFF \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_opencv_java=OFF \ + -DBUILD_opencv_nonfree=OFF \ + -DBUILD_opencv_python=ON \ + -DENABLE_NEON=ON \ + -DWITH_OPENCL=OFF \ + -DWITH_OPENMP=OFF \ + -DWITH_FFMPEG=ON \ + -DWITH_GSTREAMER=OFF \ + -DWITH_GSTREAMER_0_10=OFF \ + -DWITH_CUDA=ON \ + -DWITH_GTK=ON \ + -DWITH_VTK=OFF \ + -DWITH_TBB=ON \ + -DWITH_1394=OFF \ + -DWITH_OPENEXR=OFF \ + -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-8.0 \ + -DCUDA_ARCH_BIN=6.2 \ + -DCUDA_ARCH_PTX="" \ + -DINSTALL_C_EXAMPLES=ON \ + -DINSTALL_TESTS=ON \ + -DOPENCV_TEST_DATA_PATH=../opencv_extra/testdata \ + ../opencv + +### Configuring Jetson L4T + +For Jetson TK1: + + $ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_PNG=OFF \ + -DBUILD_TIFF=OFF \ + -DBUILD_TBB=OFF \ + -DBUILD_JPEG=OFF \ + -DBUILD_JASPER=OFF \ + -DBUILD_ZLIB=OFF \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_opencv_java=OFF \ + -DBUILD_opencv_nonfree=OFF \ + -DBUILD_opencv_python=ON \ + -DENABLE_NEON=ON \ + -DWITH_OPENCL=OFF \ + -DWITH_OPENMP=OFF \ + -DWITH_FFMPEG=ON \ + -DWITH_GSTREAMER=OFF \ + -DWITH_GSTREAMER_0_10=OFF \ + -DWITH_CUDA=ON \ + -DWITH_GTK=ON \ + -DWITH_VTK=OFF \ + -DWITH_TBB=ON \ + -DWITH_1394=OFF \ + -DWITH_OPENEXR=OFF \ + -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-6.5 \ + -DCUDA_ARCH_BIN=3.2 \ + -DCUDA_ARCH_PTX="" \ + -DINSTALL_C_EXAMPLES=ON \ + -DINSTALL_TESTS=ON \ + -DOPENCV_TEST_DATA_PATH=../opencv_extra/testdata \ + ../opencv + +For Jetson TX1: + + $ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_PNG=OFF \ + -DBUILD_TIFF=OFF \ + -DBUILD_TBB=OFF \ + -DBUILD_JPEG=OFF \ + -DBUILD_JASPER=OFF \ + -DBUILD_ZLIB=OFF \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_opencv_java=OFF \ + -DBUILD_opencv_nonfree=OFF \ + -DBUILD_opencv_python=ON \ + -DENABLE_PRECOMPILED_HEADERS=OFF \ + -DWITH_OPENCL=OFF \ + -DWITH_OPENMP=OFF \ + -DWITH_FFMPEG=ON \ + -DWITH_GSTREAMER=OFF \ + -DWITH_GSTREAMER_0_10=OFF \ + -DWITH_CUDA=ON \ + -DWITH_GTK=ON \ + -DWITH_VTK=OFF \ + -DWITH_TBB=ON \ + -DWITH_1394=OFF \ + -DWITH_OPENEXR=OFF \ + -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-8.0 \ + -DCUDA_ARCH_BIN=5.3 \ + -DCUDA_ARCH_PTX="" \ + -DINSTALL_C_EXAMPLES=ON \ + -DINSTALL_TESTS=ON \ + -DOPENCV_TEST_DATA_PATH=../opencv_extra/testdata \ + ../opencv + +### Configuring Desktop Ubuntu Linux + +For both 14.04 LTS and 16.04 LTS: + + $ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_PNG=OFF \ + -DBUILD_TIFF=OFF \ + -DBUILD_TBB=OFF \ + -DBUILD_JPEG=OFF \ + -DBUILD_JASPER=OFF \ + -DBUILD_ZLIB=OFF \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_opencv_java=OFF \ + -DBUILD_opencv_nonfree=OFF \ + -DBUILD_opencv_python=ON \ + -DWITH_OPENCL=OFF \ + -DWITH_OPENMP=OFF \ + -DWITH_FFMPEG=ON \ + -DWITH_GSTREAMER=OFF \ + -DWITH_GSTREAMER_0_10=OFF \ + -DWITH_CUDA=ON \ + -DWITH_GTK=ON \ + -DWITH_VTK=OFF \ + -DWITH_TBB=ON \ + -DWITH_1394=OFF \ + -DWITH_OPENEXR=OFF \ + -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-8.0 \ + -DCUDA_ARCH_BIN='3.0 3.5 5.0 6.0 6.2' \ + -DCUDA_ARCH_PTX="" \ + -DINSTALL_C_EXAMPLES=ON \ + -DINSTALL_TESTS=ON \ + -DOPENCV_TEST_DATA_PATH=../opencv_extra/testdata \ + ../opencv + +Building, Testing and Installing +-------------------------------- + +Once configured, the steps of building, testing, and installing are the same as above for the 3.1.0 source. + +CMake Parameter Reference {#tutorial_building_tegra_cuda_parameter_reference} +========================= + +The following is a table of all the parameters passed to CMake in the recommended invocations above. Some of these are parameters from CMake itself, while most are specific to OpenCV. + +|Parameter|Our Default Value|What It Does|Notes| +|---------|-----------------|------------|-----| +|BUILD_EXAMPLES|ON|Governs whether the C/C++ examples are built| | +|BUILD_JASPER|OFF|Governs whether the Jasper library (`libjasper`) is built from source in the `3rdparty` directory| | +|BUILD_JPEG|OFF|As above, for `libjpeg`| | +|BUILD_PNG|OFF|As above, for `libpng`| | +|BUILD_TBB|OFF|As above, for `tbb`| | +|BUILD_TIFF|OFF|As above, for `libtiff`| | +|BUILD_ZLIB|OFF|As above, for `zlib`| | +|BUILD_opencv_java|OFF|Controls the building of the Java bindings for OpenCV|Building the Java bindings requires OpenCV libraries be built for static linking only| +|BUILD_opencv_nonfree|OFF|Controls the building of non-free (non-open-source) elements|Used only for building 2.4.X| +|BUILD_opencv_python|ON|Controls the building of the Python 2 bindings in OpenCV 2.4.X|Used only for building 2.4.X| +|BUILD_opencv_python2|ON|Controls the building of the Python 2 bindings in OpenCV 3.1.0|Not used in 2.4.X| +|BUILD_opencv_python3|OFF|Controls the building of the Python 3 bindings in OpenCV 3.1.0|Not used in 2.4.X| +|CMAKE_BUILD_TYPE|Release|Selects the type of build (release vs. development)|Is generally either `Release` or `Debug`| +|CMAKE_INSTALL_PREFIX|/usr|Sets the root for installation of the libraries and header files| | +|CUDA_ARCH_BIN|varies|Sets the CUDA architecture(s) for which code is compiled|Usually only passed for platforms with known specific cards. OpenCV includes a small program that determines the architectures of the system's installed card if you do not pass this parameter. Here, for Ubuntu desktop, the value is a list to maximize card support.| +|CUDA_ARCH_PTX|""|Builds PTX intermediate code for the specified virtual PTX architectures| | +|CUDA_TOOLKIT_ROOT_DIR|/usr/local/cuda-8.0 (for Linux)|Specifies the location of the CUDA include files and libraries| | +|ENABLE_NEON|ON|Enables the use of NEON SIMD extensions for ARM chips|Only passed for builds on ARM platforms| +|ENABLE_PRECOMPILED_HEADERS|OFF|Enables/disables support for pre-compiled headers|Only specified on some of the ARM platforms| +|INSTALL_C_EXAMPLES|ON|Enables the installation of the C example files as part of `make install`| | +|INSTALL_TESTS|ON|Enables the installation of the tests as part of `make install`| | +|OPENCV_TEST_DATA_PATH|../opencv_extra/testdata|Path to the `testdata` directory in the `opencv_extra` repository| | +|WITH_1394|OFF|Specifies whether to include IEEE-1394 support| | +|WITH_CUDA|ON|Specifies whether to include CUDA support| | +|WITH_FFMPEG|ON|Specifies whether to include FFMPEG support| | +|WITH_GSTREAMER|OFF|Specifies whether to include GStreamer 1.0 support| | +|WITH_GSTREAMER_0_10|OFF|Specifies whether to include GStreamer 0.10 support| | +|WITH_GTK|ON|Specifies whether to include GTK 2.0 support|Only given on Linux platforms, not Microsoft Windows| +|WITH_OPENCL|OFF|Specifies whether to include OpenCL runtime support| | +|WITH_OPENEXR|OFF|Specifies whether to include ILM support via OpenEXR| | +|WITH_OPENMP|OFF|Specifies whether to include OpenMP runtime support| | +|WITH_TBB|ON|Specifies whether to include Intel TBB support| | +|WITH_VTK|OFF|Specifies whether to include VTK support| | + +Copyright © 2016, NVIDIA CORPORATION. All rights reserved. diff --git a/doc/tutorials/introduction/table_of_content_introduction.markdown b/doc/tutorials/introduction/table_of_content_introduction.markdown index 780d0c1767..37820a8e1b 100644 --- a/doc/tutorials/introduction/table_of_content_introduction.markdown +++ b/doc/tutorials/introduction/table_of_content_introduction.markdown @@ -126,6 +126,14 @@ Additionally you can find very basic sample source code to introduce you to the We will learn how to setup OpenCV cross compilation environment for ARM Linux. +- @subpage tutorial_building_tegra_cuda + + _Compatibility:_ \>= OpenCV 3.1.0 + + _Author:_ Randy J. Ray + + This tutorial will help you build OpenCV 3.1.0 for NVIDIA® Tegra® systems with CUDA 8.0. + - @subpage tutorial_display_image _Compatibility:_ \> OpenCV 2.0 From e941259434f85f412e1ad0221b81d66faa3d1cc3 Mon Sep 17 00:00:00 2001 From: mshabunin Date: Thu, 8 Dec 2016 14:58:18 +0300 Subject: [PATCH 190/409] Persistence: fixed valgrind warning in base64 decoder --- modules/core/src/persistence.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index 40b846ff84..ccdb135ec5 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -7513,6 +7513,8 @@ bool base64::base64_valid(uint8_t const * src, size_t off, size_t cnt) return false; if (cnt == 0U) cnt = std::strlen(reinterpret_cast(src)); + if (cnt == 0U) + return false; if (cnt & 0x3U) return false; From 6946f510fe134b55989cf0268b6cbfad6ea6d2e4 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 8 Dec 2016 13:20:38 +0300 Subject: [PATCH 191/409] mulSpectrums: refactor code --- modules/core/src/dxt.cpp | 271 ++++++++++++++++++++------------------- 1 file changed, 138 insertions(+), 133 deletions(-) diff --git a/modules/core/src/dxt.cpp b/modules/core/src/dxt.cpp index b94f65a1e9..ea587b1870 100644 --- a/modules/core/src/dxt.cpp +++ b/modules/core/src/dxt.cpp @@ -1891,13 +1891,131 @@ void cv::idft( InputArray src, OutputArray dst, int flags, int nonzero_rows ) dft( src, dst, flags | DFT_INVERSE, nonzero_rows ); } +namespace { + +#define VAL(buf, elem) (((T*)((char*)data ## buf + (step ## buf * (elem))))[0]) +#define MUL_SPECTRUMS_COL(A, B, C) \ + VAL(C, 0) = VAL(A, 0) * VAL(B, 0); \ + for (size_t j = 1; j <= rows - 2; j += 2) \ + { \ + double a_re = VAL(A, j), a_im = VAL(A, j + 1); \ + double b_re = VAL(B, j), b_im = VAL(B, j + 1); \ + if (conjB) b_im = -b_im; \ + double c_re = a_re * b_re - a_im * b_im; \ + double c_im = a_re * b_im + a_im * b_re; \ + VAL(C, j) = (T)c_re; VAL(C, j + 1) = (T)c_im; \ + } \ + if ((rows & 1) == 0) \ + VAL(C, rows-1) = VAL(A, rows-1) * VAL(B, rows-1) + +template static inline +void mulSpectrums_processCol_noinplace(const T* dataA, const T* dataB, T* dataC, size_t stepA, size_t stepB, size_t stepC, size_t rows) +{ + MUL_SPECTRUMS_COL(A, B, C); +} + +template static inline +void mulSpectrums_processCol_inplaceA(const T* dataB, T* dataAC, size_t stepB, size_t stepAC, size_t rows) +{ + MUL_SPECTRUMS_COL(AC, B, AC); +} +template static inline +void mulSpectrums_processCol(const T* dataA, const T* dataB, T* dataC, size_t stepA, size_t stepB, size_t stepC, size_t rows) +{ + if (inplaceA) + mulSpectrums_processCol_inplaceA(dataB, dataC, stepB, stepC, rows); + else + mulSpectrums_processCol_noinplace(dataA, dataB, dataC, stepA, stepB, stepC, rows); +} +#undef MUL_SPECTRUMS_COL +#undef VAL + +template static inline +void mulSpectrums_processCols(const T* dataA, const T* dataB, T* dataC, size_t stepA, size_t stepB, size_t stepC, size_t rows, size_t cols) +{ + mulSpectrums_processCol(dataA, dataB, dataC, stepA, stepB, stepC, rows); + if ((cols & 1) == 0) + { + mulSpectrums_processCol(dataA + cols - 1, dataB + cols - 1, dataC + cols - 1, stepA, stepB, stepC, rows); + } +} + +#define VAL(buf, elem) (data ## buf[(elem)]) +#define MUL_SPECTRUMS_ROW(A, B, C) \ + for (size_t j = j0; j < j1; j += 2) \ + { \ + double a_re = VAL(A, j), a_im = VAL(A, j + 1); \ + double b_re = VAL(B, j), b_im = VAL(B, j + 1); \ + if (conjB) b_im = -b_im; \ + double c_re = a_re * b_re - a_im * b_im; \ + double c_im = a_re * b_im + a_im * b_re; \ + VAL(C, j) = (T)c_re; VAL(C, j + 1) = (T)c_im; \ + } +template static inline +void mulSpectrums_processRow_noinplace(const T* dataA, const T* dataB, T* dataC, size_t j0, size_t j1) +{ + MUL_SPECTRUMS_ROW(A, B, C); +} +template static inline +void mulSpectrums_processRow_inplaceA(const T* dataB, T* dataAC, size_t j0, size_t j1) +{ + MUL_SPECTRUMS_ROW(AC, B, AC); +} +template static inline +void mulSpectrums_processRow(const T* dataA, const T* dataB, T* dataC, size_t j0, size_t j1) +{ + if (inplaceA) + mulSpectrums_processRow_inplaceA(dataB, dataC, j0, j1); + else + mulSpectrums_processRow_noinplace(dataA, dataB, dataC, j0, j1); +} +#undef MUL_SPECTRUMS_ROW +#undef VAL + +template static inline +void mulSpectrums_processRows(const T* dataA, const T* dataB, T* dataC, size_t stepA, size_t stepB, size_t stepC, size_t rows, size_t cols, size_t j0, size_t j1, bool is_1d_CN1) +{ + while (rows-- > 0) + { + if (is_1d_CN1) + dataC[0] = dataA[0]*dataB[0]; + mulSpectrums_processRow(dataA, dataB, dataC, j0, j1); + if (is_1d_CN1 && (cols & 1) == 0) + dataC[j1] = dataA[j1]*dataB[j1]; + + dataA = (const T*)(((char*)dataA) + stepA); + dataB = (const T*)(((char*)dataB) + stepB); + dataC = (T*)(((char*)dataC) + stepC); + } +} + + +template static inline +void mulSpectrums_Impl_(const T* dataA, const T* dataB, T* dataC, size_t stepA, size_t stepB, size_t stepC, size_t rows, size_t cols, size_t j0, size_t j1, bool is_1d, bool isCN1) +{ + if (!is_1d && isCN1) + { + mulSpectrums_processCols(dataA, dataB, dataC, stepA, stepB, stepC, rows, cols); + } + mulSpectrums_processRows(dataA, dataB, dataC, stepA, stepB, stepC, rows, cols, j0, j1, is_1d && isCN1); +} +template static inline +void mulSpectrums_Impl(const T* dataA, const T* dataB, T* dataC, size_t stepA, size_t stepB, size_t stepC, size_t rows, size_t cols, size_t j0, size_t j1, bool is_1d, bool isCN1) +{ + if (dataA == dataC) + mulSpectrums_Impl_(dataA, dataB, dataC, stepA, stepB, stepC, rows, cols, j0, j1, is_1d, isCN1); + else + mulSpectrums_Impl_(dataA, dataB, dataC, stepA, stepB, stepC, rows, cols, j0, j1, is_1d, isCN1); +} + +} // namespace + void cv::mulSpectrums( InputArray _srcA, InputArray _srcB, OutputArray _dst, int flags, bool conjB ) { Mat srcA = _srcA.getMat(), srcB = _srcB.getMat(); int depth = srcA.depth(), cn = srcA.channels(), type = srcA.type(); - int rows = srcA.rows, cols = srcA.cols; - int j, k; + size_t rows = srcA.rows, cols = srcA.cols; CV_Assert( type == srcB.type() && srcA.size() == srcB.size() ); CV_Assert( type == CV_32FC1 || type == CV_32FC2 || type == CV_64FC1 || type == CV_64FC2 ); @@ -1906,154 +2024,41 @@ void cv::mulSpectrums( InputArray _srcA, InputArray _srcB, Mat dst = _dst.getMat(); // correct inplace support - if (dst.data == srcA.data) - srcA = srcA.clone(); + // Case 'dst.data == srcA.data' is handled by implementation, + // because it is used frequently (filter2D, matchTemplate) if (dst.data == srcB.data) - srcB = srcB.clone(); + srcB = srcB.clone(); // workaround for B only - bool is_1d = (flags & DFT_ROWS) || (rows == 1 || (cols == 1 && - srcA.isContinuous() && srcB.isContinuous() && dst.isContinuous())); + bool is_1d = (flags & DFT_ROWS) + || (rows == 1) + || (cols == 1 && srcA.isContinuous() && srcB.isContinuous() && dst.isContinuous()); if( is_1d && !(flags & DFT_ROWS) ) cols = cols + rows - 1, rows = 1; - int ncols = cols*cn; - int j0 = cn == 1; - int j1 = ncols - (cols % 2 == 0 && cn == 1); + bool isCN1 = cn == 1; + size_t j0 = isCN1 ? 1 : 0; + size_t j1 = cols*cn - (((cols & 1) == 0 && cn == 1) ? 1 : 0); - if( depth == CV_32F ) + if (depth == CV_32F) { const float* dataA = (const float*)srcA.data; const float* dataB = (const float*)srcB.data; float* dataC = (float*)dst.data; - - size_t stepA = srcA.step/sizeof(dataA[0]); - size_t stepB = srcB.step/sizeof(dataB[0]); - size_t stepC = dst.step/sizeof(dataC[0]); - - if( !is_1d && cn == 1 ) - { - for( k = 0; k < (cols % 2 ? 1 : 2); k++ ) - { - if( k == 1 ) - dataA += cols - 1, dataB += cols - 1, dataC += cols - 1; - dataC[0] = dataA[0]*dataB[0]; - if( rows % 2 == 0 ) - dataC[(rows-1)*stepC] = dataA[(rows-1)*stepA]*dataB[(rows-1)*stepB]; - if( !conjB ) - for( j = 1; j <= rows - 2; j += 2 ) - { - double re = (double)dataA[j*stepA]*dataB[j*stepB] - - (double)dataA[(j+1)*stepA]*dataB[(j+1)*stepB]; - double im = (double)dataA[j*stepA]*dataB[(j+1)*stepB] + - (double)dataA[(j+1)*stepA]*dataB[j*stepB]; - dataC[j*stepC] = (float)re; dataC[(j+1)*stepC] = (float)im; - } - else - for( j = 1; j <= rows - 2; j += 2 ) - { - double re = (double)dataA[j*stepA]*dataB[j*stepB] + - (double)dataA[(j+1)*stepA]*dataB[(j+1)*stepB]; - double im = (double)dataA[(j+1)*stepA]*dataB[j*stepB] - - (double)dataA[j*stepA]*dataB[(j+1)*stepB]; - dataC[j*stepC] = (float)re; dataC[(j+1)*stepC] = (float)im; - } - if( k == 1 ) - dataA -= cols - 1, dataB -= cols - 1, dataC -= cols - 1; - } - } - - for( ; rows--; dataA += stepA, dataB += stepB, dataC += stepC ) - { - if( is_1d && cn == 1 ) - { - dataC[0] = dataA[0]*dataB[0]; - if( cols % 2 == 0 ) - dataC[j1] = dataA[j1]*dataB[j1]; - } - - if( !conjB ) - for( j = j0; j < j1; j += 2 ) - { - double re = (double)dataA[j]*dataB[j] - (double)dataA[j+1]*dataB[j+1]; - double im = (double)dataA[j+1]*dataB[j] + (double)dataA[j]*dataB[j+1]; - dataC[j] = (float)re; dataC[j+1] = (float)im; - } - else - for( j = j0; j < j1; j += 2 ) - { - double re = (double)dataA[j]*dataB[j] + (double)dataA[j+1]*dataB[j+1]; - double im = (double)dataA[j+1]*dataB[j] - (double)dataA[j]*dataB[j+1]; - dataC[j] = (float)re; dataC[j+1] = (float)im; - } - } + if (!conjB) + mulSpectrums_Impl(dataA, dataB, dataC, srcA.step, srcB.step, dst.step, rows, cols, j0, j1, is_1d, isCN1); + else + mulSpectrums_Impl(dataA, dataB, dataC, srcA.step, srcB.step, dst.step, rows, cols, j0, j1, is_1d, isCN1); } else { const double* dataA = (const double*)srcA.data; const double* dataB = (const double*)srcB.data; double* dataC = (double*)dst.data; - - size_t stepA = srcA.step/sizeof(dataA[0]); - size_t stepB = srcB.step/sizeof(dataB[0]); - size_t stepC = dst.step/sizeof(dataC[0]); - - if( !is_1d && cn == 1 ) - { - for( k = 0; k < (cols % 2 ? 1 : 2); k++ ) - { - if( k == 1 ) - dataA += cols - 1, dataB += cols - 1, dataC += cols - 1; - dataC[0] = dataA[0]*dataB[0]; - if( rows % 2 == 0 ) - dataC[(rows-1)*stepC] = dataA[(rows-1)*stepA]*dataB[(rows-1)*stepB]; - if( !conjB ) - for( j = 1; j <= rows - 2; j += 2 ) - { - double re = dataA[j*stepA]*dataB[j*stepB] - - dataA[(j+1)*stepA]*dataB[(j+1)*stepB]; - double im = dataA[j*stepA]*dataB[(j+1)*stepB] + - dataA[(j+1)*stepA]*dataB[j*stepB]; - dataC[j*stepC] = re; dataC[(j+1)*stepC] = im; - } - else - for( j = 1; j <= rows - 2; j += 2 ) - { - double re = dataA[j*stepA]*dataB[j*stepB] + - dataA[(j+1)*stepA]*dataB[(j+1)*stepB]; - double im = dataA[(j+1)*stepA]*dataB[j*stepB] - - dataA[j*stepA]*dataB[(j+1)*stepB]; - dataC[j*stepC] = re; dataC[(j+1)*stepC] = im; - } - if( k == 1 ) - dataA -= cols - 1, dataB -= cols - 1, dataC -= cols - 1; - } - } - - for( ; rows--; dataA += stepA, dataB += stepB, dataC += stepC ) - { - if( is_1d && cn == 1 ) - { - dataC[0] = dataA[0]*dataB[0]; - if( cols % 2 == 0 ) - dataC[j1] = dataA[j1]*dataB[j1]; - } - - if( !conjB ) - for( j = j0; j < j1; j += 2 ) - { - double re = dataA[j]*dataB[j] - dataA[j+1]*dataB[j+1]; - double im = dataA[j+1]*dataB[j] + dataA[j]*dataB[j+1]; - dataC[j] = re; dataC[j+1] = im; - } - else - for( j = j0; j < j1; j += 2 ) - { - double re = dataA[j]*dataB[j] + dataA[j+1]*dataB[j+1]; - double im = dataA[j+1]*dataB[j] - dataA[j]*dataB[j+1]; - dataC[j] = re; dataC[j+1] = im; - } - } + if (!conjB) + mulSpectrums_Impl(dataA, dataB, dataC, srcA.step, srcB.step, dst.step, rows, cols, j0, j1, is_1d, isCN1); + else + mulSpectrums_Impl(dataA, dataB, dataC, srcA.step, srcB.step, dst.step, rows, cols, j0, j1, is_1d, isCN1); } } From 781ab3d481d6b2dff61ea441a00085c8659cf6ca Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 7 Dec 2016 22:17:35 +0300 Subject: [PATCH 192/409] test: minor refactoring in test_drawing --- modules/imgproc/test/test_drawing.cpp | 34 +++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/modules/imgproc/test/test_drawing.cpp b/modules/imgproc/test/test_drawing.cpp index 1a5ff06676..37045022c5 100644 --- a/modules/imgproc/test/test_drawing.cpp +++ b/modules/imgproc/test/test_drawing.cpp @@ -42,6 +42,8 @@ #include "test_precomp.hpp" +namespace { + using namespace std; using namespace cv; @@ -60,7 +62,7 @@ protected: void CV_DrawingTest::run( int ) { Mat testImg, valImg; - const string fname = "drawing/image.png"; + const string fname = "../highgui/drawing/image.png"; string path = ts->get_data_path(), filename; filename = path + fname; @@ -69,9 +71,15 @@ void CV_DrawingTest::run( int ) valImg = imread( filename ); if( valImg.empty() ) { - imwrite( filename, testImg ); - //ts->printf( ts->LOG, "test image can not be read"); - //ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); + //imwrite( filename, testImg ); + ts->printf( ts->LOG, "test image can not be read"); +#ifdef HAVE_PNG + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); +#else + ts->printf( ts->LOG, "PNG image support is not available"); + ts->set_failed_test_info(cvtest::TS::OK); +#endif + return; } else { @@ -543,11 +551,9 @@ void CV_DrawingTest_Far::draw(Mat& img) img = img(Rect(32768, 0, 600, 400)).clone(); } -#ifdef HAVE_JPEG -TEST(Imgcodecs_Drawing, cpp_regression) { CV_DrawingTest_CPP test; test.safe_run(); } -TEST(Imgcodecs_Drawing, c_regression) { CV_DrawingTest_C test; test.safe_run(); } -TEST(Imgcodecs_Drawing, far_regression) { CV_DrawingTest_Far test; test.safe_run(); } -#endif +TEST(Drawing, cpp_regression) { CV_DrawingTest_CPP test; test.safe_run(); } +TEST(Drawing, c_regression) { CV_DrawingTest_C test; test.safe_run(); } +TEST(Drawing, far_regression) { CV_DrawingTest_Far test; test.safe_run(); } class CV_FillConvexPolyTest : public cvtest::BaseTest { @@ -581,7 +587,7 @@ protected: } }; -TEST(Imgcodecs_Drawing, fillconvexpoly_clipping) { CV_FillConvexPolyTest test; test.safe_run(); } +TEST(Drawing, fillconvexpoly_clipping) { CV_FillConvexPolyTest test; test.safe_run(); } class CV_DrawingTest_UTF8 : public cvtest::BaseTest { @@ -655,8 +661,12 @@ protected: img->copyTo(sub); shift += img->size().height + 1; } - imwrite("/tmp/all_fonts.png", result); + //imwrite("/tmp/all_fonts.png", result); } }; -TEST(Highgui_Drawing, utf8_support) { CV_DrawingTest_UTF8 test; test.safe_run(); } +TEST(Drawing, utf8_support) { CV_DrawingTest_UTF8 test; test.safe_run(); } + + + +} // namespace From bcbe2f123f2bbde60ecd3b35def3656570733d4d Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 8 Dec 2016 03:45:30 +0300 Subject: [PATCH 193/409] test: move more drawing tests --- modules/imgproc/test/test_contours.cpp | 41 ------------------------- modules/imgproc/test/test_drawing.cpp | 42 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/modules/imgproc/test/test_contours.cpp b/modules/imgproc/test/test_contours.cpp index 7b0eafbc8b..d5c4e44275 100644 --- a/modules/imgproc/test/test_contours.cpp +++ b/modules/imgproc/test/test_contours.cpp @@ -410,47 +410,6 @@ _exit_: TEST(Imgproc_FindContours, accuracy) { CV_FindContourTest test; test.safe_run(); } -TEST(Core_Drawing, _914) -{ - const int rows = 256; - const int cols = 256; - - Mat img(rows, cols, CV_8UC1, Scalar(255)); - - line(img, Point(0, 10), Point(255, 10), Scalar(0), 2, 4); - line(img, Point(-5, 20), Point(260, 20), Scalar(0), 2, 4); - line(img, Point(10, 0), Point(10, 255), Scalar(0), 2, 4); - - double x0 = 0.0/pow(2.0, -2.0); - double x1 = 255.0/pow(2.0, -2.0); - double y = 30.5/pow(2.0, -2.0); - - line(img, Point(int(x0), int(y)), Point(int(x1), int(y)), Scalar(0), 2, 4, 2); - - int pixelsDrawn = rows*cols - countNonZero(img); - ASSERT_EQ( (3*rows + cols)*3 - 3*9, pixelsDrawn); -} - -TEST(Core_Drawing, polylines_empty) -{ - Mat img(100, 100, CV_8UC1, Scalar(0)); - vector pts; // empty - polylines(img, pts, false, Scalar(255)); - int cnt = countNonZero(img); - ASSERT_EQ(cnt, 0); -} - -TEST(Core_Drawing, polylines) -{ - Mat img(100, 100, CV_8UC1, Scalar(0)); - vector pts; - pts.push_back(Point(0, 0)); - pts.push_back(Point(20, 0)); - polylines(img, pts, false, Scalar(255)); - int cnt = countNonZero(img); - ASSERT_EQ(cnt, 21); -} - //rotate/flip a quadrant appropriately static void rot(int n, int *x, int *y, int rx, int ry) { diff --git a/modules/imgproc/test/test_drawing.cpp b/modules/imgproc/test/test_drawing.cpp index 37045022c5..a5321c673f 100644 --- a/modules/imgproc/test/test_drawing.cpp +++ b/modules/imgproc/test/test_drawing.cpp @@ -668,5 +668,47 @@ protected: TEST(Drawing, utf8_support) { CV_DrawingTest_UTF8 test; test.safe_run(); } +TEST(Drawing, _914) +{ + const int rows = 256; + const int cols = 256; + + Mat img(rows, cols, CV_8UC1, Scalar(255)); + + line(img, Point(0, 10), Point(255, 10), Scalar(0), 2, 4); + line(img, Point(-5, 20), Point(260, 20), Scalar(0), 2, 4); + line(img, Point(10, 0), Point(10, 255), Scalar(0), 2, 4); + + double x0 = 0.0/pow(2.0, -2.0); + double x1 = 255.0/pow(2.0, -2.0); + double y = 30.5/pow(2.0, -2.0); + + line(img, Point(int(x0), int(y)), Point(int(x1), int(y)), Scalar(0), 2, 4, 2); + + int pixelsDrawn = rows*cols - countNonZero(img); + ASSERT_EQ( (3*rows + cols)*3 - 3*9, pixelsDrawn); +} + +TEST(Drawing, polylines_empty) +{ + Mat img(100, 100, CV_8UC1, Scalar(0)); + vector pts; // empty + polylines(img, pts, false, Scalar(255)); + int cnt = countNonZero(img); + ASSERT_EQ(cnt, 0); +} + +TEST(Drawing, polylines) +{ + Mat img(100, 100, CV_8UC1, Scalar(0)); + vector pts; + pts.push_back(Point(0, 0)); + pts.push_back(Point(20, 0)); + polylines(img, pts, false, Scalar(255)); + int cnt = countNonZero(img); + ASSERT_EQ(cnt, 21); +} + + } // namespace From 7e0f1ec00a9893152928d839d38b36a838d5dc18 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 7 Dec 2016 01:34:27 +0300 Subject: [PATCH 194/409] test: putText regression garbage test --- modules/imgproc/test/test_drawing.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/imgproc/test/test_drawing.cpp b/modules/imgproc/test/test_drawing.cpp index a5321c673f..1d6a2a8388 100644 --- a/modules/imgproc/test/test_drawing.cpp +++ b/modules/imgproc/test/test_drawing.cpp @@ -710,5 +710,20 @@ TEST(Drawing, polylines) } +TEST(Drawing, putText_no_garbage) +{ + Size sz(640, 480); + Mat mat = Mat::zeros(sz, CV_8UC1); + + mat = Scalar::all(0); + putText(mat, "029", Point(10, 350), 0, 10, Scalar(128), 15); + + EXPECT_EQ(0, cv::countNonZero(mat(Rect(0, 0, 10, sz.height)))); + EXPECT_EQ(0, cv::countNonZero(mat(Rect(sz.width-10, 0, 10, sz.height)))); + EXPECT_EQ(0, cv::countNonZero(mat(Rect(205, 0, 10, sz.height)))); + EXPECT_EQ(0, cv::countNonZero(mat(Rect(405, 0, 10, sz.height)))); +} + + } // namespace From 98181e9d7f3477adf3e4c945887c47c3be5b9354 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 7 Dec 2016 03:43:02 +0300 Subject: [PATCH 195/409] imgproc/drawing: minor refactoring in FillConvexPoly --- modules/imgproc/src/drawing.cpp | 62 ++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/modules/imgproc/src/drawing.cpp b/modules/imgproc/src/drawing.cpp index bd838c6c5d..113f499d98 100644 --- a/modules/imgproc/src/drawing.cpp +++ b/modules/imgproc/src/drawing.cpp @@ -1099,7 +1099,7 @@ FillConvexPoly( Mat& img, const Point2l* v, int npts, const void* color, int lin edge[2]; int delta = 1 << shift >> 1; - int i, y, imin = 0, left = 0, right = 1; + int i, y, imin = 0; int edges = npts; int64 xmin, xmax, ymin, ymax; uchar* ptr = img.ptr(); @@ -1181,44 +1181,46 @@ FillConvexPoly( Mat& img, const Point2l* v, int npts, const void* color, int lin { if( y >= edge[i].ye ) { - int idx = edge[i].idx, di = edge[i].di; - int64 xs = 0, xe; + int idx0 = edge[i].idx, di = edge[i].di; + int idx = idx0 + di; + if (idx >= npts) idx -= npts; int ty = 0; - for(;;) + for (; edges-- > 0; ) { ty = (int)((v[idx].y + delta) >> shift); - if( ty > y || edges == 0 ) + if (ty > y) + { + int64 xs = v[idx0].x; + int64 xe = v[idx].x; + if (shift != XY_SHIFT) + { + xs <<= XY_SHIFT - shift; + xe <<= XY_SHIFT - shift; + } + + edge[i].ye = ty; + edge[i].dx = ((xe - xs)*2 + (ty - y)) / (2 * (ty - y)); + edge[i].x = xs; + edge[i].idx = idx; break; - xs = v[idx].x; + } + idx0 = idx; idx += di; - idx -= ((idx < npts) - 1) & npts; /* idx -= idx >= npts ? npts : 0 */ - edges--; + if (idx >= npts) idx -= npts; } - - xs <<= XY_SHIFT - shift; - xe = v[idx].x << (XY_SHIFT - shift); - - /* no more edges */ - if( y >= ty) - return; - - edge[i].ye = ty; - edge[i].dx = ((xe - xs)*2 + (ty - y)) / (2 * (ty - y)); - edge[i].x = xs; - edge[i].idx = idx; } } } - if( edge[left].x > edge[right].x ) + if (y >= 0) { - left ^= 1; - right ^= 1; - } + int left = 0, right = 1; + if (edge[0].x > edge[1].x) + { + left = 1, right = 0; + } - if( y >= 0 ) - { int xx1 = (int)((edge[left].x + delta1) >> XY_SHIFT); int xx2 = (int)((edge[right].x + delta2) >> XY_SHIFT); @@ -1231,9 +1233,13 @@ FillConvexPoly( Mat& img, const Point2l* v, int npts, const void* color, int lin ICV_HLINE( ptr, xx1, xx2, color, pix_size ); } } + else + { + // TODO optimize scan for negative y + } - edge[left].x += edge[left].dx; - edge[right].x += edge[right].dx; + edge[0].x += edge[0].dx; + edge[1].x += edge[1].dx; ptr += img.step; } while( ++y <= (int)ymax ); From 1e2ddc30b10f948e733344919db247e7f665a7bc Mon Sep 17 00:00:00 2001 From: apavlenko Date: Fri, 25 Nov 2016 12:35:55 +0300 Subject: [PATCH 196/409] 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) From ccd8031a333183bafce52dc22a9510b130117200 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Fri, 2 Dec 2016 15:30:17 +0300 Subject: [PATCH 197/409] fixing compilation --- modules/imgproc/src/smooth.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/imgproc/src/smooth.cpp b/modules/imgproc/src/smooth.cpp index 552ced0144..9651d13bea 100644 --- a/modules/imgproc/src/smooth.cpp +++ b/modules/imgproc/src/smooth.cpp @@ -1703,8 +1703,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); + ivx::border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(border); if (ddepth == CV_8U && ksize.width == 3 && ksize.height == 3 && normalize) { ivx::IVX_CHECK_STATUS(vxuBox3x3(ctx, ia, ib)); @@ -1726,7 +1726,7 @@ namespace cv cnv.setScale(1 << 15); ivx::IVX_CHECK_STATUS(vxuConvolve(ctx, ia, cnv, ib)); } - ctx.setBorderMode(prevBorder); + ctx.setImmediateBorder(prevBorder); } catch (ivx::RuntimeError & e) { @@ -3405,8 +3405,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); + ivx::border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(border); #ifdef VX_VERSION_1_1 if (ksize == 3) #endif @@ -3425,7 +3425,7 @@ namespace cv ivx::IVX_CHECK_STATUS(vxQueryContext(ctx, VX_CONTEXT_NONLINEAR_MAX_DIMENSION, &supportedSize, sizeof(supportedSize))); if ((vx_size)ksize > supportedSize) { - ctx.setBorderMode(prevBorder); + ctx.setImmediateBorder(prevBorder); return false; } Mat mask(ksize, ksize, CV_8UC1, Scalar(255)); @@ -3435,7 +3435,7 @@ namespace cv ivx::IVX_CHECK_STATUS(vxuNonLinearFilter(ctx, VX_NONLINEAR_FILTER_MEDIAN, ia, mtx, ib)); } #endif - ctx.setBorderMode(prevBorder); + ctx.setImmediateBorder(prevBorder); } catch (ivx::RuntimeError & e) { From 541d5b02d9ec4f6368b1b0b3ca250ee8aac2c7e6 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Fri, 2 Dec 2016 15:59:09 +0300 Subject: [PATCH 198/409] disabling due to accuracy issues --- modules/imgproc/src/canny.cpp | 9 +++++---- modules/imgproc/src/smooth.cpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/imgproc/src/canny.cpp b/modules/imgproc/src/canny.cpp index 44ac37b1c1..ee2dc339d9 100644 --- a/modules/imgproc/src/canny.cpp +++ b/modules/imgproc/src/canny.cpp @@ -866,10 +866,11 @@ 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, + false && /* disabling due to accuracy issues */ + src.type() == CV_8UC1 && + !src.isSubmatrix() && + src.cols >= aperture_size && + src.rows >= aperture_size, openvx_canny( src, dst, diff --git a/modules/imgproc/src/smooth.cpp b/modules/imgproc/src/smooth.cpp index 9651d13bea..e7da827783 100644 --- a/modules/imgproc/src/smooth.cpp +++ b/modules/imgproc/src/smooth.cpp @@ -1714,7 +1714,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 From f3ec56fcee3c324ed61834f0795d53bb2986fc0c Mon Sep 17 00:00:00 2001 From: apavlenko Date: Mon, 5 Dec 2016 13:06:34 +0300 Subject: [PATCH 199/409] fixing build errors --- modules/imgproc/src/smooth.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/imgproc/src/smooth.cpp b/modules/imgproc/src/smooth.cpp index e7da827783..afb434cf40 100644 --- a/modules/imgproc/src/smooth.cpp +++ b/modules/imgproc/src/smooth.cpp @@ -2274,8 +2274,8 @@ static bool openvx_gaussianBlur(InputArray _src, OutputArray _dst, Size ksize, //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); + ivx::border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(border); if (ksize.width == 3 && ksize.height == 3 && (sigma1 == 0.0 || (sigma1 - 0.8) < DBL_EPSILON) && (sigma2 == 0.0 || (sigma2 - 0.8) < DBL_EPSILON)) { ivx::IVX_CHECK_STATUS(vxuGaussian3x3(ctx, ia, ib)); @@ -2285,7 +2285,7 @@ static bool openvx_gaussianBlur(InputArray _src, OutputArray _dst, Size ksize, #if VX_VERSION <= VX_VERSION_1_0 if (ctx.vendorID() == VX_ID_KHRONOS && ((vx_size)(a.cols) <= ctx.convolutionMaxDimension() || (vx_size)(a.rows) <= ctx.convolutionMaxDimension())) { - ctx.setBorderMode(prevBorder); + ctx.setImmediateBorder(prevBorder); return false; } #endif @@ -2296,7 +2296,7 @@ static bool openvx_gaussianBlur(InputArray _src, OutputArray _dst, Size ksize, cnv.setScale(1 << 15); ivx::IVX_CHECK_STATUS(vxuConvolve(ctx, ia, cnv, ib)); } - ctx.setBorderMode(prevBorder); + ctx.setImmediateBorder(prevBorder); } catch (ivx::RuntimeError & e) { From 76c38f0c809e46904a6d8c1a10ac90e75e413607 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Tue, 6 Dec 2016 17:52:56 +0300 Subject: [PATCH 200/409] trying to enable canny_vx adding a new test comparing canny_cv vs canny_vx --- modules/core/src/convert.cpp | 16 ++--- modules/imgproc/src/canny.cpp | 2 +- modules/imgproc/test/ocl/test_canny.cpp | 88 ++++++++++++++++++++++++- modules/imgproc/test/test_canny.cpp | 5 +- 4 files changed, 96 insertions(+), 15 deletions(-) diff --git a/modules/core/src/convert.cpp b/modules/core/src/convert.cpp index d9b8857a89..4e6ed70ba4 100644 --- a/modules/core/src/convert.cpp +++ b/modules/core/src/convert.cpp @@ -46,11 +46,7 @@ #include "opencl_kernels_core.hpp" #include "opencv2/core/hal/intrin.hpp" -#ifdef HAVE_OPENVX -#define IVX_USE_OPENCV -#define IVX_HIDE_INFO_WARNINGS -#include "ivx.hpp" -#endif +#include "opencv2/core/openvx/ovx_defs.hpp" #ifdef __APPLE__ #undef CV_NEON @@ -4735,12 +4731,10 @@ template static void cvt_( const T* src, size_t sstep, DT* dst, size_t dstep, Size size ) { -#ifdef HAVE_OPENVX - if(openvx_cvt(src, sstep, dst, dstep, size)) - { - return; - } -#endif + CV_OVX_RUN( + false, + openvx_cvt(src, sstep, dst, dstep, size) + ); sstep /= sizeof(src[0]); dstep /= sizeof(dst[0]); diff --git a/modules/imgproc/src/canny.cpp b/modules/imgproc/src/canny.cpp index ee2dc339d9..28141e8cbb 100644 --- a/modules/imgproc/src/canny.cpp +++ b/modules/imgproc/src/canny.cpp @@ -866,7 +866,7 @@ void Canny( InputArray _src, OutputArray _dst, Mat src = _src.getMat(), dst = _dst.getMat(); CV_OVX_RUN( - false && /* disabling due to accuracy issues */ + //false && /* disabling due to accuracy issues */ src.type() == CV_8UC1 && !src.isSubmatrix() && src.cols >= aperture_size && diff --git a/modules/imgproc/test/ocl/test_canny.cpp b/modules/imgproc/test/ocl/test_canny.cpp index 454198ac34..6798e2698c 100644 --- a/modules/imgproc/test/ocl/test_canny.cpp +++ b/modules/imgproc/test/ocl/test_canny.cpp @@ -133,6 +133,92 @@ OCL_INSTANTIATE_TEST_CASE_P(ImgProc, Canny, testing::Combine( testing::Values(L2gradient(false), L2gradient(true)), testing::Values(UseRoi(false), UseRoi(true)))); -} } // namespace cvtest::ocl + +IMPLEMENT_PARAM_CLASS(ImagePath, string) +//IMPLEMENT_PARAM_CLASS(ApertureSize, int) +//IMPLEMENT_PARAM_CLASS(L2gradient, bool) + +PARAM_TEST_CASE(CannyVX, ImagePath, ApertureSize, L2gradient) +{ + string imgPath; + int kSize; + bool useL2; + + TEST_DECLARE_INPUT_PARAMETER(src); + TEST_DECLARE_OUTPUT_PARAMETER(dst); + + virtual void SetUp() + { + imgPath = GET_PARAM(0); + kSize = GET_PARAM(1); + useL2 = GET_PARAM(2); + } + + void loadImage() + { + src = readImage(imgPath, IMREAD_GRAYSCALE); + ASSERT_FALSE(src.empty()) << "cann't load image: " << imgPath; + } +}; + +TEST_P(CannyVX, Accuracy) +{ + if(haveOpenVX()) + { + loadImage(); + + setUseOpenVX(false); + Mat canny; + cv::Canny(src, canny, 100, 150, 3); + + setUseOpenVX(true); + Mat cannyVX; + cv::Canny(src, cannyVX, 100, 150, 3); + + setUseOpenVX(false); + Mat diff, diff1; + absdiff(canny, cannyVX, diff); + boxFilter(diff, diff1, -1, Size(3,3)); + diff1 = diff1 > 255/9*3; + erode(diff1, diff1, Mat()); + double error = cv::norm(diff1, NORM_L1) / 255; + const int maxError = 10; + if(error > maxError) + { + string outPath = + string("CannyVX-diff-") + + imgPath + '-' + + 'k' + char(kSize+'0') + '-' + + (useL2 ? "l2" : "l1"); + std::replace(outPath.begin(), outPath.end(), '/', '_'); + std::replace(outPath.begin(), outPath.end(), '\\', '_'); + std::replace(outPath.begin(), outPath.end(), '.', '_'); + imwrite(outPath+".png", diff); + } + ASSERT_LE(error, maxError); + + } +} + + INSTANTIATE_TEST_CASE_P( + ImgProc, CannyVX, + testing::Combine( + testing::Values( + string("shared/baboon.png"), + string("shared/fruits.png"), + string("shared/lena.png"), + string("shared/pic1.png"), + string("shared/pic3.png"), + string("shared/pic5.png"), + string("shared/pic6.png") + ), + testing::Values(ApertureSize(3), ApertureSize(5)), + testing::Values(L2gradient(false), L2gradient(true)) + ) + ); + +} // namespace ocl + +} // namespace cvtest #endif // HAVE_OPENCL diff --git a/modules/imgproc/test/test_canny.cpp b/modules/imgproc/test/test_canny.cpp index db4a82bf9d..4031839494 100644 --- a/modules/imgproc/test/test_canny.cpp +++ b/modules/imgproc/test/test_canny.cpp @@ -302,7 +302,8 @@ int CV_CannyTest::validate_test_results( int test_case_idx ) return code; } -TEST(Imgproc_Canny, accuracy) { CV_CannyTest test; test.safe_run(); } -TEST(Imgproc_Canny, accuracy_deriv) { CV_CannyTest test(true); test.safe_run(); } +// disabling, since testing on a white noise seems having too few sense... +TEST(DISABLED_Imgproc_Canny, accuracy) { CV_CannyTest test; test.safe_run(); } +TEST(DISABLED_Imgproc_Canny, accuracy_deriv) { CV_CannyTest test(true); test.safe_run(); } /* End of file. */ From 4246d3667ff44555a9bb265a0ca643e11f082cf8 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Tue, 6 Dec 2016 18:55:09 +0300 Subject: [PATCH 201/409] disabling again --- modules/imgproc/src/canny.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/src/canny.cpp b/modules/imgproc/src/canny.cpp index 28141e8cbb..ee2dc339d9 100644 --- a/modules/imgproc/src/canny.cpp +++ b/modules/imgproc/src/canny.cpp @@ -866,7 +866,7 @@ void Canny( InputArray _src, OutputArray _dst, Mat src = _src.getMat(), dst = _dst.getMat(); CV_OVX_RUN( - //false && /* disabling due to accuracy issues */ + false && /* disabling due to accuracy issues */ src.type() == CV_8UC1 && !src.isSubmatrix() && src.cols >= aperture_size && From a99118c4c71c4552338d3410c3793eda3a836915 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Fri, 9 Dec 2016 15:07:53 +0300 Subject: [PATCH 202/409] moving CannyVX test from ocl to cpp file --- modules/imgproc/test/ocl/test_canny.cpp | 84 ------------------- modules/imgproc/test/test_canny.cpp | 103 ++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 84 deletions(-) diff --git a/modules/imgproc/test/ocl/test_canny.cpp b/modules/imgproc/test/ocl/test_canny.cpp index 6798e2698c..c5e0485ba7 100644 --- a/modules/imgproc/test/ocl/test_canny.cpp +++ b/modules/imgproc/test/ocl/test_canny.cpp @@ -133,90 +133,6 @@ OCL_INSTANTIATE_TEST_CASE_P(ImgProc, Canny, testing::Combine( testing::Values(L2gradient(false), L2gradient(true)), testing::Values(UseRoi(false), UseRoi(true)))); - -IMPLEMENT_PARAM_CLASS(ImagePath, string) -//IMPLEMENT_PARAM_CLASS(ApertureSize, int) -//IMPLEMENT_PARAM_CLASS(L2gradient, bool) - -PARAM_TEST_CASE(CannyVX, ImagePath, ApertureSize, L2gradient) -{ - string imgPath; - int kSize; - bool useL2; - - TEST_DECLARE_INPUT_PARAMETER(src); - TEST_DECLARE_OUTPUT_PARAMETER(dst); - - virtual void SetUp() - { - imgPath = GET_PARAM(0); - kSize = GET_PARAM(1); - useL2 = GET_PARAM(2); - } - - void loadImage() - { - src = readImage(imgPath, IMREAD_GRAYSCALE); - ASSERT_FALSE(src.empty()) << "cann't load image: " << imgPath; - } -}; - -TEST_P(CannyVX, Accuracy) -{ - if(haveOpenVX()) - { - loadImage(); - - setUseOpenVX(false); - Mat canny; - cv::Canny(src, canny, 100, 150, 3); - - setUseOpenVX(true); - Mat cannyVX; - cv::Canny(src, cannyVX, 100, 150, 3); - - setUseOpenVX(false); - Mat diff, diff1; - absdiff(canny, cannyVX, diff); - boxFilter(diff, diff1, -1, Size(3,3)); - diff1 = diff1 > 255/9*3; - erode(diff1, diff1, Mat()); - double error = cv::norm(diff1, NORM_L1) / 255; - const int maxError = 10; - if(error > maxError) - { - string outPath = - string("CannyVX-diff-") + - imgPath + '-' + - 'k' + char(kSize+'0') + '-' + - (useL2 ? "l2" : "l1"); - std::replace(outPath.begin(), outPath.end(), '/', '_'); - std::replace(outPath.begin(), outPath.end(), '\\', '_'); - std::replace(outPath.begin(), outPath.end(), '.', '_'); - imwrite(outPath+".png", diff); - } - ASSERT_LE(error, maxError); - - } -} - - INSTANTIATE_TEST_CASE_P( - ImgProc, CannyVX, - testing::Combine( - testing::Values( - string("shared/baboon.png"), - string("shared/fruits.png"), - string("shared/lena.png"), - string("shared/pic1.png"), - string("shared/pic3.png"), - string("shared/pic5.png"), - string("shared/pic6.png") - ), - testing::Values(ApertureSize(3), ApertureSize(5)), - testing::Values(L2gradient(false), L2gradient(true)) - ) - ); - } // namespace ocl } // namespace cvtest diff --git a/modules/imgproc/test/test_canny.cpp b/modules/imgproc/test/test_canny.cpp index 4031839494..ea7f5637f8 100644 --- a/modules/imgproc/test/test_canny.cpp +++ b/modules/imgproc/test/test_canny.cpp @@ -306,4 +306,107 @@ int CV_CannyTest::validate_test_results( int test_case_idx ) TEST(DISABLED_Imgproc_Canny, accuracy) { CV_CannyTest test; test.safe_run(); } TEST(DISABLED_Imgproc_Canny, accuracy_deriv) { CV_CannyTest test(true); test.safe_run(); } + +/* + * Comparing OpenVX based implementation with the main one +*/ + +#ifndef IMPLEMENT_PARAM_CLASS +#define IMPLEMENT_PARAM_CLASS(name, type) \ + class name \ + { \ + public: \ + name ( type arg = type ()) : val_(arg) {} \ + operator type () const {return val_;} \ + private: \ + type val_; \ + }; \ + inline void PrintTo( name param, std::ostream* os) \ + { \ + *os << #name << "(" << testing::PrintToString(static_cast< type >(param)) << ")"; \ + } +#endif // IMPLEMENT_PARAM_CLASS + +IMPLEMENT_PARAM_CLASS(ImagePath, string) +IMPLEMENT_PARAM_CLASS(ApertureSize, int) +IMPLEMENT_PARAM_CLASS(L2gradient, bool) + +PARAM_TEST_CASE(CannyVX, ImagePath, ApertureSize, L2gradient) +{ + string imgPath; + int kSize; + bool useL2; + Mat src, dst; + + virtual void SetUp() + { + imgPath = GET_PARAM(0); + kSize = GET_PARAM(1); + useL2 = GET_PARAM(2); + } + + void loadImage() + { + src = cv::imread(cvtest::TS::ptr()->get_data_path() + imgPath, IMREAD_GRAYSCALE); + ASSERT_FALSE(src.empty()) << "cann't load image: " << imgPath; + } +}; + +TEST_P(CannyVX, Accuracy) +{ + if(haveOpenVX()) + { + loadImage(); + + setUseOpenVX(false); + Mat canny; + cv::Canny(src, canny, 100, 150, 3); + + setUseOpenVX(true); + Mat cannyVX; + cv::Canny(src, cannyVX, 100, 150, 3); + + setUseOpenVX(false); + Mat diff, diff1; + absdiff(canny, cannyVX, diff); + boxFilter(diff, diff1, -1, Size(3,3)); + const int minPixelsAroud = 3; // empirical number + diff1 = diff1 > 255/9 * minPixelsAroud; + erode(diff1, diff1, Mat()); + double error = cv::norm(diff1, NORM_L1) / 255; + const int maxError = 10; // empirical number + if(error > maxError) + { + string outPath = + string("CannyVX-diff-") + + imgPath + '-' + + 'k' + char(kSize+'0') + '-' + + (useL2 ? "l2" : "l1"); + std::replace(outPath.begin(), outPath.end(), '/', '_'); + std::replace(outPath.begin(), outPath.end(), '\\', '_'); + std::replace(outPath.begin(), outPath.end(), '.', '_'); + imwrite(outPath+".png", diff); + } + ASSERT_LE(error, maxError); + + } +} + + INSTANTIATE_TEST_CASE_P( + ImgProc, CannyVX, + testing::Combine( + testing::Values( + string("shared/baboon.png"), + string("shared/fruits.png"), + string("shared/lena.png"), + string("shared/pic1.png"), + string("shared/pic3.png"), + string("shared/pic5.png"), + string("shared/pic6.png") + ), + testing::Values(ApertureSize(3), ApertureSize(5)), + testing::Values(L2gradient(false), L2gradient(true)) + ) + ); + /* End of file. */ From a93c70f533e3f0f0e528d79e6d21f029b5f00edc Mon Sep 17 00:00:00 2001 From: catree Date: Wed, 7 Dec 2016 17:36:01 +0100 Subject: [PATCH 203/409] Add note about solvePnP methods that cannot be used. Add note: SOLVEPNP_EPNP will be used instead of SOLVEPNP_DLS and instead of SOLVEPNP_UPNP. --- modules/calib3d/include/opencv2/calib3d.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/calib3d/include/opencv2/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d.hpp index 023fe103e0..362e81f8bf 100644 --- a/modules/calib3d/include/opencv2/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d.hpp @@ -574,6 +574,9 @@ projections, as well as the camera matrix and the distortion coefficients. - Thus, given some data D = np.array(...) where D.shape = (N,M), in order to use a subset of it as, e.g., imagePoints, one must effectively copy it into a new array: imagePoints = np.ascontiguousarray(D[:,:2]).reshape((N,1,2)) + - The methods **SOLVEPNP_DLS** and **SOLVEPNP_UPNP** cannot be used as the current implementations are + unstable and sometimes give completly wrong results. If you pass one of these two flags, + **SOLVEPNP_EPNP** method will be used instead. */ CV_EXPORTS_W bool solvePnP( InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, From c447e5f61574337641c1238f3af1b27871222ee7 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Fri, 9 Dec 2016 17:48:06 +0300 Subject: [PATCH 204/409] Added vxTruncateArray wrapper to ivx::Array --- 3rdparty/openvx/include/ivx.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index 57eeda34a2..2d1bba75b3 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -2008,6 +2008,14 @@ public: IVX_CHECK_STATUS(vxAddArrayItems(ref, count, ptr, stride)); } + void truncateArray(vx_size new_count) + { + if(new_count <= itemCount()) + IVX_CHECK_STATUS(vxTruncateArray(ref, new_count)); + else + throw WrapperError(std::string(__func__) + "(): array is too small"); + } + void copyRangeTo(size_t start, size_t end, void* data) { if (!data) throw WrapperError(std::string(__func__) + "(): output pointer is 0"); From c98330b2d1d9eff6086ab29c4876bc183dba48df Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 9 Dec 2016 20:03:05 +0300 Subject: [PATCH 205/409] cmake: special target to build/install docs only --- doc/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 6825b5199f..f956cde4e8 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -171,4 +171,10 @@ if(BUILD_DOCS AND DOXYGEN_FOUND) DESTINATION "${OPENCV_DOC_INSTALL_PATH}" COMPONENT "docs" OPTIONAL ) + + # Alias to build/install docs only + add_custom_target(install_docs + DEPENDS doxygen + COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=docs -P "${CMAKE_BINARY_DIR}/cmake_install.cmake" + ) endif() From f55e96621039630f6be8fc9b29f798d73bcaf354 Mon Sep 17 00:00:00 2001 From: parismita Date: Wed, 7 Dec 2016 02:12:52 +0530 Subject: [PATCH 206/409] Update linux_install.markdown Update linux_install.markdown Update linux_install.markdown Update linux_install.markdown Update linux_install.markdown Update linux_install.markdown Update linux_install.markdown --- .../introduction/linux_install/linux_install.markdown | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/tutorials/introduction/linux_install/linux_install.markdown b/doc/tutorials/introduction/linux_install/linux_install.markdown index e442e70cec..dfbdd4c05b 100644 --- a/doc/tutorials/introduction/linux_install/linux_install.markdown +++ b/doc/tutorials/introduction/linux_install/linux_install.markdown @@ -74,6 +74,9 @@ Building OpenCV from Source Using CMake - run: “Configure” - run: “Generate” + @note + Use `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ..` , without spaces after -D if the above example doesn't work. + -# Description of some parameters - build type: `CMAKE_BUILD_TYPE=Release\Debug` - to build with modules from opencv_contrib set OPENCV_EXTRA_MODULES_PATH to \ Date: Thu, 8 Dec 2016 18:02:57 +0300 Subject: [PATCH 207/409] cmake: disable warning C4127 for MSVS < 2015 --- cmake/OpenCVCompilerOptions.cmake | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index 57d142eb0b..06992c2de0 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -341,8 +341,13 @@ if(MSVC) string(REPLACE "/W3" "/W4" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") string(REPLACE "/W3" "/W4" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - if(NOT ENABLE_NOISY_WARNINGS AND MSVC_VERSION EQUAL 1400) - ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4510 /wd4610 /wd4312 /wd4201 /wd4244 /wd4328 /wd4267) + if(NOT ENABLE_NOISY_WARNINGS) + if(MSVC_VERSION EQUAL 1400) + ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4510 /wd4610 /wd4312 /wd4201 /wd4244 /wd4328 /wd4267) + endif() + if(MSVC_VERSION LESS 1900) # MSVS2015 + ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4127) # warning C4127: conditional expression is constant + endif() endif() # allow extern "C" functions throw exceptions From 7bceef715c9d14353b134a42e236bd2782d9e511 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sat, 10 Dec 2016 15:53:57 +0300 Subject: [PATCH 208/409] test: fix mulSpectrums error tolerance --- modules/core/test/test_dxt.cpp | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/modules/core/test/test_dxt.cpp b/modules/core/test/test_dxt.cpp index 1b2995a541..dad12ce1d0 100644 --- a/modules/core/test/test_dxt.cpp +++ b/modules/core/test/test_dxt.cpp @@ -787,9 +787,7 @@ public: protected: void run_func(); void prepare_to_validation( int test_case_idx ); -#if defined(__aarch64__) && defined(NDEBUG) double get_success_error_level( int test_case_idx, int i, int j ); -#endif }; @@ -797,31 +795,19 @@ CxCore_MulSpectrumsTest::CxCore_MulSpectrumsTest() : CxCore_DXTBaseTest( true, t { } -#if defined(__aarch64__) && defined(NDEBUG) double CxCore_MulSpectrumsTest::get_success_error_level( int test_case_idx, int i, int j ) { + (void)test_case_idx; + CV_Assert(i == OUTPUT); + CV_Assert(j == 0); int elem_depth = CV_MAT_DEPTH(cvGetElemType(test_array[i][j])); - if( elem_depth <= CV_32F ) - { - return ArrayTest::get_success_error_level( test_case_idx, i, j ); - } - switch( test_case_idx ) - { - // Usual threshold is too strict for these test cases due to the difference of fmsub and fsub - case 399: - case 420: - return DBL_EPSILON * 20000; - case 65: - case 161: - case 287: - case 351: - case 458: - return DBL_EPSILON * 10000; - default: - return ArrayTest::get_success_error_level( test_case_idx, i, j ); - } + CV_Assert(elem_depth == CV_32F || elem_depth == CV_64F); + + element_wise_relative_error = false; + double maxInputValue = 1000; // ArrayTest::get_minmax_bounds + double err = 8 * maxInputValue; // result = A*B + C*D + return (elem_depth == CV_32F ? FLT_EPSILON : DBL_EPSILON) * err; } -#endif void CxCore_MulSpectrumsTest::run_func() { From 4a3da1c4ed67af8c2037cac548e2b351be080de6 Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Mon, 12 Dec 2016 12:25:57 +0300 Subject: [PATCH 209/409] Fix missing angles in AKAZE keypoints --- modules/features2d/src/akaze.cpp | 2 ++ modules/features2d/src/kaze/AKAZEFeatures.cpp | 11 ++++++++++ modules/features2d/src/kaze/AKAZEFeatures.h | 1 + .../test/test_detectors_regression.cpp | 20 +++++++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/modules/features2d/src/akaze.cpp b/modules/features2d/src/akaze.cpp index d598155275..2297194b2c 100644 --- a/modules/features2d/src/akaze.cpp +++ b/modules/features2d/src/akaze.cpp @@ -200,6 +200,8 @@ namespace cv if (!useProvidedKeypoints) { impl.Feature_Detection(keypoints); + if( !descriptors.needed() ) + impl.Compute_Keypoints_Orientation(keypoints); } if (!mask.empty()) diff --git a/modules/features2d/src/kaze/AKAZEFeatures.cpp b/modules/features2d/src/kaze/AKAZEFeatures.cpp index 9c30ca928a..77a68a554c 100644 --- a/modules/features2d/src/kaze/AKAZEFeatures.cpp +++ b/modules/features2d/src/kaze/AKAZEFeatures.cpp @@ -844,6 +844,17 @@ void AKAZEFeatures::Compute_Main_Orientation(KeyPoint& kpt, const std::vector& kpts) const +{ + for(size_t i = 0; i < kpts.size(); i++) + Compute_Main_Orientation(kpts[i], evolution_); +} + /* ************************************************************************* */ /** * @brief This method computes the upright descriptor (not rotation invariant) of diff --git a/modules/features2d/src/kaze/AKAZEFeatures.h b/modules/features2d/src/kaze/AKAZEFeatures.h index 14800bc372..16a858fd6d 100644 --- a/modules/features2d/src/kaze/AKAZEFeatures.h +++ b/modules/features2d/src/kaze/AKAZEFeatures.h @@ -54,6 +54,7 @@ public: /// Feature description methods void Compute_Descriptors(std::vector& kpts, cv::Mat& desc); static void Compute_Main_Orientation(cv::KeyPoint& kpt, const std::vector& evolution_); + void Compute_Keypoints_Orientation(std::vector& kpts) const; }; /* ************************************************************************* */ diff --git a/modules/features2d/test/test_detectors_regression.cpp b/modules/features2d/test/test_detectors_regression.cpp index a235065172..9b6d489535 100644 --- a/modules/features2d/test/test_detectors_regression.cpp +++ b/modules/features2d/test/test_detectors_regression.cpp @@ -301,3 +301,23 @@ TEST( Features2d_Detector_AKAZE, regression ) CV_FeatureDetectorTest test( "detector-akaze", AKAZE::create() ); test.safe_run(); } + +TEST( Features2d_Detector_AKAZE, detect_and_compute_split ) +{ + Mat testImg(100, 100, CV_8U); + RNG rng(101); + rng.fill(testImg, RNG::UNIFORM, Scalar(0), Scalar(255), true); + + Ptr ext = AKAZE::create(AKAZE::DESCRIPTOR_MLDB, 0, 3, 0.001f, 1, 1, KAZE::DIFF_PM_G2); + vector detAndCompKps; + Mat desc; + ext->detectAndCompute(testImg, noArray(), detAndCompKps, desc); + + vector detKps; + ext->detect(testImg, detKps); + + ASSERT_EQ(detKps.size(), detAndCompKps.size()); + + for(size_t i = 0; i < detKps.size(); i++) + ASSERT_EQ(detKps[i].hash(), detAndCompKps[i].hash()); +} From 120531cb464e8568174aecb1e71e8732f30797af Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Mon, 12 Dec 2016 13:22:44 +0300 Subject: [PATCH 210/409] Fix error threshhold in SolvePnPRansac --- modules/calib3d/src/solvepnp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/calib3d/src/solvepnp.cpp b/modules/calib3d/src/solvepnp.cpp index 84a3256739..e205580921 100644 --- a/modules/calib3d/src/solvepnp.cpp +++ b/modules/calib3d/src/solvepnp.cpp @@ -197,7 +197,7 @@ public: float* err = _err.getMat().ptr(); for ( i = 0; i < count; ++i) - err[i] = (float)norm( ipoints_ptr[i] - projpoints_ptr[i] ); + err[i] = (float)norm( Matx21f(ipoints_ptr[i] - projpoints_ptr[i]), NORM_L2SQR ); } From 3f7cde0485fd23b8882e560ed53d6bd401e84cc3 Mon Sep 17 00:00:00 2001 From: Naba Kumar Date: Mon, 12 Dec 2016 12:15:31 +0200 Subject: [PATCH 211/409] Implement 32f support for morphology operation --- modules/cudafilters/src/filtering.cpp | 57 +++++++++++++++++++++------ 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/modules/cudafilters/src/filtering.cpp b/modules/cudafilters/src/filtering.cpp index fcf54523e1..21efde0103 100644 --- a/modules/cudafilters/src/filtering.cpp +++ b/modules/cudafilters/src/filtering.cpp @@ -525,14 +525,17 @@ namespace void apply(InputArray src, OutputArray dst, Stream& stream = Stream::Null()); private: - typedef NppStatus (*nppMorfFilter_t)(const Npp8u* pSrc, Npp32s nSrcStep, Npp8u* pDst, Npp32s nDstStep, NppiSize oSizeROI, - const Npp8u* pMask, NppiSize oMaskSize, NppiPoint oAnchor); + typedef NppStatus (*nppMorfFilter8u_t)(const Npp8u* pSrc, Npp32s nSrcStep, Npp8u* pDst, Npp32s nDstStep, NppiSize oSizeROI, + const Npp8u* pMask, NppiSize oMaskSize, NppiPoint oAnchor); + typedef NppStatus (*nppMorfFilter32f_t)(const Npp32f* pSrc, Npp32s nSrcStep, Npp32f* pDst, Npp32s nDstStep, NppiSize oSizeROI, + const Npp8u* pMask, NppiSize oMaskSize, NppiPoint oAnchor); int type_; GpuMat kernel_; Point anchor_; int iters_; - nppMorfFilter_t func_; + nppMorfFilter8u_t func8u_; + nppMorfFilter32f_t func32f_; GpuMat srcBorder_; GpuMat buf_; @@ -541,14 +544,19 @@ namespace MorphologyFilter::MorphologyFilter(int op, int srcType, InputArray _kernel, Point anchor, int iterations) : type_(srcType), anchor_(anchor), iters_(iterations) { - static const nppMorfFilter_t funcs[2][5] = + static const nppMorfFilter8u_t funcs8u[2][5] = { {0, nppiErode_8u_C1R, 0, 0, nppiErode_8u_C4R }, {0, nppiDilate_8u_C1R, 0, 0, nppiDilate_8u_C4R } }; + static const nppMorfFilter32f_t funcs32f[2][5] = + { + {0, nppiErode_32f_C1R, 0, 0, nppiErode_32f_C4R }, + {0, nppiDilate_32f_C1R, 0, 0, nppiDilate_32f_C4R } + }; CV_Assert( op == MORPH_ERODE || op == MORPH_DILATE ); - CV_Assert( srcType == CV_8UC1 || srcType == CV_8UC4 ); + CV_Assert( srcType == CV_8UC1 || srcType == CV_8UC4 || srcType == CV_32FC1 || srcType == CV_32FC4 ); Mat kernel = _kernel.getMat(); Size ksize = !kernel.empty() ? _kernel.size() : Size(3, 3); @@ -579,7 +587,14 @@ namespace kernel_ = cuda::createContinuous(kernel.size(), CV_8UC1); kernel_.upload(kernel8U); - func_ = funcs[op][CV_MAT_CN(srcType)]; + if(srcType == CV_8UC1 || srcType == CV_8UC4) + { + func8u_ = funcs8u[op][CV_MAT_CN(srcType)]; + } + else if(srcType == CV_32FC1 || srcType == CV_32FC4) + { + func32f_ = funcs32f[op][CV_MAT_CN(srcType)]; + } } void MorphologyFilter::apply(InputArray _src, OutputArray _dst, Stream& _stream) @@ -618,15 +633,31 @@ namespace oAnchor.x = anchor_.x; oAnchor.y = anchor_.y; - nppSafeCall( func_(srcRoi.ptr(), static_cast(srcRoi.step), dst.ptr(), static_cast(dst.step), - oSizeROI, kernel_.ptr(), oMaskSize, oAnchor) ); - - for(int i = 1; i < iters_; ++i) + if (type_ == CV_8UC1 || type_ == CV_8UC4) { - dst.copyTo(bufRoi, _stream); + nppSafeCall( func8u_(srcRoi.ptr(), static_cast(srcRoi.step), dst.ptr(), static_cast(dst.step), + oSizeROI, kernel_.ptr(), oMaskSize, oAnchor) ); - nppSafeCall( func_(bufRoi.ptr(), static_cast(bufRoi.step), dst.ptr(), static_cast(dst.step), - oSizeROI, kernel_.ptr(), oMaskSize, oAnchor) ); + for(int i = 1; i < iters_; ++i) + { + dst.copyTo(bufRoi, _stream); + + nppSafeCall( func8u_(bufRoi.ptr(), static_cast(bufRoi.step), dst.ptr(), static_cast(dst.step), + oSizeROI, kernel_.ptr(), oMaskSize, oAnchor) ); + } + } + else if (type_ == CV_32FC1 || type_ == CV_32FC4) + { + nppSafeCall( func32f_(srcRoi.ptr(), static_cast(srcRoi.step), dst.ptr(), static_cast(dst.step), + oSizeROI, kernel_.ptr(), oMaskSize, oAnchor) ); + + for(int i = 1; i < iters_; ++i) + { + dst.copyTo(bufRoi, _stream); + + nppSafeCall( func32f_(bufRoi.ptr(), static_cast(bufRoi.step), dst.ptr(), static_cast(dst.step), + oSizeROI, kernel_.ptr(), oMaskSize, oAnchor) ); + } } if (stream == 0) From a5d857eda8cd689e675ad590647d4553cf59c4f6 Mon Sep 17 00:00:00 2001 From: Naba Kumar Date: Mon, 12 Dec 2016 12:38:43 +0200 Subject: [PATCH 212/409] Update docs for 32f support in morphology operation --- modules/cudafilters/include/opencv2/cudafilters.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cudafilters/include/opencv2/cudafilters.hpp b/modules/cudafilters/include/opencv2/cudafilters.hpp index c732a9d990..1e25e5602d 100644 --- a/modules/cudafilters/include/opencv2/cudafilters.hpp +++ b/modules/cudafilters/include/opencv2/cudafilters.hpp @@ -250,7 +250,7 @@ CV_EXPORTS Ptr createGaussianFilter(int srcType, int dstType, Size ksize - **MORPH_GRADIENT** morphological gradient - **MORPH_TOPHAT** "top hat" - **MORPH_BLACKHAT** "black hat" -@param srcType Input/output image type. Only CV_8UC1 and CV_8UC4 are supported. +@param srcType Input/output image type. Only CV_8UC1, CV_8UC4, CV_32FC1 and CV_32FC4 are supported. @param kernel 2D 8-bit structuring element for the morphological operation. @param anchor Anchor position within the structuring element. Negative values mean that the anchor is at the center. From 21683a550a4e69baff7ea4c88be15c04de65d30e Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Mon, 12 Dec 2016 14:12:47 +0300 Subject: [PATCH 213/409] Update MSER python sample --- samples/python/mser.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/samples/python/mser.py b/samples/python/mser.py index 9d7a65c10f..1f0cb046a7 100755 --- a/samples/python/mser.py +++ b/samples/python/mser.py @@ -17,9 +17,9 @@ Keys: import numpy as np import cv2 import video +import sys if __name__ == '__main__': - import sys try: video_src = sys.argv[1] except: @@ -27,12 +27,15 @@ if __name__ == '__main__': cam = video.create_capture(video_src) mser = cv2.MSER_create() + while True: ret, img = cam.read() + if ret == 0: + break gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) vis = img.copy() - regions = mser.detectRegions(gray, None) + regions, _ = mser.detectRegions(gray) hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions] cv2.polylines(vis, hulls, 1, (0, 255, 0)) From 03bcfe4107eacaf38bd7bf708eda174602e4a985 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 12 Dec 2016 14:22:52 +0300 Subject: [PATCH 214/409] test: fix RNG problem in accuracy tests --- modules/ts/include/opencv2/ts.hpp | 4 +++- modules/ts/include/opencv2/ts/ts_ext.hpp | 4 +++- modules/ts/src/ts.cpp | 5 +++++ modules/ts/src/ts_perf.cpp | 1 - 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/ts/include/opencv2/ts.hpp b/modules/ts/include/opencv2/ts.hpp index 28a071a565..c573905c6f 100644 --- a/modules/ts/include/opencv2/ts.hpp +++ b/modules/ts/include/opencv2/ts.hpp @@ -539,11 +539,13 @@ protected: } }; +extern uint64 param_seed; + struct CV_EXPORTS DefaultRngAuto { const uint64 old_state; - DefaultRngAuto() : old_state(cv::theRNG().state) { cv::theRNG().state = (uint64)-1; } + DefaultRngAuto() : old_state(cv::theRNG().state) { cv::theRNG().state = cvtest::param_seed; } ~DefaultRngAuto() { cv::theRNG().state = old_state; } DefaultRngAuto& operator=(const DefaultRngAuto&); diff --git a/modules/ts/include/opencv2/ts/ts_ext.hpp b/modules/ts/include/opencv2/ts/ts_ext.hpp index 781b61e4dd..05ccc63cc1 100644 --- a/modules/ts/include/opencv2/ts/ts_ext.hpp +++ b/modules/ts/include/opencv2/ts/ts_ext.hpp @@ -12,7 +12,9 @@ namespace cvtest { void checkIppStatus(); } -#define CV_TEST_INIT cv::ipp::setIppStatus(0); +#define CV_TEST_INIT \ + cv::ipp::setIppStatus(0); \ + cv::theRNG().state = cvtest::param_seed; #define CV_TEST_CLEANUP ::cvtest::checkIppStatus(); #define CV_TEST_BODY_IMPL \ { \ diff --git a/modules/ts/src/ts.cpp b/modules/ts/src/ts.cpp index c60e5d635a..b2763d4469 100644 --- a/modules/ts/src/ts.cpp +++ b/modules/ts/src/ts.cpp @@ -80,6 +80,8 @@ namespace cvtest { +uint64 param_seed = 0x12345678; // real value is passed via parseCustomOptions function + static std::string path_join(const std::string& prefix, const std::string& subpath) { CV_Assert(subpath.empty() || subpath[0] != '/'); @@ -695,6 +697,7 @@ void parseCustomOptions(int argc, char **argv) { const char * const command_line_keys = "{ ipp test_ipp_check |false |check whether IPP works without failures }" + "{ test_seed |809564 |seed for random numbers generator }" "{ h help |false |print help info }"; cv::CommandLineParser parser(argc, argv, command_line_keys); @@ -711,6 +714,8 @@ void parseCustomOptions(int argc, char **argv) #else test_ipp_check = false; #endif + + param_seed = parser.get("test_seed"); } diff --git a/modules/ts/src/ts_perf.cpp b/modules/ts/src/ts_perf.cpp index f4967748ee..84ce5d6a64 100644 --- a/modules/ts/src/ts_perf.cpp +++ b/modules/ts/src/ts_perf.cpp @@ -38,7 +38,6 @@ static double param_max_outliers; static double param_max_deviation; static unsigned int param_min_samples; static unsigned int param_force_samples; -static uint64 param_seed; static double param_time_limit; static int param_threads; static bool param_write_sanity; From fb223784a6199b44ffca5019c080dab3521c3d92 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 12 Dec 2016 14:39:08 +0300 Subject: [PATCH 215/409] test: fix Core_HAL.mat_decomp test implementation --- modules/core/test/test_hal_core.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/modules/core/test/test_hal_core.cpp b/modules/core/test/test_hal_core.cpp index dfd0867def..ab85659ec1 100644 --- a/modules/core/test/test_hal_core.cpp +++ b/modules/core/test/test_hal_core.cpp @@ -120,15 +120,20 @@ TEST(Core_HAL, mathfuncs) } } +namespace { + enum { HAL_LU = 0, HAL_CHOL = 1 }; -TEST(Core_HAL, mat_decomp) +typedef testing::TestWithParam HAL; + +TEST_P(HAL, mat_decomp) { - for( int hcase = 0; hcase < 16; hcase++ ) + int hcase = GetParam(); + SCOPED_TRACE(cv::format("hcase=%d", hcase)); { int depth = hcase % 2 == 0 ? CV_32F : CV_64F; int size = (hcase / 2) % 4; @@ -137,7 +142,7 @@ TEST(Core_HAL, mat_decomp) double eps = depth == CV_32F ? 1e-5 : 1e-10; if( size == 3 ) - continue; + return; // TODO ??? Mat a0(size, size, depth), a(size, size, depth), b(size, 1, depth), x(size, 1, depth), x0(size, 1, depth); randu(a0, -1, 1); @@ -175,14 +180,19 @@ TEST(Core_HAL, mat_decomp) min_hal_t = std::min(min_hal_t, t); t = (double)getTickCount(); - solve(a0, b, x0, (nfunc == HAL_LU ? DECOMP_LU : DECOMP_CHOLESKY)); + bool solveStatus = solve(a0, b, x0, (nfunc == HAL_LU ? DECOMP_LU : DECOMP_CHOLESKY)); t = (double)getTickCount() - t; + EXPECT_TRUE(solveStatus); min_ocv_t = std::min(min_ocv_t, t); } //std::cout << "x: " << Mat(x.t()) << std::endl; //std::cout << "x0: " << Mat(x0.t()) << std::endl; - EXPECT_LE(norm(x, x0, NORM_INF | NORM_RELATIVE), eps); + EXPECT_LE(norm(x, x0, NORM_INF | NORM_RELATIVE), eps) + << "x: " << Mat(x.t()) + << "\nx0: " << Mat(x0.t()) + << "\na0: " << a0 + << "\nb: " << b; double freq = getTickFrequency(); printf("%s (%d x %d, %s): hal time=%.2fusec, ocv time=%.2fusec\n", @@ -192,3 +202,7 @@ TEST(Core_HAL, mat_decomp) min_hal_t*1e6/freq, min_ocv_t*1e6/freq); } } + +INSTANTIATE_TEST_CASE_P(Core, HAL, testing::Range(0, 16)); + +} // namespace From 3aedc134c205eb01109498270cdc738d772ec0a5 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Fri, 9 Dec 2016 15:49:08 +0300 Subject: [PATCH 216/409] replacing white noise with fruits picture --- modules/imgproc/test/test_canny.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/modules/imgproc/test/test_canny.cpp b/modules/imgproc/test/test_canny.cpp index ea7f5637f8..cee069a3a8 100644 --- a/modules/imgproc/test/test_canny.cpp +++ b/modules/imgproc/test/test_canny.cpp @@ -62,6 +62,8 @@ protected: double threshold1, threshold2; bool test_cpp; bool test_custom_deriv; + + Mat img; }; @@ -77,6 +79,9 @@ CV_CannyTest::CV_CannyTest(bool custom_deriv) test_cpp = false; test_custom_deriv = custom_deriv; + + const char imgPath[] = "shared/fruits.png"; + img = cv::imread(cvtest::TS::ptr()->get_data_path() + imgPath, IMREAD_GRAYSCALE); } @@ -112,8 +117,21 @@ int CV_CannyTest::prepare_test_case( int test_case_idx ) int code = cvtest::ArrayTest::prepare_test_case( test_case_idx ); if( code > 0 ) { + RNG& rng = ts->get_rng(); Mat& src = test_mat[INPUT][0]; - GaussianBlur(src, src, Size(11, 11), 5, 5); + //GaussianBlur(src, src, Size(11, 11), 5, 5); + if(src.cols > img.cols || src.rows > img.rows) + resize(img, src, src.size()); + else + img( + Rect( + cvtest::randInt(rng) % (img.cols-src.cols), + cvtest::randInt(rng) % (img.rows-src.rows), + src.cols, + src.rows + ) + ).copyTo(src); + GaussianBlur(src, src, Size(5, 5), 0); } return code; @@ -302,9 +320,8 @@ int CV_CannyTest::validate_test_results( int test_case_idx ) return code; } -// disabling, since testing on a white noise seems having too few sense... -TEST(DISABLED_Imgproc_Canny, accuracy) { CV_CannyTest test; test.safe_run(); } -TEST(DISABLED_Imgproc_Canny, accuracy_deriv) { CV_CannyTest test(true); test.safe_run(); } +TEST(Imgproc_Canny, accuracy) { CV_CannyTest test; test.safe_run(); } +TEST(Imgproc_Canny, accuracy_deriv) { CV_CannyTest test(true); test.safe_run(); } /* @@ -366,7 +383,7 @@ TEST_P(CannyVX, Accuracy) Mat cannyVX; cv::Canny(src, cannyVX, 100, 150, 3); - setUseOpenVX(false); + // 'smart' diff check (excluding isolated pixels) Mat diff, diff1; absdiff(canny, cannyVX, diff); boxFilter(diff, diff1, -1, Size(3,3)); @@ -374,7 +391,7 @@ TEST_P(CannyVX, Accuracy) diff1 = diff1 > 255/9 * minPixelsAroud; erode(diff1, diff1, Mat()); double error = cv::norm(diff1, NORM_L1) / 255; - const int maxError = 10; // empirical number + const int maxError = std::min(10, diff.size().area()/100); // empirical number if(error > maxError) { string outPath = From ec859ceb2c95689b569d6f184a66db4a1163ded0 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 12 Dec 2016 18:09:02 +0300 Subject: [PATCH 217/409] test: limit chessboard generator parameters --- modules/calib3d/test/test_chessboardgenerator.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/calib3d/test/test_chessboardgenerator.cpp b/modules/calib3d/test/test_chessboardgenerator.cpp index 7c0bd34dc7..8a9c5ea936 100644 --- a/modules/calib3d/test/test_chessboardgenerator.cpp +++ b/modules/calib3d/test/test_chessboardgenerator.cpp @@ -51,7 +51,7 @@ using namespace cv; using namespace std; ChessBoardGenerator::ChessBoardGenerator(const Size& _patternSize) : sensorWidth(32), sensorHeight(24), - squareEdgePointsNum(200), min_cos(std::sqrt(2.f)*0.5f), cov(0.5), + squareEdgePointsNum(200), min_cos(std::sqrt(3.f)*0.5f), cov(0.5), patternSize(_patternSize), rendererResolutionMultiplier(4), tvec(Mat::zeros(1, 3, CV_32F)) { Rodrigues(Mat::eye(3, 3, CV_32F), rvec); @@ -85,8 +85,10 @@ void cv::ChessBoardGenerator::generateBasis(Point3f& pb1, Point3f& pb2) const { n[0] = rng.uniform(-1.f, 1.f); n[1] = rng.uniform(-1.f, 1.f); - n[2] = rng.uniform(-1.f, 1.f); + n[2] = rng.uniform(0.0f, 1.f); float len = (float)norm(n); + if (len < 1e-3) + continue; n[0]/=len; n[1]/=len; n[2]/=len; From ffcf866e2f64397bc25dc609e1b2765b474b6784 Mon Sep 17 00:00:00 2001 From: bedbad Date: Mon, 12 Dec 2016 14:29:53 -0500 Subject: [PATCH 218/409] Merge pull request #7754 from LAZI-2240:toupstream2.4 Introducing boundingRect2f() (#7754) * Introducing boundingRect2f to return exact bounding float rectangle for RotatedRect * Introducing boundingRect2f to return exact bounding float rectangle for RotatedRect * Introducing boundingRect2f() - update * Introducing boundingRect2f to return exact bounding float rectangle for RotatedRect * Introducing boundingRect2f() - update - tested --- modules/core/include/opencv2/core/core.hpp | 4 +++- modules/core/src/matrix.cpp | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/core.hpp b/modules/core/include/opencv2/core/core.hpp index 591d50ade6..0e8091a54d 100644 --- a/modules/core/include/opencv2/core/core.hpp +++ b/modules/core/include/opencv2/core/core.hpp @@ -910,8 +910,10 @@ public: //! returns 4 vertices of the rectangle void points(Point2f pts[]) const; - //! returns the minimal up-right rectangle containing the rotated rectangle + //! returns the minimal up-right integer rectangle containing the rotated rectangle Rect boundingRect() const; + //! returns the minimal (exact) floating point rectangle containing the rotated rectangle, not intended for use with images + Rect_ boundingRect2f() const; //! conversion to the old-style CvBox2D structure operator CvBox2D() const; diff --git a/modules/core/src/matrix.cpp b/modules/core/src/matrix.cpp index 57abffc3ca..aee377f665 100644 --- a/modules/core/src/matrix.cpp +++ b/modules/core/src/matrix.cpp @@ -4277,6 +4277,16 @@ Rect RotatedRect::boundingRect() const return r; } + +Rect_ RotatedRect::boundingRect2f() const +{ + Point2f pt[4]; + points(pt); + Rect_ r(Point_(min(min(min(pt[0].x, pt[1].x), pt[2].x), pt[3].x), min(min(min(pt[0].y, pt[1].y), pt[2].y), pt[3].y)), + Point_(max(max(max(pt[0].x, pt[1].x), pt[2].x), pt[3].x), max(max(max(pt[0].y, pt[1].y), pt[2].y), pt[3].y))); + return r; +} + } /* End of file. */ From 12383a124be9bd9a5d8d7b8a14fa99ecf2c7a0f0 Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Mon, 5 Dec 2016 14:26:15 +0300 Subject: [PATCH 219/409] Disable error throwing in convexityDefects when hull is a line or point --- modules/imgproc/src/convhull.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/imgproc/src/convhull.cpp b/modules/imgproc/src/convhull.cpp index f7e7c2291d..1f8ef7b44b 100644 --- a/modules/imgproc/src/convhull.cpp +++ b/modules/imgproc/src/convhull.cpp @@ -280,11 +280,16 @@ void convexityDefects( InputArray _points, InputArray _hull, OutputArray _defect Mat hull = _hull.getMat(); int hpoints = hull.checkVector(1, CV_32S); - CV_Assert( hpoints > 2 ); + CV_Assert( hpoints > 0 ); const Point* ptr = points.ptr(); const int* hptr = hull.ptr(); std::vector defects; + if ( hpoints < 3 ) //if hull consists of one or two points, contour is always convex + { + _defects.release(); + return; + } // 1. recognize co-orientation of the contour and its hull bool rev_orientation = ((hptr[1] > hptr[0]) + (hptr[2] > hptr[1]) + (hptr[0] > hptr[2])) != 2; From 166d75e3c5ec53f1b6367f3b9fcc32e6510fbd2b Mon Sep 17 00:00:00 2001 From: chacha21 Date: Tue, 13 Dec 2016 10:40:06 +0100 Subject: [PATCH 220/409] Fix for unhandled error cases when using Media Foundation Some function calls to Media Foundation can fail and retrun null pointers. They should be checked before being dereferenced to avoid a crash. --- modules/videoio/src/cap_msmf.cpp | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/modules/videoio/src/cap_msmf.cpp b/modules/videoio/src/cap_msmf.cpp index c10a7c045e..a63e4fa59f 100644 --- a/modules/videoio/src/cap_msmf.cpp +++ b/modules/videoio/src/cap_msmf.cpp @@ -1986,13 +1986,17 @@ long videoDevice::resetDevice(IMFActivate *pActivate) &vd_pFriendlyName, NULL ); - hr = pActivate->ActivateObject( - __uuidof(IMFMediaSource), - (void**)&pSource - ); - enumerateCaptureFormats(pSource); - buildLibraryofTypes(); - SafeRelease(&pSource); + if (SUCCEEDED(hr)) + hr = pActivate->ActivateObject( + __uuidof(IMFMediaSource), + (void**)&pSource + ); + if (SUCCEEDED(hr) && pSource) + { + enumerateCaptureFormats(pSource); + buildLibraryofTypes(); + SafeRelease(&pSource); + }//end if (SUCCEEDED(hr) && pSource) if(FAILED(hr)) { vd_pFriendlyName = NULL; @@ -2638,7 +2642,12 @@ HRESULT videoDevice::enumerateCaptureFormats(IMFMediaSource *pSource) _ComPtr pSD = NULL; _ComPtr pHandler = NULL; _ComPtr pType = NULL; - HRESULT hr = pSource->CreatePresentationDescriptor(pPD.GetAddressOf()); + HRESULT hr = !pSource ? E_POINTER : S_OK; + if (FAILED(hr)) + { + goto done; + } + hr = pSource->CreatePresentationDescriptor(pPD.GetAddressOf()); if (FAILED(hr)) { goto done; @@ -3815,7 +3824,12 @@ HRESULT CvCaptureFile_MSMF::enumerateCaptureFormats(IMFMediaSource *pSource) _ComPtr pSD = NULL; _ComPtr pHandler = NULL; _ComPtr pType = NULL; - HRESULT hr = pSource->CreatePresentationDescriptor(pPD.GetAddressOf()); + HRESULT hr = !pSource ? E_POINTER : S_OK; + if (FAILED(hr)) + { + goto done; + } + hr = pSource->CreatePresentationDescriptor(pPD.GetAddressOf()); if (FAILED(hr)) { goto done; From 2c89b39eee3de2e30b3f66cfb7bc44c87c77f6d7 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Fri, 25 Nov 2016 21:49:26 +0300 Subject: [PATCH 221/409] Added OpenVX based processing to calcHist --- 3rdparty/openvx/include/ivx.hpp | 176 ++++++++++++++++++++++++++++++ modules/imgproc/src/histogram.cpp | 70 +++++++++++- 2 files changed, 241 insertions(+), 5 deletions(-) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index 3d5102f5cf..04304be390 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -343,6 +343,15 @@ template <> struct RefTypeTraits static vx_status release(vxType& ref) { return vxReleaseLUT(&ref); } }; +class Distribution; +template <> struct RefTypeTraits +{ + typedef vx_distribution vxType; + typedef Distribution wrapperType; + static const vx_enum vxTypeEnum = VX_TYPE_DISTRIBUTION; + static vx_status release(vxType& ref) { return vxReleaseDistribution(&ref); } +}; + #ifdef IVX_USE_CXX98 /// Casting to vx_reference with compile-time check @@ -2738,6 +2747,173 @@ public: #endif //IVX_USE_OPENCV }; +/* +* Distribution +*/ +class Distribution : public RefWrapper +{ +public: + IVX_REF_STD_CTORS_AND_ASSIGNMENT(Distribution); + + static Distribution create(vx_context context, vx_size numBins, vx_int32 offset, vx_uint32 range) + { + return Distribution(vxCreateDistribution(context, numBins, offset, range)); + } + +#ifndef VX_VERSION_1_1 + static const vx_enum + VX_MEMORY_TYPE_HOST = VX_IMPORT_TYPE_HOST, + VX_DISTRIBUTION_DIMENSIONS = VX_DISTRIBUTION_ATTRIBUTE_DIMENSIONS, + VX_DISTRIBUTION_OFFSET = VX_DISTRIBUTION_ATTRIBUTE_OFFSET, + VX_DISTRIBUTION_RANGE = VX_DISTRIBUTION_ATTRIBUTE_RANGE, + VX_DISTRIBUTION_BINS = VX_DISTRIBUTION_ATTRIBUTE_BINS, + VX_DISTRIBUTION_WINDOW = VX_DISTRIBUTION_ATTRIBUTE_WINDOW, + VX_DISTRIBUTION_SIZE = VX_DISTRIBUTION_ATTRIBUTE_SIZE; +#endif + + template + void query(vx_enum att, T& value) const + { + IVX_CHECK_STATUS(vxQueryDistribution(ref, att, &value, sizeof(value))); + } + + vx_size dimensions() const + { + vx_size v; + query(VX_DISTRIBUTION_DIMENSIONS, v); + return v; + } + + vx_int32 offset() const + { + vx_int32 v; + query(VX_DISTRIBUTION_OFFSET, v); + return v; + } + + vx_uint32 range() const + { + vx_uint32 v; + query(VX_DISTRIBUTION_RANGE, v); + return v; + } + + vx_size bins() const + { + vx_size v; + query(VX_DISTRIBUTION_BINS, v); + return v; + } + + vx_uint32 window() const + { + vx_uint32 v; + query(VX_DISTRIBUTION_WINDOW, v); + return v; + } + + vx_size size() const + { + vx_size v; + query(VX_DISTRIBUTION_SIZE, v); + return v; + } + + vx_size dataType() const + { + return VX_TYPE_UINT32; + } + + void copyTo(void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): output pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyDistribution(ref, data, VX_READ_ONLY, VX_MEMORY_TYPE_HOST)); +#else + IVX_CHECK_STATUS(vxAccessDistribution(ref, &data, VX_READ_ONLY)); + IVX_CHECK_STATUS(vxCommitDistribution(ref, data)); +#endif + } + + void copyFrom(const void* data) + { + if (!data) throw WrapperError(std::string(__func__) + "(): input pointer is 0"); +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyDistribution(ref, const_cast(data), VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST)); +#else + IVX_CHECK_STATUS(vxAccessDistribution(ref, const_cast(&data), VX_WRITE_ONLY)); + IVX_CHECK_STATUS(vxCommitDistribution(ref, data)); +#endif + } + + void copy(void* data, vx_enum usage, vx_enum memType = VX_MEMORY_TYPE_HOST) + { +#ifdef VX_VERSION_1_1 + IVX_CHECK_STATUS(vxCopyDistribution(ref, data, usage, memType)); +#else + IVX_CHECK_STATUS(vxAccessDistribution(ref, const_cast(&data), usage)); + IVX_CHECK_STATUS(vxCommitDistribution(ref, data)); + (void)memType; +#endif + } + + template void copyTo(std::vector& data) + { + if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (data.size() != bins()) + { + if (data.size() == 0) + data.resize(bins()); + else + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + } + copyTo(&data[0]); + } + + template void copyFrom(const std::vector& data) + { + if (TypeToEnum::value != dataType()) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + if (data.size() != bins()) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + copyFrom(&data[0]); + } + +#ifdef IVX_USE_OPENCV + void copyTo(cv::Mat& m) + { + if (m.type() != enumToCVType(dataType())) throw WrapperError(std::string(__func__) + "(): destination type is wrong"); + if (!( + ((vx_size)(m.rows) == bins() && m.cols == 1) || + ((vx_size)(m.cols) == bins() && m.rows == 1) + ) && !m.empty()) + throw WrapperError(std::string(__func__) + "(): destination size is wrong"); + + if (m.isContinuous() && (vx_size)(m.total()) == bins()) + { + copyTo(m.ptr()); + } + else + { + cv::Mat tmp(1, (int)bins(), enumToCVType(dataType())); + copyTo(tmp.ptr()); + if (m.empty()) + m = tmp; + else + tmp.copyTo(m); + } + } + + void copyFrom(const cv::Mat& m) + { + if (!( + ((vx_size)(m.rows) == bins() && m.cols == 1) || + ((vx_size)(m.cols) == bins() && m.rows == 1) + )) throw WrapperError(std::string(__func__) + "(): source size is wrong"); + if (m.type() != enumToCVType(dataType())) throw WrapperError(std::string(__func__) + "(): source type is wrong"); + copyFrom(m.isContinuous() ? m.ptr() : m.clone().ptr()); + } +#endif //IVX_USE_OPENCV +}; + /// Standard nodes namespace nodes { diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index 7f46bc2d03..23ec520444 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -42,11 +42,7 @@ #include "precomp.hpp" #include "opencl_kernels_imgproc.hpp" -#ifdef HAVE_OPENVX -#define IVX_USE_OPENCV -#define IVX_HIDE_INFO_WARNINGS -#include "ivx.hpp" -#endif +#include "opencv2/core/openvx/ovx_defs.hpp" namespace cv { @@ -1268,6 +1264,63 @@ private: } +#ifdef HAVE_OPENVX +namespace cv +{ + static bool openvx_calchist(const Mat& image, OutputArray _hist, const int histSize, + const float* _range) + { + vx_int32 offset = (vx_int32)(_range[0]); + vx_uint32 range = (vx_uint32)(_range[1] - _range[0]); + if (float(offset) != _range[0] || float(range) != (_range[1] - _range[0])) + return false; + + size_t total_size = image.total(); + int rows = image.dims > 1 ? image.size[0] : 1, cols = rows ? (int)(total_size / rows) : 0; + if (image.dims > 2 && !(image.isContinuous() && cols > 0 && (size_t)rows*cols == total_size)) + return false; + + try + { + ivx::Context ctx = ivx::Context::create(); +#if VX_VERSION <= VX_VERSION_1_0 + if (ctx.vendorID() == VX_ID_KHRONOS && (range % histSize)) + return false; +#endif + + ivx::Image + img = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(cols, rows, 1, (vx_int32)(image.step[0])), image.data); + + ivx::Distribution vxHist = ivx::Distribution::create(ctx, histSize, offset, range); + ivx::IVX_CHECK_STATUS(vxuHistogram(ctx, img, vxHist)); + + _hist.create(1, &histSize, CV_32F); + Mat hist = _hist.getMat(), ihist = hist; + ihist.flags = (ihist.flags & ~CV_MAT_TYPE_MASK) | CV_32S; + vxHist.copyTo(ihist); + ihist.convertTo(hist, CV_32F); + +#ifdef VX_VERSION_1_1 + img.swapHandle(); +#endif + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + + return true; + } +} +#endif + #if defined(HAVE_IPP) namespace cv { @@ -1321,6 +1374,13 @@ void cv::calcHist( const Mat* images, int nimages, const int* channels, { CV_INSTRUMENT_REGION() + CV_OVX_RUN( + images && histSize && + nimages == 1 && images[0].type() == CV_8UC1 && dims == 1 && _mask.getMat().empty() && + (!channels || channels[0] == 0) && !accumulate && uniform && + ranges && ranges[0], + openvx_calchist(images[0], _hist, histSize[0], ranges[0])); + CV_IPP_RUN(nimages == 1 && images[0].type() == CV_8UC1 && dims == 1 && channels && channels[0] == 0 && _mask.getMat().empty() && images[0].dims <= 2 && !accumulate && uniform, From 7241b90dec00d673c8f348abd8a50bde4161daea Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 9 Dec 2016 20:03:05 +0300 Subject: [PATCH 222/409] cmake: special target to build/install docs only --- CMakeLists.txt | 14 ++++++++------ doc/CMakeLists.txt | 14 +++++++++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6c8adb3d1..3f2ceb9184 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,12 +278,14 @@ if (ANDROID) endif() endif() -if(ANDROID OR WIN32) - set(OPENCV_DOC_INSTALL_PATH doc) -elseif(INSTALL_TO_MANGLED_PATHS) - set(OPENCV_DOC_INSTALL_PATH share/OpenCV-${OPENCV_VERSION}/doc) -else() - set(OPENCV_DOC_INSTALL_PATH share/OpenCV/doc) +if(NOT DEFINED OPENCV_DOC_INSTALL_PATH) + if(ANDROID OR WIN32) + set(OPENCV_DOC_INSTALL_PATH doc) + elseif(INSTALL_TO_MANGLED_PATHS) + set(OPENCV_DOC_INSTALL_PATH share/OpenCV-${OPENCV_VERSION}/doc) + else() + set(OPENCV_DOC_INSTALL_PATH share/OpenCV/doc) + endif() endif() if(WIN32) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 29d5a0a1e1..ebb3855740 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -49,7 +49,7 @@ if(BUILD_DOCS AND HAVE_SPHINX) function(ocv_doc_add_file_to_fake_root source destination) add_custom_command( OUTPUT "${DOC_FAKE_ROOT}/${destination}" - COMMAND "${CMAKE_COMMAND}" -E copy "${source}" "${DOC_FAKE_ROOT}/${destination}" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${source}" "${DOC_FAKE_ROOT}/${destination}" DEPENDS "${source}" COMMENT "Copying ${destination} to fake root..." VERBATIM @@ -184,4 +184,16 @@ if(HAVE_DOC_GENERATOR) foreach(f ${OPTIONAL_DOC_LIST}) install(FILES "${f}" DESTINATION "${OPENCV_DOC_INSTALL_PATH}" OPTIONAL COMPONENT docs) endforeach() + + # Alias to build/install docs only + set(__deps "") + foreach(target docs html_docs doxygen) + if(TARGET ${target}) + list(APPEND __deps ${target}) + endif() + endforeach() + add_custom_target(install_docs + DEPENDS ${__deps} + COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=docs -P "${CMAKE_BINARY_DIR}/cmake_install.cmake" + ) endif(HAVE_DOC_GENERATOR) From dd99b7c1cbf1d8a5f2ef55f1c5d2be0184455a2a Mon Sep 17 00:00:00 2001 From: "Randy J. Ray" Date: Tue, 13 Dec 2016 10:54:10 -0800 Subject: [PATCH 223/409] Further editing/refinement of the tutorial. This is based on testing OpenCV 3.1 with VisionWorks, which identified issues. The tutorial now specifies 3 cherry-pick commands for opencv. --- .../building_tegra_cuda.markdown | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/doc/tutorials/introduction/building_tegra_cuda/building_tegra_cuda.markdown b/doc/tutorials/introduction/building_tegra_cuda/building_tegra_cuda.markdown index b93f23220b..9c384a08da 100644 --- a/doc/tutorials/introduction/building_tegra_cuda/building_tegra_cuda.markdown +++ b/doc/tutorials/introduction/building_tegra_cuda/building_tegra_cuda.markdown @@ -32,7 +32,7 @@ There are two (2) ways to get the OpenCV source code: * Direct download from the [OpenCV downloads](http://opencv.org/downloads.html) page * Cloning the git repositories hosted on [GitHub](https://github.com/opencv) -For this guide, the focus is on using the git repositories. This is because the 3.1.0 version of OpenCV will not build with CUDA 8.0 without applying a small upstream change from the git repository. +For this guide, the focus is on using the git repositories. This is because the 3.1.0 version of OpenCV will not build with CUDA 8.0 without applying a few small upstream changes from the git repository. OpenCV ------ @@ -49,7 +49,7 @@ To build the 3.1.0 version (as opposed to building the most-recent source), you __Note:__ This operation creates a new local branch in your clone's repository. -If you are building OpenCV with CUDA 8.0, you must execute one additional git command. This is to apply a fix for building specifically with the 8.0 version of CUDA that was not part of the 3.1.0 release. To do this, use the "git cherry-pick" command: +There are some upstream changes that must be applied via the `git cherry-pick` command. The first of these is to apply a fix for building specifically with the 8.0 version of CUDA that was not part of the 3.1.0 release: # While still in the opencv directory: $ git cherry-pick 10896 @@ -60,7 +60,27 @@ You will see the following output from the command: Author: Vladislav Vinogradov 1 file changed, 2 insertions(+), 1 deletion(-) -This step is not needed if you are building with CUDA 7.0 or 7.5. +Secondly, there is a fix for a CMake macro call that is problematic on some systems: + + $ git cherry pick cdb9c + +You should see output similar to: + + [v3.1.0-28613 e5ac2e4] gpu samples: fix REMOVE_ITEM error + Author: Alexander Alekhin + 1 file changed, 1 insertion(+), 1 deletion(-) + +The last upstream fix that is needed deals with the `pkg-config` configuration file that is bundled with the developer package (`libopencv-dev`): + + $ git cherry-pick 24dbb + +You should see output similar to: + + [v3.1.0 3a6d7ab] pkg-config: modules list contains only OpenCV modules (fixes #5852) + Author: Alexander Alekhin + 1 file changed, 7 insertions(+), 4 deletions(-) + +At this point, the `opencv` repository is ready for building. OpenCV Extra ------------ From 965deaba8d59b2f551de1fab0c71aeeb4e4b180b Mon Sep 17 00:00:00 2001 From: mshabunin Date: Wed, 14 Dec 2016 14:14:13 +0300 Subject: [PATCH 224/409] Documentation fixes for latest doxygen --- modules/core/include/opencv2/core/hal/intrin_cpp.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp index 2f8d027b7e..93ca397817 100644 --- a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp @@ -130,7 +130,7 @@ Element-wise binary and unary operations. @ref v_shl, @ref v_shr - Bitwise logic: -@ref operator &(const v_reg &a, const v_reg &b) "&", +@ref operator&(const v_reg &a, const v_reg &b) "&", @ref operator |(const v_reg &a, const v_reg &b) "|", @ref operator ^(const v_reg &a, const v_reg &b) "^", @ref operator ~(const v_reg &a) "~" @@ -140,7 +140,7 @@ Element-wise binary and unary operations. @ref operator >=(const v_reg &a, const v_reg &b) ">=", @ref operator <(const v_reg &a, const v_reg &b) "<", @ref operator <=(const v_reg &a, const v_reg &b) "<=", -@ref operator ==(const v_reg &a, const v_reg &b) "==", +@ref operator==(const v_reg &a, const v_reg &b) "==", @ref operator !=(const v_reg &a, const v_reg &b) "!=" - min/max: @ref v_min, @ref v_max From 138f0cbf3cb5325a8af5277fb4d22deaba458c39 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Wed, 14 Dec 2016 13:23:52 +0300 Subject: [PATCH 225/409] Added wrapper for IMAGE_SPACE attribute setter --- 3rdparty/openvx/include/ivx.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index 3d5102f5cf..fc2ceb1106 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -1505,6 +1505,15 @@ static const vx_enum } #endif // VX_VERSION_1_1 + /// vxSetImageAttribute() wrapper + template + void setAttribute(vx_enum att, T& value) const + { IVX_CHECK_STATUS(vxSetImageAttribute(ref, att, &value, sizeof(value))); } + + /// vxSetImageAttribute(SPACE) wrapper + void setColorSpace(const vx_enum& sp) + { setAttribute(VX_IMAGE_SPACE, sp); } + /// vxGetValidRegionImage() wrapper vx_rectangle_t getValidRegion() const { From f8b4d287457df7ca191c22bdbb563e78bd3908b3 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Thu, 1 Dec 2016 17:24:48 +0300 Subject: [PATCH 226/409] Added OpenVX based processing to remap --- 3rdparty/openvx/include/ivx.hpp | 207 ++++++++++++++++++++++++++++++++ modules/imgproc/src/imgwarp.cpp | 85 +++++++++++++ 2 files changed, 292 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index 04304be390..c808361b28 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -352,6 +352,15 @@ template <> struct RefTypeTraits static vx_status release(vxType& ref) { return vxReleaseDistribution(&ref); } }; +class Remap; +template <> struct RefTypeTraits +{ + typedef vx_remap vxType; + typedef Remap wrapperType; + static const vx_enum vxTypeEnum = VX_TYPE_REMAP; + static vx_status release(vxType& ref) { return vxReleaseRemap(&ref); } +}; + #ifdef IVX_USE_CXX98 /// Casting to vx_reference with compile-time check @@ -909,6 +918,34 @@ public: void setImmediateBorder(vx_enum mode, const vx_pixel_value_t& val) { border_t bm = {mode, val}; setImmediateBorder(bm); } + /// vxSetContextAttribute(BORDER) wrapper + template + void setImmediateBorder(vx_enum mode, const T& _val) + { + vx_pixel_value_t val; + switch (TypeToEnum::value) + { + case VX_TYPE_UINT8: + val.U8 = _val; + break; + case VX_TYPE_INT16: + val.S16 = _val; + break; + case VX_TYPE_UINT16: + val.U16 = _val; + break; + case VX_TYPE_INT32: + val.S32 = _val; + break; + case VX_TYPE_UINT32: + val.U32 = _val; + break; + default: + throw WrapperError("Unsupported constant border value type"); + } + setImmediateBorder(mode, val); + } + /// vxSetContextAttribute(BORDER) wrapper void setImmediateBorder(vx_enum mode) { vx_pixel_value_t val = {}; setImmediateBorder(mode, val); } @@ -1264,6 +1301,34 @@ static const vx_enum void setBorder(vx_enum mode, const vx_pixel_value_t& val) { vx_border_t bm = {mode, val}; setBorder(bm); } + /// vxSetNodeAttribute(BORDER) wrapper + template + void setBorder(vx_enum mode, const T& _val) + { + vx_pixel_value_t val; + switch (TypeToEnum::value) + { + case VX_TYPE_UINT8: + val.U8 = _val; + break; + case VX_TYPE_INT16: + val.S16 = _val; + break; + case VX_TYPE_UINT16: + val.U16 = _val; + break; + case VX_TYPE_INT32: + val.S32 = _val; + break; + case VX_TYPE_UINT32: + val.U32 = _val; + break; + default: + throw WrapperError("Unsupported constant border value type"); + } + setBorder(mode, val); + } + /// vxSetNodeAttribute(BORDER) wrapper void setBorder(vx_enum mode) { vx_pixel_value_t val = {}; setBorder(mode, val); } @@ -2914,6 +2979,148 @@ public: #endif //IVX_USE_OPENCV }; +/* +* Remap +*/ +class Remap : public RefWrapper +{ +public: + IVX_REF_STD_CTORS_AND_ASSIGNMENT(Remap); + + static Remap create(vx_context context, vx_uint32 src_width, vx_uint32 src_height, vx_uint32 dst_width, vx_uint32 dst_height) + { + return Remap(vxCreateRemap(context, src_width, src_height, dst_width, dst_height)); + } + +#ifndef VX_VERSION_1_1 + static const vx_enum + VX_REMAP_SOURCE_WIDTH = VX_REMAP_ATTRIBUTE_SOURCE_WIDTH, + VX_REMAP_SOURCE_HEIGHT = VX_REMAP_ATTRIBUTE_SOURCE_HEIGHT, + VX_REMAP_DESTINATION_WIDTH = VX_REMAP_ATTRIBUTE_DESTINATION_WIDTH, + VX_REMAP_DESTINATION_HEIGHT = VX_REMAP_ATTRIBUTE_DESTINATION_HEIGHT; +#endif + + template + void query(vx_enum att, T& value) const + { IVX_CHECK_STATUS(vxQueryRemap(ref, att, &value, sizeof(value))); } + + vx_uint32 srcWidth() const + { + vx_uint32 v; + query(VX_REMAP_SOURCE_WIDTH, v); + return v; + } + + vx_uint32 srcHeight() const + { + vx_uint32 v; + query(VX_REMAP_SOURCE_HEIGHT, v); + return v; + } + + vx_uint32 dstWidth() const + { + vx_uint32 v; + query(VX_REMAP_DESTINATION_WIDTH, v); + return v; + } + + vx_uint32 dstHeight() const + { + vx_uint32 v; + query(VX_REMAP_DESTINATION_HEIGHT, v); + return v; + } + + vx_uint32 srcCoordType() const + { return VX_TYPE_FLOAT32; } + + vx_uint32 dstCoordType() const + { return VX_TYPE_UINT32; } + + void setMapping(vx_uint32 dst_x, vx_uint32 dst_y, vx_float32 src_x, vx_float32 src_y) + { IVX_CHECK_STATUS(vxSetRemapPoint(ref, dst_x, dst_y, src_x, src_y)); } + + void getMapping(vx_uint32 dst_x, vx_uint32 dst_y, vx_float32 &src_x, vx_float32 &src_y) const + { IVX_CHECK_STATUS(vxGetRemapPoint(ref, dst_x, dst_y, &src_x, &src_y)); } + +#ifdef IVX_USE_OPENCV + void setMappings(const cv::Mat& map_x, const cv::Mat& map_y) + { + if (map_x.type() != enumToCVType(srcCoordType()) || map_y.type() != enumToCVType(srcCoordType())) + throw WrapperError(std::string(__func__) + "(): mapping type is wrong"); + if ((vx_uint32)(map_x.rows) != dstHeight() || (vx_uint32)(map_x.cols) != dstWidth()) + throw WrapperError(std::string(__func__) + "(): x mapping size is wrong"); + if ((vx_uint32)(map_y.rows) != dstHeight() || (vx_uint32)(map_y.cols) != dstWidth()) + throw WrapperError(std::string(__func__) + "(): y mapping size is wrong"); + + for (vx_uint32 y = 0; y < dstHeight(); y++) + { + const vx_float32* map_x_line = map_x.ptr(y); + const vx_float32* map_y_line = map_y.ptr(y); + for (vx_uint32 x = 0; x < dstWidth(); x++) + setMapping(x, y, map_x_line[x], map_y_line[x]); + } + } + + void setMappings(const cv::Mat& map) + { + if (map.depth() != CV_MAT_DEPTH(enumToCVType(srcCoordType())) || map.channels() != 2) + throw WrapperError(std::string(__func__) + "(): mapping type is wrong"); + if ((vx_uint32)(map.rows) != dstHeight() || (vx_uint32)(map.cols) != dstWidth()) + throw WrapperError(std::string(__func__) + "(): x mapping size is wrong"); + + for (vx_uint32 y = 0; y < dstHeight(); y++) + { + const vx_float32* map_line = map.ptr(y); + for (vx_uint32 x = 0; x < 2*dstWidth(); x+=2) + setMapping(x, y, map_line[x], map_line[x+1]); + } + } + + void getMappings(cv::Mat& map_x, cv::Mat& map_y) const + { + if (map_x.type() != enumToCVType(srcCoordType()) || map_y.type() != enumToCVType(srcCoordType())) + throw WrapperError(std::string(__func__) + "(): mapping type is wrong"); + if (((vx_uint32)(map_x.rows) != dstHeight() || (vx_uint32)(map_x.cols) != dstWidth()) && !map_x.empty()) + throw WrapperError(std::string(__func__) + "(): x mapping size is wrong"); + if (((vx_uint32)(map_y.rows) != dstHeight() || (vx_uint32)(map_y.cols) != dstWidth()) && !map_y.empty()) + throw WrapperError(std::string(__func__) + "(): y mapping size is wrong"); + + if (map_x.empty()) + map_x = cv::Mat((int)dstHeight(), (int)dstWidth(), enumToCVType(srcCoordType())); + if (map_y.empty()) + map_y = cv::Mat((int)dstHeight(), (int)dstWidth(), enumToCVType(srcCoordType())); + + for (vx_uint32 y = 0; y < dstHeight(); y++) + { + vx_float32* map_x_line = map_x.ptr(y); + vx_float32* map_y_line = map_y.ptr(y); + for (vx_uint32 x = 0; x < dstWidth(); x++) + getMapping(x, y, map_x_line[x], map_y_line[x]); + } + } + + void getMappings(cv::Mat& map) const + { + if (map.depth() != CV_MAT_DEPTH(enumToCVType(srcCoordType())) || map.channels() != 2) + throw WrapperError(std::string(__func__) + "(): mapping type is wrong"); + if (((vx_uint32)(map.rows) != dstHeight() || (vx_uint32)(map.cols) != dstWidth()) && !map.empty()) + throw WrapperError(std::string(__func__) + "(): x mapping size is wrong"); + + if (map.empty()) + map = cv::Mat((int)dstHeight(), (int)dstWidth(), CV_MAKETYPE(CV_MAT_DEPTH(enumToCVType(srcCoordType())),2)); + + for (vx_uint32 y = 0; y < dstHeight(); y++) + { + vx_float32* map_line = map.ptr(y); + for (vx_uint32 x = 0; x < 2*dstWidth(); x+=2) + getMapping(x, y, map_line[x], map_line[x+1]); + } + } +#endif //IVX_USE_OPENCV +}; + /// Standard nodes namespace nodes { diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index b027b9c39a..c7f5b3c31b 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -51,6 +51,8 @@ #include "opencl_kernels_imgproc.hpp" #include "hal_replacement.hpp" +#include "opencv2/core/openvx/ovx_defs.hpp" + using namespace cv; namespace cv @@ -4750,6 +4752,78 @@ static bool ocl_logPolar(InputArray _src, OutputArray _dst, } #endif +#ifdef HAVE_OPENVX +static bool openvx_remap(Mat src, Mat dst, Mat map1, Mat map2, int interpolation, const Scalar& borderValue) +{ + vx_interpolation_type_e inter_type; + switch (interpolation) + { + case INTER_LINEAR: +#if VX_VERSION > VX_VERSION_1_0 + inter_type = VX_INTERPOLATION_BILINEAR; +#else + inter_type = VX_INTERPOLATION_TYPE_BILINEAR; +#endif + break; + case INTER_NEAREST: +#if VX_VERSION > VX_VERSION_1_0 + inter_type = VX_INTERPOLATION_NEAREST_NEIGHBOR; +#else + inter_type = VX_INTERPOLATION_TYPE_NEAREST_NEIGHBOR; +#endif + break; + case INTER_AREA://AREA interpolation mode is unsupported + default: + return false; + } + + try + { + ivx::Context ctx = ivx::Context::create(); + + Mat a; + if (dst.data != src.data) + a = src; + else + src.copyTo(a); + + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(a.cols, a.rows, 1, (vx_int32)(a.step)), a.data), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(dst.cols, dst.rows, 1, (vx_int32)(dst.step)), dst.data); + + //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 + ivx::border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(VX_BORDER_CONSTANT, (vx_uint8)(borderValue[0])); + + ivx::Remap map = ivx::Remap::create(ctx, src.cols, src.rows, dst.cols, dst.rows); + if (map1.empty()) map.setMappings(map2); + else if (map2.empty()) map.setMappings(map1); + else map.setMappings(map1, map2); + ivx::IVX_CHECK_STATUS(vxuRemap(ctx, ia, map, inter_type, ib)); +#ifdef VX_VERSION_1_1 + ib.swapHandle(); + ia.swapHandle(); +#endif + + ctx.setImmediateBorder(prevBorder); + } + catch (ivx::RuntimeError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + catch (ivx::WrapperError & e) + { + CV_Error(CV_StsInternal, e.what()); + return false; + } + return true; +} +#endif + #if defined HAVE_IPP && IPP_DISABLE_BLOCK typedef IppStatus (CV_STDCALL * ippiRemap)(const void * pSrc, IppiSize srcSize, int srcStep, IppiRect srcRoi, @@ -4852,6 +4926,17 @@ void cv::remap( InputArray _src, OutputArray _dst, Mat src = _src.getMat(), map1 = _map1.getMat(), map2 = _map2.getMat(); _dst.create( map1.size(), src.type() ); Mat dst = _dst.getMat(); + + + CV_OVX_RUN( + src.type() == CV_8UC1 && dst.type() == CV_8UC1 && + (borderType& ~BORDER_ISOLATED) == BORDER_CONSTANT && + ((map1.type() == CV_32FC2 && map2.empty() && map1.size == dst.size) || + (map1.type() == CV_32FC1 && map2.type() == CV_32FC1 && map1.size == dst.size && map2.size == dst.size) || + (map1.empty() && map2.type() == CV_32FC2 && map2.size == dst.size)) && + ((borderType & BORDER_ISOLATED) != 0 || !src.isSubmatrix()), + openvx_remap(src, dst, map1, map2, interpolation, borderValue)); + CV_Assert( dst.cols < SHRT_MAX && dst.rows < SHRT_MAX && src.cols < SHRT_MAX && src.rows < SHRT_MAX ); if( dst.data == src.data ) From 3c5eb513dd0fe24e6cadb3dd08f006bc9c6936de Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Mon, 5 Dec 2016 11:17:40 +0300 Subject: [PATCH 227/409] Fixed OpenVX to OpenCV compatibility for NN remap --- modules/imgproc/src/imgwarp.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index c7f5b3c31b..0fa520228e 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -4766,12 +4766,28 @@ static bool openvx_remap(Mat src, Mat dst, Mat map1, Mat map2, int interpolation #endif break; case INTER_NEAREST: +/* NEAREST_NEIGHBOR mode disabled since OpenCV round half to even while OpenVX sample implementation round half up #if VX_VERSION > VX_VERSION_1_0 inter_type = VX_INTERPOLATION_NEAREST_NEIGHBOR; #else inter_type = VX_INTERPOLATION_TYPE_NEAREST_NEIGHBOR; #endif + if (!map1.empty()) + for (int y = 0; y < map1.rows; ++y) + { + float* line = map1.ptr(y); + for (int x = 0; x < map1.cols; ++x) + line[x] = cvRound(line[x]); + } + if (!map2.empty()) + for (int y = 0; y < map2.rows; ++y) + { + float* line = map2.ptr(y); + for (int x = 0; x < map2.cols; ++x) + line[x] = cvRound(line[x]); + } break; +*/ case INTER_AREA://AREA interpolation mode is unsupported default: return false; From f5bf1e510bee23e7edcf2c1a375e5e2a24af1067 Mon Sep 17 00:00:00 2001 From: mshabunin Date: Wed, 14 Dec 2016 17:06:11 +0300 Subject: [PATCH 228/409] Test run script: added long test filter for valgrind --- modules/ts/misc/run_long.py | 92 ++++++++++++++++++++++++++++++++++++ modules/ts/misc/run_suite.py | 3 +- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 modules/ts/misc/run_long.py diff --git a/modules/ts/misc/run_long.py b/modules/ts/misc/run_long.py new file mode 100644 index 0000000000..6ae76aec0f --- /dev/null +++ b/modules/ts/misc/run_long.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +from __future__ import print_function +import xml.etree.ElementTree as ET +from glob import glob +from pprint import PrettyPrinter as PP + +LONG_TESTS_DEBUG_VALGRIND = [ + ('calib3d', 'Calib3d_InitUndistortRectifyMap.accuracy', 2017.22), + ('features2d', 'Features2d_Feature2d.no_crash', 1235.68), + ('ml', 'ML_RTrees.regression', 1423.47), + ('optflow', 'DenseOpticalFlow_DeepFlow.ReferenceAccuracy', 1360.95), + ('optflow', 'DenseOpticalFlow_DeepFlow_perf.perf/0', 1881.59), + ('optflow', 'DenseOpticalFlow_DeepFlow_perf.perf/1', 5608.75), + ('optflow', 'DenseOpticalFlow_GlobalPatchColliderDCT.ReferenceAccuracy', 5433.84), + ('optflow', 'DenseOpticalFlow_GlobalPatchColliderWHT.ReferenceAccuracy', 5232.73), + ('optflow', 'DenseOpticalFlow_SimpleFlow.ReferenceAccuracy', 1542.1), + ('photo', 'Photo_Denoising.speed', 1484.87), + ('photo', 'Photo_DenoisingColoredMulti.regression', 2447.11), + ('rgbd', 'Rgbd_Normals.compute', 1156.32), + ('shape', 'Hauss.regression', 2625.72), + ('shape', 'ShapeEMD_SCD.regression', 61913.7), + ('shape', 'Shape_SCD.regression', 3311.46), + ('tracking', 'AUKF.br_mean_squared_error', 10764.6), + ('tracking', 'UKF.br_mean_squared_error', 5228.27), + ('xfeatures2d', 'Features2d_RotationInvariance_Descriptor_BoostDesc_LBGM.regression', 1124.51), + ('xfeatures2d', 'Features2d_RotationInvariance_Descriptor_VGG120.regression', 2198.1), + ('xfeatures2d', 'Features2d_RotationInvariance_Descriptor_VGG48.regression', 1958.52), + ('xfeatures2d', 'Features2d_RotationInvariance_Descriptor_VGG64.regression', 2113.12), + ('xfeatures2d', 'Features2d_RotationInvariance_Descriptor_VGG80.regression', 2167.16), + ('xfeatures2d', 'Features2d_ScaleInvariance_Descriptor_BoostDesc_LBGM.regression', 1511.39), + ('xfeatures2d', 'Features2d_ScaleInvariance_Descriptor_VGG120.regression', 1222.07), + ('xfeatures2d', 'Features2d_ScaleInvariance_Descriptor_VGG48.regression', 1059.14), + ('xfeatures2d', 'Features2d_ScaleInvariance_Descriptor_VGG64.regression', 1163.41), + ('xfeatures2d', 'Features2d_ScaleInvariance_Descriptor_VGG80.regression', 1179.06), + ('ximgproc', 'L0SmoothTest.SplatSurfaceAccuracy', 6382.26), + ('ximgproc', 'L0SmoothTest_perf.perf/17', 2052.16), + ('ximgproc', 'RollingGuidanceFilterTest_perf.perf/59', 2760.29), + ('ximgproc', 'TypicalSet1/RollingGuidanceFilterTest.MultiThreadReproducibility/5', 1086.33), + ('ximgproc', 'TypicalSet1/RollingGuidanceFilterTest.MultiThreadReproducibility/7', 1405.05), + ('ximgproc', 'TypicalSet1/RollingGuidanceFilterTest.SplatSurfaceAccuracy/5', 1253.07), + ('ximgproc', 'TypicalSet1/RollingGuidanceFilterTest.SplatSurfaceAccuracy/7', 1599.98), +] + + +def longTestFilter(data): + res = ['*', '-'] + for _, v, _ in data: + res.append(v) + return '--gtest_filter={}'.format(':'.join(res)) + + +# Parse one xml file, filter out tests which took less than 'timeLimit' seconds +# Returns tuple: ( , [ (, , ), ... ] ) +def parseOneFile(filename, timeLimit): + tree = ET.parse(filename) + root = tree.getroot() + + def guess(s, delims): + for delim in delims: + tmp = s.partition(delim) + if len(tmp[1]) != 0: + return tmp[0] + return None + module = guess(filename, ['_posix_', '_nt_', '__']) or root.get('cv_module_name') + if not module: + return (None, None) + res = [] + for elem in root.findall('.//testcase'): + key = '{}.{}'.format(elem.get('classname'), elem.get('name')) + val = elem.get('time') + if float(val) >= timeLimit: + res.append((module, key, float(val))) + return (module, res) + + +# Parse all xml files in current folder and combine results into one list +# Print result to the stdout +if __name__ == '__main__': + LIMIT = 1000 + res = [] + xmls = glob('*.xml') + for xml in xmls: + print('Parsing file', xml, '...') + module, testinfo = parseOneFile(xml, LIMIT) + if not module: + print('SKIP') + continue + res.extend(testinfo) + + print('========= RESULTS =========') + PP(indent=4, width=100).pprint(sorted(res)) diff --git a/modules/ts/misc/run_suite.py b/modules/ts/misc/run_suite.py index bd80874475..ca0841d586 100644 --- a/modules/ts/misc/run_suite.py +++ b/modules/ts/misc/run_suite.py @@ -2,6 +2,7 @@ import datetime from run_utils import * +from run_long import LONG_TESTS_DEBUG_VALGRIND, longTestFilter class TestSuite(object): def __init__(self, options, cache): @@ -99,7 +100,7 @@ class TestSuite(object): if self.options.valgrind_supp: res.append("--suppressions=%s" % self.options.valgrind_supp) res.extend(self.options.valgrind_opt) - return res + cmd + return res + cmd + [longTestFilter(LONG_TESTS_DEBUG_VALGRIND)] return cmd def tryCommand(self, cmd): From 8b9422a052dd8259b1f9325c00551443da85de94 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Wed, 14 Dec 2016 17:31:41 +0300 Subject: [PATCH 229/409] OpenVX wrappers rewritten with CV_OVX_RUN, VX_DbgThrow --- .../include/opencv2/core/openvx/ovx_defs.hpp | 7 +++ modules/core/src/convert.cpp | 20 +++------ modules/core/src/stat.cpp | 38 ++++++---------- modules/imgproc/src/accum.cpp | 36 ++++------------ modules/imgproc/src/canny.cpp | 8 ++-- modules/imgproc/src/deriv.cpp | 21 +++------ modules/imgproc/src/histogram.cpp | 22 +++------- modules/imgproc/src/smooth.cpp | 43 ++++++------------- modules/imgproc/src/thresh.cpp | 18 +++----- 9 files changed, 70 insertions(+), 143 deletions(-) diff --git a/modules/core/include/opencv2/core/openvx/ovx_defs.hpp b/modules/core/include/opencv2/core/openvx/ovx_defs.hpp index a5cad4d99a..3f055dd776 100644 --- a/modules/core/include/opencv2/core/openvx/ovx_defs.hpp +++ b/modules/core/include/opencv2/core/openvx/ovx_defs.hpp @@ -30,4 +30,11 @@ #define CV_OVX_RUN(condition, func, ...) #endif // HAVE_OPENVX +// Throw an error in debug mode or try another implementation in release +#ifdef _DEBUG +#define VX_DbgThrow(s) CV_Error(cv::Error::StsInternal, (s)) +#else +#define VX_DbgThrow(s) return false +#endif + #endif // OPENCV_OVX_DEFS_HPP diff --git a/modules/core/src/convert.cpp b/modules/core/src/convert.cpp index 4e6ed70ba4..4d3b2cc290 100644 --- a/modules/core/src/convert.cpp +++ b/modules/core/src/convert.cpp @@ -4638,12 +4638,6 @@ cvtScaleHalf_( const short* src, size_t sstep, float* dst, size_t #ifdef HAVE_OPENVX -#ifdef _DEBUG -#define VX_DbgThrow(s) CV_Error(cv::Error::StsInternal, (s)) -#else -#define VX_DbgThrow(s) return false; -#endif - template static bool _openvx_cvt(const T* src, size_t sstep, DT* dst, size_t dstep, Size continuousSize) @@ -4734,7 +4728,7 @@ cvt_( const T* src, size_t sstep, CV_OVX_RUN( false, openvx_cvt(src, sstep, dst, dstep, size) - ); + ) sstep /= sizeof(src[0]); dstep /= sizeof(dst[0]); @@ -5407,13 +5401,11 @@ static bool openvx_LUT(Mat src, Mat dst, Mat _lut) } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } return true; @@ -5685,10 +5677,8 @@ void cv::LUT( InputArray _src, InputArray _lut, OutputArray _dst ) _dst.create(src.dims, src.size, CV_MAKETYPE(_lut.depth(), cn)); Mat dst = _dst.getMat(); -#ifdef HAVE_OPENVX - if (openvx_LUT(src, dst, lut)) - return; -#endif + CV_OVX_RUN(true, + openvx_LUT(src, dst, lut)) CV_IPP_RUN(_src.dims() <= 2, ipp_lut(src, lut, dst)); diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index c5eef79a84..5cdae20871 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -47,11 +47,7 @@ #include "opencl_kernels_core.hpp" -#ifdef HAVE_OPENVX -#define IVX_HIDE_INFO_WARNINGS -#define IVX_USE_OPENCV -#include "ivx.hpp" -#endif +#include "opencv2/core/openvx/ovx_defs.hpp" namespace cv { @@ -1706,19 +1702,17 @@ namespace cv for (int c = 1; c < (int)stddev.total(); c++) pstddev[c] = 0; } - - return true; } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } + + return true; } } #endif @@ -1848,10 +1842,8 @@ void cv::meanStdDev( InputArray _src, OutputArray _mean, OutputArray _sdv, Input Mat src = _src.getMat(), mask = _mask.getMat(); CV_Assert( mask.empty() || mask.type() == CV_8UC1 ); -#ifdef HAVE_OPENVX - if (openvx_meanStdDev(src, _mean, _sdv, mask)) - return; -#endif + CV_OVX_RUN(true, + openvx_meanStdDev(src, _mean, _sdv, mask)) CV_IPP_RUN(IPP_VERSION_X100 >= 700, ipp_meanStdDev(src, _mean, _sdv, mask)); @@ -2365,19 +2357,17 @@ static bool openvx_minMaxIdx(Mat &src, double* minVal, double* maxVal, int* minI size_t maxidx = loc.y * cols + loc.x + 1; ofs2idx(src, maxidx, maxIdx); } - - return true; } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } + + return true; } #endif @@ -2505,10 +2495,8 @@ void cv::minMaxIdx(InputArray _src, double* minVal, Mat src = _src.getMat(), mask = _mask.getMat(); -#ifdef HAVE_OPENVX - if (openvx_minMaxIdx(src, minVal, maxVal, minIdx, maxIdx, mask)) - return; -#endif + CV_OVX_RUN(true, + openvx_minMaxIdx(src, minVal, maxVal, minIdx, maxIdx, mask)) CV_IPP_RUN(IPP_VERSION_X100 >= 700, ipp_minMaxIdx(src, minVal, maxVal, minIdx, maxIdx, mask)) diff --git a/modules/imgproc/src/accum.cpp b/modules/imgproc/src/accum.cpp index a156d147c6..8c457e341b 100644 --- a/modules/imgproc/src/accum.cpp +++ b/modules/imgproc/src/accum.cpp @@ -45,11 +45,7 @@ #include "opencl_kernels_imgproc.hpp" #include "opencv2/core/hal/intrin.hpp" -#ifdef HAVE_OPENVX -#define IVX_USE_OPENCV -#define IVX_HIDE_INFO_WARNINGS -#include "ivx.hpp" -#endif +#include "opencv2/core/openvx/ovx_defs.hpp" namespace cv { @@ -1993,13 +1989,11 @@ static bool openvx_accumulate(InputArray _src, InputOutputArray _dst, InputArray } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } return true; @@ -2023,12 +2017,8 @@ void cv::accumulate( InputArray _src, InputOutputArray _dst, InputArray _mask ) CV_IPP_RUN((_src.dims() <= 2 || (_src.isContinuous() && _dst.isContinuous() && (_mask.empty() || _mask.isContinuous()))), ipp_accumulate(_src, _dst, _mask)); -#ifdef HAVE_OPENVX - if(openvx_accumulate(_src, _dst, _mask, 0.0, VX_ACCUMULATE_OP)) - { - return; - } -#endif + CV_OVX_RUN(_src.dims() <= 2, + openvx_accumulate(_src, _dst, _mask, 0.0, VX_ACCUMULATE_OP)) Mat src = _src.getMat(), dst = _dst.getMat(), mask = _mask.getMat(); @@ -2126,12 +2116,8 @@ void cv::accumulateSquare( InputArray _src, InputOutputArray _dst, InputArray _m CV_IPP_RUN((_src.dims() <= 2 || (_src.isContinuous() && _dst.isContinuous() && (_mask.empty() || _mask.isContinuous()))), ipp_accumulate_square(_src, _dst, _mask)); -#ifdef HAVE_OPENVX - if(openvx_accumulate(_src, _dst, _mask, 0.0, VX_ACCUMULATE_SQUARE_OP)) - { - return; - } -#endif + CV_OVX_RUN(_src.dims() <= 2, + openvx_accumulate(_src, _dst, _mask, 0.0, VX_ACCUMULATE_SQUARE_OP)) Mat src = _src.getMat(), dst = _dst.getMat(), mask = _mask.getMat(); @@ -2334,12 +2320,8 @@ void cv::accumulateWeighted( InputArray _src, InputOutputArray _dst, CV_IPP_RUN((_src.dims() <= 2 || (_src.isContinuous() && _dst.isContinuous() && _mask.isContinuous())), ipp_accumulate_weighted(_src, _dst, alpha, _mask)); -#ifdef HAVE_OPENVX - if(openvx_accumulate(_src, _dst, _mask, alpha, VX_ACCUMULATE_WEIGHTED_OP)) - { - return; - } -#endif + CV_OVX_RUN(_src.dims() <= 2, + openvx_accumulate(_src, _dst, _mask, alpha, VX_ACCUMULATE_WEIGHTED_OP)) Mat src = _src.getMat(), dst = _dst.getMat(), mask = _mask.getMat(); diff --git a/modules/imgproc/src/canny.cpp b/modules/imgproc/src/canny.cpp index ee2dc339d9..cdc4e7aac3 100644 --- a/modules/imgproc/src/canny.cpp +++ b/modules/imgproc/src/canny.cpp @@ -822,13 +822,11 @@ static bool openvx_canny(const Mat& src, Mat& dst, int loVal, int hiVal, int kSi } catch(const WrapperError& e) { - //CV_DbgAssert(!"openvx_canny - WrapperError"); - return false; + VX_DbgThrow(e.what()); } catch(const RuntimeError& e) { - //CV_DbgAssert(!"openvx_canny - RuntimeError"); - return false; + VX_DbgThrow(e.what()); } return true; @@ -877,7 +875,7 @@ void Canny( InputArray _src, OutputArray _dst, cvFloor(low_thresh), cvFloor(high_thresh), aperture_size, - L2gradient ) ); + L2gradient ) ) #ifdef HAVE_TEGRA_OPTIMIZATION if (tegra::useTegra() && tegra::canny(src, dst, low_thresh, high_thresh, aperture_size, L2gradient)) diff --git a/modules/imgproc/src/deriv.cpp b/modules/imgproc/src/deriv.cpp index fb3b2fff10..24c5760a00 100644 --- a/modules/imgproc/src/deriv.cpp +++ b/modules/imgproc/src/deriv.cpp @@ -43,11 +43,7 @@ #include "precomp.hpp" #include "opencl_kernels_imgproc.hpp" -#ifdef HAVE_OPENVX -#define IVX_HIDE_INFO_WARNINGS -#define IVX_USE_OPENCV -#include "ivx.hpp" -#endif +#include "opencv2/core/openvx/ovx_defs.hpp" /****************************************************************************************\ Sobel & Scharr Derivative Filters @@ -293,18 +289,17 @@ namespace cv ivx::IVX_CHECK_STATUS(vxuConvolve(ctx, ia, cnv, ib)); } ctx.setImmediateBorder(prevBorder); - return true; } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } + + return true; } } #endif @@ -729,10 +724,8 @@ void cv::Sobel( InputArray _src, OutputArray _dst, int ddepth, int dx, int dy, } #endif -#ifdef HAVE_OPENVX - if (openvx_sobel(_src, _dst, dx, dy, ksize, scale, delta, borderType)) - return; -#endif + CV_OVX_RUN(true, + openvx_sobel(_src, _dst, dx, dy, ksize, scale, delta, borderType)) CV_IPP_RUN(!(ocl::useOpenCL() && _dst.isUMat()), ipp_sobel(_src, _dst, ddepth, dx, dy, ksize, scale, delta, borderType)); diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index 23ec520444..235f34786f 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -1307,13 +1307,11 @@ namespace cv } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } return true; @@ -1379,7 +1377,7 @@ void cv::calcHist( const Mat* images, int nimages, const int* channels, nimages == 1 && images[0].type() == CV_8UC1 && dims == 1 && _mask.getMat().empty() && (!channels || channels[0] == 0) && !accumulate && uniform && ranges && ranges[0], - openvx_calchist(images[0], _hist, histSize[0], ranges[0])); + openvx_calchist(images[0], _hist, histSize[0], ranges[0])) CV_IPP_RUN(nimages == 1 && images[0].type() == CV_8UC1 && dims == 1 && channels && channels[0] == 0 && _mask.getMat().empty() && images[0].dims <= 2 && @@ -3791,13 +3789,11 @@ static bool openvx_equalize_hist(Mat srcMat, Mat dstMat) } catch (RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } return true; @@ -3821,12 +3817,8 @@ void cv::equalizeHist( InputArray _src, OutputArray _dst ) _dst.create( src.size(), src.type() ); Mat dst = _dst.getMat(); -#ifdef HAVE_OPENVX - if(openvx_equalize_hist(src, dst)) - { - return; - } -#endif + CV_OVX_RUN(true, + openvx_equalize_hist(src, dst)) Mutex histogramLockInstance; diff --git a/modules/imgproc/src/smooth.cpp b/modules/imgproc/src/smooth.cpp index afb434cf40..76444398ed 100644 --- a/modules/imgproc/src/smooth.cpp +++ b/modules/imgproc/src/smooth.cpp @@ -44,11 +44,8 @@ #include "precomp.hpp" #include "opencl_kernels_imgproc.hpp" -#ifdef HAVE_OPENVX -#define IVX_HIDE_INFO_WARNINGS -#define IVX_USE_OPENCV -#include "ivx.hpp" -#endif +#include "opencv2/core/openvx/ovx_defs.hpp" + /* * This file includes the code, contributed by Simon Perreault * (the function icvMedianBlur_8u_O1) @@ -1730,13 +1727,11 @@ namespace cv } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } return true; @@ -1855,10 +1850,8 @@ void cv::boxFilter( InputArray _src, OutputArray _dst, int ddepth, CV_OCL_RUN(_dst.isUMat(), ocl_boxFilter(_src, _dst, ddepth, ksize, anchor, borderType, normalize)) -#ifdef HAVE_OPENVX - if (openvx_boxfilter(_src, _dst, ddepth, ksize, anchor, normalize, borderType)) - return; -#endif + CV_OVX_RUN(true, + openvx_boxfilter(_src, _dst, ddepth, ksize, anchor, normalize, borderType)) Mat src = _src.getMat(); int stype = src.type(), sdepth = CV_MAT_DEPTH(stype), cn = CV_MAT_CN(stype); @@ -2300,13 +2293,11 @@ static bool openvx_gaussianBlur(InputArray _src, OutputArray _dst, Size ksize, } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } return true; } @@ -2433,10 +2424,8 @@ void cv::GaussianBlur( InputArray _src, OutputArray _dst, Size ksize, return; } -#ifdef HAVE_OPENVX - if (openvx_gaussianBlur(_src, _dst, ksize, sigma1, sigma2, borderType)) - return; -#endif + CV_OVX_RUN(true, + openvx_gaussianBlur(_src, _dst, ksize, sigma1, sigma2, borderType)) #ifdef HAVE_TEGRA_OPTIMIZATION Mat src = _src.getMat(); @@ -3439,13 +3428,11 @@ namespace cv } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } return true; @@ -3533,10 +3520,8 @@ void cv::medianBlur( InputArray _src0, OutputArray _dst, int ksize ) _dst.create( src0.size(), src0.type() ); Mat dst = _dst.getMat(); -#ifdef HAVE_OPENVX - if (openvx_medianFilter(_src0, _dst, ksize)) - return; -#endif + CV_OVX_RUN(true, + openvx_medianFilter(_src0, _dst, ksize)) CV_IPP_RUN(IPP_VERSION_X100 >= 810 && ksize <= 5, ipp_medianFilter(_src0,_dst, ksize)); diff --git a/modules/imgproc/src/thresh.cpp b/modules/imgproc/src/thresh.cpp index e7709ba3e0..f4f329739f 100644 --- a/modules/imgproc/src/thresh.cpp +++ b/modules/imgproc/src/thresh.cpp @@ -44,11 +44,7 @@ #include "opencl_kernels_imgproc.hpp" #include "opencv2/core/hal/intrin.hpp" -#ifdef HAVE_OPENVX -#define IVX_HIDE_INFO_WARNINGS -#define IVX_USE_OPENCV -#include "ivx.hpp" -#endif +#include "opencv2/core/openvx/ovx_defs.hpp" namespace cv { @@ -1330,13 +1326,11 @@ static bool openvx_threshold(Mat src, Mat dst, int thresh, int maxval, int type) } catch (ivx::RuntimeError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } catch (ivx::WrapperError & e) { - CV_Error(CV_StsInternal, e.what()); - return false; + VX_DbgThrow(e.what()); } return true; @@ -1396,10 +1390,8 @@ double cv::threshold( InputArray _src, OutputArray _dst, double thresh, double m return thresh; } -#ifdef HAVE_OPENVX - if (openvx_threshold(src, dst, ithresh, imaxval, type)) - return thresh; -#endif + CV_OVX_RUN(true, + openvx_threshold(src, dst, ithresh, imaxval, type), (double)ithresh) thresh = ithresh; maxval = imaxval; From fa6692afcf64d7d4a082175ce8f4ab4dcd03afb7 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 14 Dec 2016 12:56:43 -0600 Subject: [PATCH 230/409] Added new overloaded functions for Mat and UMat that accepts std::vector instead of int * for the sizes on a N-dimensional array. This allows for an N-dimensional array to be setup in one line instead of two when using C++11 initializer lists. cv::Mat(3, {zDim, yDim, xDim}, ...) can be used instead of having to create an int pointer to hold the size array. --- modules/core/include/opencv2/core/mat.hpp | 39 +++++++++++++++++++ modules/core/include/opencv2/core/mat.inl.hpp | 17 ++++++++ modules/core/src/matrix.cpp | 16 ++++++++ modules/core/src/umatrix.cpp | 5 +++ 4 files changed, 77 insertions(+) diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 758016a03e..c2b4558db5 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -794,6 +794,13 @@ public: */ Mat(int ndims, const int* sizes, int type); + /** @overload + @param sizes Array of integers specifying an n-dimensional array shape. + @param type Array type. Use CV_8UC1, ..., CV_64FC4 to create 1-4 channel matrices, or + CV_8UC(n), ..., CV_64FC(n) to create multi-channel (up to CV_CN_MAX channels) matrices. + */ + Mat(const std::vector& sizes, int type); + /** @overload @param ndims Array dimensionality. @param sizes Array of integers specifying an n-dimensional array shape. @@ -805,6 +812,17 @@ public: */ Mat(int ndims, const int* sizes, int type, const Scalar& s); + /** @overload + @param sizes Array of integers specifying an n-dimensional array shape. + @param type Array type. Use CV_8UC1, ..., CV_64FC4 to create 1-4 channel matrices, or + CV_8UC(n), ..., CV_64FC(n) to create multi-channel (up to CV_CN_MAX channels) matrices. + @param s An optional value to initialize each matrix element with. To set all the matrix elements to + the particular value after the construction, use the assignment operator + Mat::operator=(const Scalar& value) . + */ + Mat(const std::vector& sizes, int type, const Scalar& s); + + /** @overload @param m Array that (as a whole or partly) is assigned to the constructed matrix. No data is copied by these constructors. Instead, the header pointing to m data or its sub-array is constructed and @@ -861,6 +879,20 @@ public: */ Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0); + /** @overload + @param sizes Array of integers specifying an n-dimensional array shape. + @param type Array type. Use CV_8UC1, ..., CV_64FC4 to create 1-4 channel matrices, or + CV_8UC(n), ..., CV_64FC(n) to create multi-channel (up to CV_CN_MAX channels) matrices. + @param data Pointer to the user data. Matrix constructors that take data and step parameters do not + allocate matrix data. Instead, they just initialize the matrix header that points to the specified + data, which means that no data is copied. This operation is very efficient and can be used to + process external data using OpenCV functions. The external data is not automatically deallocated, so + you should take care of it. + @param steps Array of ndims-1 steps in case of a multi-dimensional array (the last step is always + set to the element size). If not specified, the matrix is assumed to be continuous. + */ + Mat(const std::vector& sizes, int type, void* data, const size_t* steps=0); + /** @overload @param m Array that (as a whole or partly) is assigned to the constructed matrix. No data is copied by these constructors. Instead, the header pointing to m data or its sub-array is constructed and @@ -1328,6 +1360,12 @@ public: */ void create(int ndims, const int* sizes, int type); + /** @overload + @param sizes Array of integers specifying a new array shape. + @param type New matrix type. + */ + void create(const std::vector& sizes, int type); + /** @brief Increments the reference counter. The method increments the reference counter associated with the matrix data. If the matrix header @@ -2273,6 +2311,7 @@ public: void create(int rows, int cols, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT); void create(Size size, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT); void create(int ndims, const int* sizes, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT); + void create(const std::vector& sizes, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT); //! increases the reference counter; use with care to avoid memleaks void addref(); diff --git a/modules/core/include/opencv2/core/mat.inl.hpp b/modules/core/include/opencv2/core/mat.inl.hpp index 31638afd01..d164ac68c8 100644 --- a/modules/core/include/opencv2/core/mat.inl.hpp +++ b/modules/core/include/opencv2/core/mat.inl.hpp @@ -386,6 +386,23 @@ Mat::Mat(int _dims, const int* _sz, int _type, const Scalar& _s) *this = _s; } +inline +Mat::Mat(const std::vector& _sz, int _type) + : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0), + datalimit(0), allocator(0), u(0), size(&rows) +{ + create(_sz, _type); +} + +inline +Mat::Mat(const std::vector& _sz, int _type, const Scalar& _s) + : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0), + datalimit(0), allocator(0), u(0), size(&rows) +{ + create(_sz, _type); + *this = _s; +} + inline Mat::Mat(const Mat& m) : flags(m.flags), dims(m.dims), rows(m.rows), cols(m.cols), data(m.data), diff --git a/modules/core/src/matrix.cpp b/modules/core/src/matrix.cpp index 46137ddda3..d1383af8e8 100644 --- a/modules/core/src/matrix.cpp +++ b/modules/core/src/matrix.cpp @@ -439,6 +439,11 @@ void Mat::create(int d, const int* _sizes, int _type) finalizeHdr(*this); } +void Mat::create(const std::vector& _sizes, int _type) +{ + create((int)_sizes.size(), _sizes.data(), _type); +} + void Mat::copySize(const Mat& m) { setSize(*this, m.dims, 0, 0); @@ -541,6 +546,17 @@ Mat::Mat(int _dims, const int* _sizes, int _type, void* _data, const size_t* _st } +Mat::Mat(const std::vector& _sizes, int _type, void* _data, const size_t* _steps) + : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0), + datalimit(0), allocator(0), u(0), size(&rows) +{ + flags |= CV_MAT_TYPE(_type); + datastart = data = (uchar*)_data; + setSize(*this, (int)_sizes.size(), _sizes.data(), _steps, true); + finalizeHdr(*this); +} + + Mat::Mat(const Mat& m, const Range* ranges) : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&rows) diff --git a/modules/core/src/umatrix.cpp b/modules/core/src/umatrix.cpp index 997ee2b6ef..a543595b8b 100644 --- a/modules/core/src/umatrix.cpp +++ b/modules/core/src/umatrix.cpp @@ -386,6 +386,11 @@ void UMat::create(int d, const int* _sizes, int _type, UMatUsageFlags _usageFlag addref(); } +void UMat::create(const std::vector& _sizes, int _type, UMatUsageFlags _usageFlags) +{ + create((int)_sizes.size(), _sizes.data(), _type, _usageFlags); +} + void UMat::copySize(const UMat& m) { setSize(*this, m.dims, 0, 0); From 4e7b5214387e74b8df1c847d64b68e4bb3876d91 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 15 Dec 2016 14:17:38 +0300 Subject: [PATCH 231/409] highgui: change waitKey() default behaviour The old behaviour is available via waitKeyEx() call or via setting of OPENCV_LEGACY_WAITKEY environment variable --- modules/highgui/include/opencv2/highgui.hpp | 9 +++++++++ modules/highgui/src/window.cpp | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/highgui/include/opencv2/highgui.hpp b/modules/highgui/include/opencv2/highgui.hpp index 2f005da5cf..16ef8c4a9e 100644 --- a/modules/highgui/include/opencv2/highgui.hpp +++ b/modules/highgui/include/opencv2/highgui.hpp @@ -320,6 +320,15 @@ CV_EXPORTS_W void destroyAllWindows(); CV_EXPORTS_W int startWindowThread(); +/** @brief Similar to #waitKey, but returns full key code. + +@note + +Key code is implementation specific and depends on used backend: QT/GTK/Win32/etc + +*/ +CV_EXPORTS_W int waitKeyEx(int delay = 0); + /** @brief Waits for a pressed key. The function waitKey waits for a key event infinitely (when \f$\texttt{delay}\leq 0\f$ ) or for delay diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index f9496f5335..9306f8ef5e 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -201,11 +201,26 @@ double cv::getWindowProperty(const String& winname, int prop_id) return cvGetWindowProperty(winname.c_str(), prop_id); } -int cv::waitKey(int delay) +int cv::waitKeyEx(int delay) { return cvWaitKey(delay); } +int cv::waitKey(int delay) +{ + int code = waitKeyEx(delay); +#ifndef HAVE_WINRT + static int use_legacy = -1; + if (use_legacy < 0) + { + use_legacy = getenv("OPENCV_LEGACY_WAITKEY") != NULL ? 1 : 0; + } + if (use_legacy > 0) + return code; +#endif + return code & 0xff; +} + int cv::createTrackbar(const String& trackbarName, const String& winName, int* value, int count, TrackbarCallback callback, void* userdata) From 45d3286168a089fce446a13daefddc708a5e458a Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Fri, 2 Dec 2016 13:52:25 +0300 Subject: [PATCH 232/409] wrappers for vx_pyramid added --- 3rdparty/openvx/include/ivx.hpp | 77 +++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index c18723b278..abc71e048f 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -343,6 +343,15 @@ template <> struct RefTypeTraits static vx_status release(vxType& ref) { return vxReleaseLUT(&ref); } }; +class Pyramid; +template <> struct RefTypeTraits +{ + typedef vx_pyramid vxType; + typedef Pyramid wrapperType; + static const vx_enum vxTypeEnum = VX_TYPE_PYRAMID; + static vx_status release(vxType& ref) { return vxReleasePyramid(&ref); } +}; + class Distribution; template <> struct RefTypeTraits { @@ -2755,6 +2764,74 @@ public: #endif //IVX_USE_OPENCV }; +/* + * Pyramid + */ +class Pyramid : public RefWrapper +{ +public: + IVX_REF_STD_CTORS_AND_ASSIGNMENT(Pyramid) + + static Pyramid create(vx_context context, vx_size levels, vx_float32 scale, + vx_uint32 width, vx_uint32 height, vx_df_image format) + {return Pyramid(vxCreatePyramid(context, levels, scale, width, height, format));} + + static Pyramid createVirtual(vx_graph graph, vx_size levels, vx_float32 scale, + vx_uint32 width, vx_uint32 height, vx_df_image format) + {return Pyramid(vxCreateVirtualPyramid(graph, levels, scale, width, height, format));} + +#ifndef VX_VERSION_1_1 + static const vx_enum + VX_PYRAMID_LEVELS = VX_PYRAMID_ATTRIBUTE_LEVELS, + VX_PYRAMID_SCALE = VX_PYRAMID_ATTRIBUTE_SCALE, + VX_PYRAMID_WIDTH = VX_PYRAMID_ATTRIBUTE_WIDTH, + VX_PYRAMID_HEIGHT = VX_PYRAMID_ATTRIBUTE_HEIGHT, + VX_PYRAMID_FORMAT = VX_PYRAMID_ATTRIBUTE_FORMAT; +#endif + + template + void query(vx_enum att, T& value) const + { IVX_CHECK_STATUS( vxQueryPyramid(ref, att, &value, sizeof(value)) ); } + + vx_size levels() const + { + vx_size l; + query(VX_PYRAMID_LEVELS, l); + return l; + } + + vx_float32 scale() const + { + vx_float32 s; + query(VX_PYRAMID_SCALE, s); + return s; + } + + vx_uint32 width() const + { + vx_uint32 v; + query(VX_PYRAMID_WIDTH, v); + return v; + } + + vx_uint32 height() const + { + vx_uint32 v; + query(VX_PYRAMID_HEIGHT, v); + return v; + } + + vx_df_image format() const + { + vx_df_image f; + query(VX_PYRAMID_FORMAT, f); + return f; + } + + Image getLevel(vx_uint32 index) + { return Image(vxGetPyramidLevel(ref, index)); } +}; + /* * Distribution */ From fcddfa4f8605c7dbab8a0b8d88fcec312a74f7de Mon Sep 17 00:00:00 2001 From: Cartucho Date: Thu, 15 Dec 2016 12:32:12 +0000 Subject: [PATCH 233/409] GSoC 2016 - Adding ALIASES for tutorial (#7041) * GSoC 2016 - Adding toggle files to be used by tutorials. Add a toggle option for tutorials. * adds a button on the HTML tutorial pages to switch between blocks * the default option is for languages: one can write a block for C++ and another one for Python without re-writing the tutorial Add aliases to the doxyfile. * adding alises to make a link to previous and next tutorial. * adding alias to specify the toggle options in the tutorials index. * adding alias to add a youtube video directly from link. Add a sample tutorial (mat_mask_opertaions) using the developed aliases: * youtube alias * previous and next tutorial alias * buttons * languages info for tutorial table of content * code referances with snippets (and associated sample code files) * Removing the automatic ordering. Adding specific toggles for cpp, java and python. Move all the code to the footer / header and Doxyfile. Updating documentation. --- doc/Doxyfile.in | 11 +- doc/footer.html | 69 ++++++ doc/header.html | 30 +++ doc/mymath.js | 3 +- .../mat_mask_operations.markdown | 198 +++++++++++------- .../core/table_of_content_core.markdown | 2 + .../documentation_tutorial.markdown | 100 ++++++++- .../mat_mask_operations.cpp | 16 ++ .../documentation/documentation.cpp | 14 ++ .../MatMaskOperations.java | 139 ++++++++++++ .../documentation/Documentation.java | 9 + .../mat_mask_operations.py | 57 +++++ .../documentation/documentation.py | 5 + 13 files changed, 564 insertions(+), 89 deletions(-) create mode 100644 samples/cpp/tutorial_code/introduction/documentation/documentation.cpp create mode 100644 samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java create mode 100644 samples/java/tutorial_code/introduction/documentation/Documentation.java create mode 100644 samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py create mode 100644 samples/python/tutorial_code/introduction/documentation/documentation.py diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index f0c15e01b0..2e4ac7066a 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -31,10 +31,17 @@ MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 4 -ALIASES = +ALIASES += add_toggle{1}="@htmlonly[block]
\1
@endhtmlonly" +ALIASES += add_toggle_cpp="@htmlonly[block]
::type; + try { Context context = Context::create(); - Image srcImage = Image::createFromHandle(context, Image::matTypeToFormat(DataType::type), + + // Other conversions are marked as "experimental" + if(context.vendorID() == VX_ID_KHRONOS && + !(srcType == CV_8U && dstType == CV_16S) && + !(srcType == CV_16S && dstType == CV_8U)) + { + return false; + } + + Image srcImage = Image::createFromHandle(context, Image::matTypeToFormat(srcType), Image::createAddressing(imgSize.width, imgSize.height, (vx_uint32)sizeof(T), (vx_uint32)sstep), (void*)src); - Image dstImage = Image::createFromHandle(context, Image::matTypeToFormat(DataType
::type), + Image dstImage = Image::createFromHandle(context, Image::matTypeToFormat(dstType), Image::createAddressing(imgSize.width, imgSize.height, (vx_uint32)sizeof(DT), (vx_uint32)dstep), (void*)dst); From 328151c8a4b446ef7ce14e8f72c82637cd7e4bca Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Fri, 16 Dec 2016 23:38:47 +0300 Subject: [PATCH 264/409] don't run OpenVX pyrLK if user asked for err vector --- modules/video/src/lkpyramid.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/video/src/lkpyramid.cpp b/modules/video/src/lkpyramid.cpp index c23a670495..7c89bc31fe 100644 --- a/modules/video/src/lkpyramid.cpp +++ b/modules/video/src/lkpyramid.cpp @@ -1091,11 +1091,10 @@ namespace for(size_t i = 0; i < npoints; i++ ) status[i] = true; - Mat errMat; + // OpenVX doesn't return detection errors if( _err.needed() ) { - _err.create((int)npoints, 1, CV_32F, -1, true); - errMat = _err.getMat(); + return false; } try @@ -1184,8 +1183,6 @@ namespace vx_keypoint_t kp = vxNextPts[i]; nextPtsMat.at(i) = Point2f(kp.x, kp.y); statusMat.at(i) = (bool)kp.tracking_status; - // OpenVX doesn't return detection errors - errMat.at(i) = 0; } #ifdef VX_VERSION_1_1 From dd482c6a2936b57d4b588eb73832b9a7923f9fe7 Mon Sep 17 00:00:00 2001 From: jacobdang Date: Sat, 17 Dec 2016 21:48:29 +0800 Subject: [PATCH 265/409] bug fix for #7846 --- samples/gpu/video_writer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/gpu/video_writer.cpp b/samples/gpu/video_writer.cpp index 80d2cfc47b..6b9cca3dfa 100644 --- a/samples/gpu/video_writer.cpp +++ b/samples/gpu/video_writer.cpp @@ -11,6 +11,7 @@ #include "opencv2/cudacodec.hpp" #include "opencv2/highgui.hpp" +using namespace cv; int main(int argc, const char* argv[]) { if (argc != 2) From d71ec0cd1d5a6fe288cb8462e6ca9805798947a3 Mon Sep 17 00:00:00 2001 From: Sergiu Deitsch Date: Sun, 20 Nov 2016 14:36:34 +0100 Subject: [PATCH 266/409] cmake: support OPTIONAL_COMPONENTS in OpenCVConfig.cmake find_package allows to specify optional components. This way, the command will not fail if any of the components marked as optional was not found. This is useful in cases where components such as xfeatures2d, viz etc. are not available either because they were not compiled by the user or package maintainers decided to not provide the packages at all. The user can check the availability of the optional component using the OpenCV__FOUND variable. --- cmake/templates/OpenCVConfig.cmake.in | 51 ++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/cmake/templates/OpenCVConfig.cmake.in b/cmake/templates/OpenCVConfig.cmake.in index dfe9aeafe1..47751f55cf 100644 --- a/cmake/templates/OpenCVConfig.cmake.in +++ b/cmake/templates/OpenCVConfig.cmake.in @@ -14,6 +14,10 @@ # # find_package(OpenCV REQUIRED core videoio) # +# You can also mark OpenCV components as optional: + +# find_package(OpenCV REQUIRED core OPTIONAL_COMPONENTS viz) +# # If the module is found then OPENCV__FOUND is set to TRUE. # # This file will define the following variables: @@ -48,6 +52,21 @@ SET(OpenCV_VERSION_PATCH @OPENCV_VERSION_PATCH@) SET(OpenCV_VERSION_TWEAK 0) SET(OpenCV_VERSION_STATUS "@OPENCV_VERSION_STATUS@") +include(FindPackageHandleStandardArgs) + +if(NOT CMAKE_VERSION VERSION_LESS 2.8.8 + AND OpenCV_FIND_COMPONENTS # prevent excessive output +) + # HANDLE_COMPONENTS was introduced in CMake 2.8.8 + list(APPEND _OpenCV_FPHSA_ARGS HANDLE_COMPONENTS) + # The missing components will be handled by the FindPackageHandleStandardArgs + # module. + set(_OpenCV_HANDLE_COMPONENTS_MANUALLY FALSE) +else() + # The missing components will be handled by this config. + set(_OpenCV_HANDLE_COMPONENTS_MANUALLY TRUE) +endif() + # Extract directory name from full path of the file currently being processed. # Note that CMake 2.8.3 introduced CMAKE_CURRENT_LIST_DIR. We reimplement it # for older versions of CMake to support these as well. @@ -120,24 +139,33 @@ endif() set(OpenCV_WORLD_COMPONENTS @OPENCV_WORLD_MODULES@) # expand short module names and see if requested components exist -set(OpenCV_FIND_COMPONENTS_ "") foreach(__cvcomponent ${OpenCV_FIND_COMPONENTS}) + # Store the name of the original component so we can set the + # OpenCV__FOUND variable which can be checked by the user. + set (__original_cvcomponent ${__cvcomponent}) if(NOT __cvcomponent MATCHES "^opencv_") set(__cvcomponent opencv_${__cvcomponent}) endif() list(FIND OpenCV_LIB_COMPONENTS ${__cvcomponent} __cvcomponentIdx) if(__cvcomponentIdx LESS 0) - #requested component is not found... - if(OpenCV_FIND_REQUIRED) - message(FATAL_ERROR "${__cvcomponent} is required but was not found") - elseif(NOT OpenCV_FIND_QUIETLY) - message(WARNING "${__cvcomponent} is required but was not found") - endif() + if(_OpenCV_HANDLE_COMPONENTS_MANUALLY) + # Either the component is required or the user did not set any components at + # all. In the latter case, the OpenCV_FIND_REQUIRED_ variable + # will not be defined since it is not set by this config. So let's assume + # the implicitly set components are always required. + if(NOT DEFINED OpenCV_FIND_REQUIRED_${__original_cvcomponent} OR + OpenCV_FIND_REQUIRED_${__original_cvcomponent}) + message(FATAL_ERROR "${__cvcomponent} is required but was not found") + elseif(NOT OpenCV_FIND_QUIETLY) + # The component was marked as optional using OPTIONAL_COMPONENTS + message(WARNING "Optional component ${__cvcomponent} was not found") + endif() + endif(_OpenCV_HANDLE_COMPONENTS_MANUALLY) #indicate that module is NOT found string(TOUPPER "${__cvcomponent}" __cvcomponentUP) set(${__cvcomponentUP}_FOUND "${__cvcomponentUP}_FOUND-NOTFOUND") + set(OpenCV_${__original_cvcomponent}_FOUND FALSE) else() - list(APPEND OpenCV_FIND_COMPONENTS_ ${__cvcomponent}) # Not using list(APPEND) here, because OpenCV_LIBS may not exist yet. # Also not clearing OpenCV_LIBS anywhere, so that multiple calls # to find_package(OpenCV) with different component lists add up. @@ -145,6 +173,7 @@ foreach(__cvcomponent ${OpenCV_FIND_COMPONENTS}) #indicate that module is found string(TOUPPER "${__cvcomponent}" __cvcomponentUP) set(${__cvcomponentUP}_FOUND 1) + set(OpenCV_${__original_cvcomponent}_FOUND TRUE) endif() if(OpenCV_SHARED AND ";${OpenCV_WORLD_COMPONENTS};" MATCHES ";${__cvcomponent};" AND NOT TARGET ${__cvcomponent}) get_target_property(__implib_dbg opencv_world IMPORTED_IMPLIB_DEBUG) @@ -170,7 +199,6 @@ foreach(__cvcomponent ${OpenCV_FIND_COMPONENTS}) endif() endif() endforeach() -set(OpenCV_FIND_COMPONENTS ${OpenCV_FIND_COMPONENTS_}) # ============================================================== # Compatibility stuff @@ -226,3 +254,8 @@ macro(ocv_list_filterout lst regex) endif() endforeach() endmacro() + +# We do not actually need REQUIRED_VARS to be checked for. Just use the +# installation directory for the status. +find_package_handle_standard_args(OpenCV REQUIRED_VARS OpenCV_INSTALL_PATH + VERSION_VAR OpenCV_VERSION ${_OpenCV_FPHSA_ARGS}) From 35308bc45f48c75677b268dd1a2d9a082b7d71b0 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 19 Dec 2016 14:58:56 +0300 Subject: [PATCH 267/409] cmake: fix CMake error "install DIRECTORY does not allow "COMPONENT" after PATTERN or REGEX." --- cmake/OpenCVUtils.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index fe8dbcc87d..cdf257d5fe 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -1098,16 +1098,16 @@ endfunction() macro(ocv_add_testdata basedir dest_subdir) if(BUILD_TESTS) - cmake_parse_arguments(__TESTDATA "" "COMPONENT" "" ${ARGN}) if(NOT CMAKE_CROSSCOMPILING AND NOT INSTALL_TESTS) file(COPY ${basedir}/ DESTINATION ${CMAKE_BINARY_DIR}/${OPENCV_TEST_DATA_INSTALL_PATH}/${dest_subdir} - ${__TESTDATA_UNPARSED_ARGUMENTS} + ${ARGN} ) endif() if(INSTALL_TESTS) install(DIRECTORY ${basedir}/ DESTINATION ${OPENCV_TEST_DATA_INSTALL_PATH}/contrib/text + COMPONENT "tests" ${ARGN} ) endif() From 3e1695d6e3b4f4d6c00b61257d4ab75c185475a7 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 19 Dec 2016 00:22:16 +0300 Subject: [PATCH 268/409] cmake: fix ffmpeg check code --- cmake/OpenCVFindLibsVideo.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/OpenCVFindLibsVideo.cmake b/cmake/OpenCVFindLibsVideo.cmake index b2b74b7a3b..1eff03b581 100644 --- a/cmake/OpenCVFindLibsVideo.cmake +++ b/cmake/OpenCVFindLibsVideo.cmake @@ -236,6 +236,7 @@ if(WITH_FFMPEG) if(NOT __VALID_FFMPEG) #message(FATAL_ERROR "FFMPEG: test check build log:\n${TRY_OUT}") message(STATUS "WARNING: Can't build ffmpeg test code") + set(HAVE_FFMPEG FALSE) else() ocv_append_build_options(VIDEOIO FFMPEG) endif() From d85c11e525ba7decca80154fc1ed8a4482a47512 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 19 Dec 2016 17:12:18 +0300 Subject: [PATCH 269/409] OpenCV version++ 3.2.0-rc --- modules/core/include/opencv2/core/version.hpp | 4 ++-- .../src/java/android+OpenCVLoader.java | 14 +++++++++++++ platforms/android/build_sdk.py | 2 +- .../service/engine/AndroidManifest.xml | 4 ++-- .../opencv/engine/OpenCVEngineService.java | 2 +- platforms/android/service/readme.txt | 20 ++++++++++--------- platforms/maven/pom.xml | 2 +- 7 files changed, 32 insertions(+), 16 deletions(-) diff --git a/modules/core/include/opencv2/core/version.hpp b/modules/core/include/opencv2/core/version.hpp index cc03a095c6..1794dba4c0 100644 --- a/modules/core/include/opencv2/core/version.hpp +++ b/modules/core/include/opencv2/core/version.hpp @@ -51,9 +51,9 @@ #define OPENCV_VERSION_HPP #define CV_VERSION_MAJOR 3 -#define CV_VERSION_MINOR 1 +#define CV_VERSION_MINOR 2 #define CV_VERSION_REVISION 0 -#define CV_VERSION_STATUS "-dev" +#define CV_VERSION_STATUS "-rc" #define CVAUX_STR_EXP(__A) #__A #define CVAUX_STR(__A) CVAUX_STR_EXP(__A) diff --git a/modules/java/generator/src/java/android+OpenCVLoader.java b/modules/java/generator/src/java/android+OpenCVLoader.java index b8a9705923..a84a49e6b4 100644 --- a/modules/java/generator/src/java/android+OpenCVLoader.java +++ b/modules/java/generator/src/java/android+OpenCVLoader.java @@ -57,6 +57,16 @@ public class OpenCVLoader */ public static final String OPENCV_VERSION_2_4_11 = "2.4.11"; + /** + * OpenCV Library version 2.4.12. + */ + public static final String OPENCV_VERSION_2_4_12 = "2.4.12"; + + /** + * OpenCV Library version 2.4.13. + */ + public static final String OPENCV_VERSION_2_4_13 = "2.4.13"; + /** * OpenCV Library version 3.0.0. */ @@ -67,6 +77,10 @@ public class OpenCVLoader */ public static final String OPENCV_VERSION_3_1_0 = "3.1.0"; + /** + * OpenCV Library version 3.2.0. + */ + public static final String OPENCV_VERSION_3_2_0 = "3.2.0"; /** * Loads and initializes OpenCV library from current application package. Roughly, it's an analog of system.loadLibrary("opencv_java"). diff --git a/platforms/android/build_sdk.py b/platforms/android/build_sdk.py index 7d52eab9c0..e78e3b92fb 100755 --- a/platforms/android/build_sdk.py +++ b/platforms/android/build_sdk.py @@ -171,7 +171,7 @@ class Builder: # Add extra data apkxmldest = check_dir(os.path.join(apkdest, "res", "xml"), create=True) apklibdest = check_dir(os.path.join(apkdest, "libs", abi.name), create=True) - for ver, d in self.extra_packs + [("3.1.0", os.path.join(self.libdest, "lib"))]: + for ver, d in self.extra_packs + [("3.2.0", os.path.join(self.libdest, "lib"))]: r = ET.Element("library", attrib={"version": ver}) log.info("Adding libraries from %s", d) diff --git a/platforms/android/service/engine/AndroidManifest.xml b/platforms/android/service/engine/AndroidManifest.xml index ef1cab2a0e..6c90858d6d 100644 --- a/platforms/android/service/engine/AndroidManifest.xml +++ b/platforms/android/service/engine/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="320@ANDROID_PLATFORM_ID@" + android:versionName="3.20"> diff --git a/platforms/android/service/engine/src/org/opencv/engine/OpenCVEngineService.java b/platforms/android/service/engine/src/org/opencv/engine/OpenCVEngineService.java index 917a64111e..c604fa9003 100644 --- a/platforms/android/service/engine/src/org/opencv/engine/OpenCVEngineService.java +++ b/platforms/android/service/engine/src/org/opencv/engine/OpenCVEngineService.java @@ -134,7 +134,7 @@ public class OpenCVEngineService extends Service { @Override public int getEngineVersion() throws RemoteException { - int version = 3100; + int version = 3200; try { version = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; } catch (NameNotFoundException e) { diff --git a/platforms/android/service/readme.txt b/platforms/android/service/readme.txt index 82cac62553..f7e886afc3 100644 --- a/platforms/android/service/readme.txt +++ b/platforms/android/service/readme.txt @@ -10,14 +10,16 @@ from Google Play. If Google Play is not available (i.e. on emulator, developer board, etc), you can install it manually using adb tool: - adb install /apk/OpenCV_3.1.0_Manager_3.10_.apk + adb install /apk/OpenCV__Manager__.apk -Use the list below to determine proper OpenCV Manager package for your device: +Example: OpenCV_3.2.0-dev_Manager_3.20_armeabi-v7a.apk -- OpenCV_3.1.0-dev_Manager_3.10_armeabi.apk - armeabi (ARMv5, ARMv6) -- OpenCV_3.1.0-dev_Manager_3.10_armeabi-v7a.apk - armeabi-v7a (ARMv7-A + NEON) -- OpenCV_3.1.0-dev_Manager_3.10_arm64-v8a.apk - arm64-v8a (ARM64-v8a) -- OpenCV_3.1.0-dev_Manager_3.10_mips.apk - mips (MIPS) -- OpenCV_3.1.0-dev_Manager_3.10_mips64.apk - mips64 (MIPS64) -- OpenCV_3.1.0-dev_Manager_3.10_x86.apk - x86 -- OpenCV_3.1.0-dev_Manager_3.10_x86_64.apk - x86_64 +Use the list of platforms below to determine proper OpenCV Manager package for your device: + +- armeabi (ARMv5, ARMv6) +- armeabi-v7a (ARMv7-A + NEON) +- arm64-v8a +- mips +- mips64 +- x86 +- x86_64 diff --git a/platforms/maven/pom.xml b/platforms/maven/pom.xml index 8314ab9f58..d070acf8ce 100644 --- a/platforms/maven/pom.xml +++ b/platforms/maven/pom.xml @@ -4,7 +4,7 @@ opencv.org opencv bundle - 3.1.0 + 3.2.0 OpenCV From bbfbdd9da1c9cb8247d1e6fee23139a3cd2fb900 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Fri, 18 Nov 2016 10:40:47 +0300 Subject: [PATCH 270/409] moving hal to a separate folder --- 3rdparty/openvx/CMakeLists.txt | 7 ++++--- 3rdparty/openvx/hal/README.md | 4 ++++ 3rdparty/openvx/{src => hal}/openvx_hal.cpp | 0 3rdparty/openvx/{include => hal}/openvx_hal.hpp | 0 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 3rdparty/openvx/hal/README.md rename 3rdparty/openvx/{src => hal}/openvx_hal.cpp (100%) rename 3rdparty/openvx/{include => hal}/openvx_hal.hpp (100%) diff --git a/3rdparty/openvx/CMakeLists.txt b/3rdparty/openvx/CMakeLists.txt index 4c71ecfe6b..699cf91617 100644 --- a/3rdparty/openvx/CMakeLists.txt +++ b/3rdparty/openvx/CMakeLists.txt @@ -1,5 +1,6 @@ -add_library(openvx_hal STATIC src/openvx_hal.cpp include/openvx_hal.hpp include/ivx.hpp include/ivx_lib_debug.hpp) +add_library(openvx_hal STATIC hal/openvx_hal.cpp hal/openvx_hal.hpp include/ivx.hpp include/ivx_lib_debug.hpp) target_include_directories(openvx_hal PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/hal ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/modules/core/include ${CMAKE_SOURCE_DIR}/modules/imgproc/include @@ -14,5 +15,5 @@ endif() set(OPENVX_HAL_FOUND TRUE PARENT_SCOPE) set(OPENVX_HAL_VERSION 0.0.1 PARENT_SCOPE) set(OPENVX_HAL_LIBRARIES "openvx_hal" PARENT_SCOPE) -set(OPENVX_HAL_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/openvx_hal.hpp" PARENT_SCOPE) -set(OPENVX_HAL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" "${OPENVX_INCLUDE_DIR}" PARENT_SCOPE) +set(OPENVX_HAL_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/hal/openvx_hal.hpp" PARENT_SCOPE) +set(OPENVX_HAL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/hal" "${OPENVX_INCLUDE_DIR}" PARENT_SCOPE) diff --git a/3rdparty/openvx/hal/README.md b/3rdparty/openvx/hal/README.md new file mode 100644 index 0000000000..a4266015a7 --- /dev/null +++ b/3rdparty/openvx/hal/README.md @@ -0,0 +1,4 @@ +#OpenVX-based HAL implementation. +It's built when OpenVX is available (`HAVE_OPENVX`). +To build OpenCV with OpenVX support add the following **cmake** options: +`-DOPENVX_ROOT=/path/to/prebuilt/openvx -DWITH_OPENVX=YES` \ No newline at end of file diff --git a/3rdparty/openvx/src/openvx_hal.cpp b/3rdparty/openvx/hal/openvx_hal.cpp similarity index 100% rename from 3rdparty/openvx/src/openvx_hal.cpp rename to 3rdparty/openvx/hal/openvx_hal.cpp diff --git a/3rdparty/openvx/include/openvx_hal.hpp b/3rdparty/openvx/hal/openvx_hal.hpp similarity index 100% rename from 3rdparty/openvx/include/openvx_hal.hpp rename to 3rdparty/openvx/hal/openvx_hal.hpp From 2312131e274a0409e6f6f7f194c2007edab2b6c6 Mon Sep 17 00:00:00 2001 From: apavlenko Date: Mon, 21 Nov 2016 13:16:56 +0300 Subject: [PATCH 271/409] moving HAL-related CMakeLists.txt to 'hal' sub-dir --- 3rdparty/openvx/CMakeLists.txt | 22 +++++----------------- 3rdparty/openvx/hal/CMakeLists.txt | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 3rdparty/openvx/hal/CMakeLists.txt diff --git a/3rdparty/openvx/CMakeLists.txt b/3rdparty/openvx/CMakeLists.txt index 699cf91617..41fd377d70 100644 --- a/3rdparty/openvx/CMakeLists.txt +++ b/3rdparty/openvx/CMakeLists.txt @@ -1,19 +1,7 @@ -add_library(openvx_hal STATIC hal/openvx_hal.cpp hal/openvx_hal.hpp include/ivx.hpp include/ivx_lib_debug.hpp) -target_include_directories(openvx_hal PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/hal - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/modules/core/include - ${CMAKE_SOURCE_DIR}/modules/imgproc/include - ${OPENVX_INCLUDE_DIR}) -target_link_libraries(openvx_hal LINK_PUBLIC ${OPENVX_LIBRARIES}) -set_target_properties(openvx_hal PROPERTIES POSITION_INDEPENDENT_CODE TRUE) -set_target_properties(openvx_hal PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${3P_LIBRARY_OUTPUT_PATH}) -if(NOT BUILD_SHARED_LIBS) - ocv_install_target(openvx_hal EXPORT OpenCVModules ARCHIVE DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev) +if(NOT HAVE_OPENVX) + message(STATUS "OpenVX is not available, disabling openvx-related HAL and stuff") + return() endif() -set(OPENVX_HAL_FOUND TRUE PARENT_SCOPE) -set(OPENVX_HAL_VERSION 0.0.1 PARENT_SCOPE) -set(OPENVX_HAL_LIBRARIES "openvx_hal" PARENT_SCOPE) -set(OPENVX_HAL_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/hal/openvx_hal.hpp" PARENT_SCOPE) -set(OPENVX_HAL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/hal" "${OPENVX_INCLUDE_DIR}" PARENT_SCOPE) +set(OPENCV_3P_OPENVX_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +add_subdirectory(hal) \ No newline at end of file diff --git a/3rdparty/openvx/hal/CMakeLists.txt b/3rdparty/openvx/hal/CMakeLists.txt new file mode 100644 index 0000000000..519d8d731f --- /dev/null +++ b/3rdparty/openvx/hal/CMakeLists.txt @@ -0,0 +1,19 @@ +add_library(openvx_hal STATIC openvx_hal.cpp openvx_hal.hpp ${OPENCV_3P_OPENVX_DIR}/include/ivx.hpp ${OPENCV_3P_OPENVX_DIR}/include/ivx_lib_debug.hpp) +target_include_directories(openvx_hal PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${OPENCV_3P_OPENVX_DIR}/include + ${CMAKE_SOURCE_DIR}/modules/core/include + ${CMAKE_SOURCE_DIR}/modules/imgproc/include + ${OPENVX_INCLUDE_DIR}) +target_link_libraries(openvx_hal LINK_PUBLIC ${OPENVX_LIBRARIES}) +set_target_properties(openvx_hal PROPERTIES POSITION_INDEPENDENT_CODE TRUE) +set_target_properties(openvx_hal PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${3P_LIBRARY_OUTPUT_PATH}) +if(NOT BUILD_SHARED_LIBS) + ocv_install_target(openvx_hal EXPORT OpenCVModules ARCHIVE DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev) +endif() + +set(OPENVX_HAL_FOUND TRUE CACHE INTERNAL "") +set(OPENVX_HAL_VERSION 0.0.1 CACHE INTERNAL "") +set(OPENVX_HAL_LIBRARIES "openvx_hal" CACHE INTERNAL "") +set(OPENVX_HAL_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/openvx_hal.hpp" CACHE INTERNAL "") +set(OPENVX_HAL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}" "${OPENCV_3P_OPENVX_DIR}/include" "${OPENVX_INCLUDE_DIR}" CACHE INTERNAL "") From 0d32bec5c3550557cf71a1f34192490baa557cae Mon Sep 17 00:00:00 2001 From: Kevin Hughes Date: Sat, 17 Dec 2016 13:54:55 -0500 Subject: [PATCH 272/409] add the type_id to the old save format --- apps/traincascade/cascadeclassifier.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/traincascade/cascadeclassifier.cpp b/apps/traincascade/cascadeclassifier.cpp index 9c24feb49a..ebfb43e6c3 100644 --- a/apps/traincascade/cascadeclassifier.cpp +++ b/apps/traincascade/cascadeclassifier.cpp @@ -412,6 +412,7 @@ bool CvCascadeClassifier::readStages( const FileNode &node) } // For old Haar Classifier file saving +#define ICV_HAAR_TYPE_ID "opencv-haar-classifier" #define ICV_HAAR_SIZE_NAME "size" #define ICV_HAAR_STAGES_NAME "stages" #define ICV_HAAR_TREES_NAME "trees" @@ -434,11 +435,12 @@ void CvCascadeClassifier::save( const string filename, bool baseFormat ) if ( !fs.isOpened() ) return; - fs << FileStorage::getDefaultObjectName(filename) << "{"; + fs << FileStorage::getDefaultObjectName(filename); if ( !baseFormat ) { Mat featureMap; getUsedFeaturesIdxMap( featureMap ); + fs << "{"; writeParams( fs ); fs << CC_STAGE_NUM << (int)stageClassifiers.size(); writeStages( fs, featureMap ); @@ -450,6 +452,7 @@ void CvCascadeClassifier::save( const string filename, bool baseFormat ) CvSeq* weak; if ( cascadeParams.featureType != CvFeatureParams::HAAR ) CV_Error( CV_StsBadFunc, "old file format is used for Haar-like features only"); + fs << "{:" ICV_HAAR_TYPE_ID; fs << ICV_HAAR_SIZE_NAME << "[:" << cascadeParams.winSize.width << cascadeParams.winSize.height << "]"; fs << ICV_HAAR_STAGES_NAME << "["; From b3900c5dfebfe409dad48fdc7a294901c8960bfa Mon Sep 17 00:00:00 2001 From: Peter Entwistle Date: Mon, 19 Dec 2016 23:22:08 +0000 Subject: [PATCH 273/409] Fixed broken links in the cascade classifier tutorial --- .../objdetect/cascade_classifier/cascade_classifier.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tutorials/objdetect/cascade_classifier/cascade_classifier.markdown b/doc/tutorials/objdetect/cascade_classifier/cascade_classifier.markdown index 3953635193..6589a1c01a 100644 --- a/doc/tutorials/objdetect/cascade_classifier/cascade_classifier.markdown +++ b/doc/tutorials/objdetect/cascade_classifier/cascade_classifier.markdown @@ -18,9 +18,9 @@ Code ---- This tutorial code's is shown lines below. You can also download it from -[here](https://github.com/opencv/tree/master/samples/cpp/tutorial_code/objectDetection/objectDetection.cpp) +[here](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/objectDetection/objectDetection.cpp) . The second version (using LBP for face detection) can be [found -here](https://github.com/opencv/tree/master/samples/cpp/tutorial_code/objectDetection/objectDetection2.cpp) +here](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/objectDetection/objectDetection2.cpp) @include samples/cpp/tutorial_code/objectDetection/objectDetection.cpp Explanation From 40686b5e87a1a4d2b96c1a09d249cf58fc819d7d Mon Sep 17 00:00:00 2001 From: Pavel Rojtberg Date: Tue, 20 Dec 2016 10:22:32 +0100 Subject: [PATCH 274/409] initUndistortRectifyMap: CV_32FC2 is also supported as m1type --- modules/imgproc/include/opencv2/imgproc.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 007a238d67..176017365b 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -2920,7 +2920,7 @@ computed by stereoRectify can be passed here. If the matrix is empty, the identi is assumed. In cvInitUndistortMap R assumed to be an identity matrix. @param newCameraMatrix New camera matrix \f$A'=\vecthreethree{f_x'}{0}{c_x'}{0}{f_y'}{c_y'}{0}{0}{1}\f$. @param size Undistorted image size. -@param m1type Type of the first output map that can be CV_32FC1 or CV_16SC2, see cv::convertMaps +@param m1type Type of the first output map that can be CV_32FC1, CV_32FC2 or CV_16SC2, see cv::convertMaps @param map1 The first output map. @param map2 The second output map. */ From fdbc598e4cb9b1cde5d05b7719944f996f971354 Mon Sep 17 00:00:00 2001 From: StevenPuttemans Date: Wed, 21 Dec 2016 10:49:03 +0100 Subject: [PATCH 275/409] add new frontal face detection model, VISAPP2017 contribution --- .../lbpcascade_frontalface_improved.xml | 1469 +++++++++++++++++ 1 file changed, 1469 insertions(+) create mode 100644 data/lbpcascades/lbpcascade_frontalface_improved.xml diff --git a/data/lbpcascades/lbpcascade_frontalface_improved.xml b/data/lbpcascades/lbpcascade_frontalface_improved.xml new file mode 100644 index 0000000000..77eb0f64de --- /dev/null +++ b/data/lbpcascades/lbpcascade_frontalface_improved.xml @@ -0,0 +1,1469 @@ + + + + + + BOOST + LBP + 45 + 45 + + GAB + 9.9500000476837158e-001 + 5.0000000000000000e-001 + 9.4999999999999996e-001 + 1 + 100 + + 256 + 1 + 19 + + + <_> + 6 + -4.1617846488952637e+000 + + <_> + + 0 -1 26 -1 -1 -17409 -1 -1 -1 -1 -1 + + -9.9726462364196777e-001 -3.8938775658607483e-001 + <_> + + 0 -1 18 -1 -1 -21569 -20545 -1 -1 -20545 -1 + + -9.8648911714553833e-001 -2.5386649370193481e-001 + <_> + + 0 -1 30 -21569 -16449 1006578219 -20801 -16449 -1 -21585 -1 + + -9.6436238288879395e-001 -1.4039695262908936e-001 + <_> + + 0 -1 54 -1 -1 -16402 -4370 -1 -1 -1053010 -4456466 + + -8.4081345796585083e-001 3.8321062922477722e-001 + <_> + + 0 -1 29 -184747280 -705314819 1326353 1364574079 -131073 -5 + 2147481147 -1 + + -8.1084597110748291e-001 4.3495711684226990e-001 + <_> + + 0 -1 89 -142618625 -4097 -37269 -20933 872350430 -268476417 + 1207894255 2139032115 + + -7.3140043020248413e-001 4.3799084424972534e-001 + + <_> + 6 + -4.0652265548706055e+000 + + <_> + + 0 -1 19 -1 -1 -17409 -1 -1 -1 -1 -1 + + -9.9727255105972290e-001 -7.2050148248672485e-001 + <_> + + 0 -1 38 -1 1073741823 -1 -1 -1 -1 -1 -1 + + -9.8717331886291504e-001 -5.3031939268112183e-001 + <_> + + 0 -1 28 -16385 -1 -21569 -20545 -1 -1 -21569 -1 + + -9.3442338705062866e-001 6.5213099122047424e-002 + <_> + + 0 -1 112 -2097153 -1 -1 -1 -1 -8193 -1 -35467 + + -7.9567342996597290e-001 4.2883640527725220e-001 + <_> + + 0 -1 48 -134239573 -16465 58663467 -1079022929 -1073758273 + -81937 -8412501 -404766817 + + -7.1264797449111938e-001 4.1050794720649719e-001 + <_> + + 0 -1 66 -17047555 -1099008003 2147479551 -1090584581 -69633 + -1342177281 -1090650121 -1472692240 + + -7.6119172573089600e-001 4.2042696475982666e-001 + + <_> + 7 + -4.6904473304748535e+000 + + <_> + + 0 -1 12 -1 -1 -17409 -1 -1 -1 -1 -1 + + -9.9725550413131714e-001 -8.3142280578613281e-001 + <_> + + 0 -1 31 -1 -168429569 -1 -1 -1 -1 -1 -1 + + -9.8183268308639526e-001 -3.6373397707939148e-001 + <_> + + 0 -1 38 -1 1073741759 -1 -1 -1 -1 -1 -1 + + -9.1890293359756470e-001 7.8322596848011017e-002 + <_> + + 0 -1 27 -17409 -2097153 -134372726 -21873 -65 -536870913 + -161109 -4215889 + + -8.0752444267272949e-001 1.9565649330615997e-001 + <_> + + 0 -1 46 -469779457 -286371842 -33619971 -212993 -1 -41943049 + -134217731 -1346863620 + + -6.9232726097106934e-001 3.8141927123069763e-001 + <_> + + 0 -1 125 -1896950780 -1964839052 -9 707723004 -34078727 + -1074266122 -536872969 -262145 + + -8.1760478019714355e-001 3.4172961115837097e-001 + <_> + + 0 -1 80 -402657501 654311423 -419533278 -452984853 + 1979676215 -1208090625 -167772569 -524289 + + -6.3433408737182617e-001 4.3154156208038330e-001 + + <_> + 8 + -4.2590322494506836e+000 + + <_> + + 0 -1 42 -1 -655361 -1 -1 -1 -1 -1 -1 + + -9.9715477228164673e-001 -8.6178696155548096e-001 + <_> + + 0 -1 40 -1 -705300491 -1 -1 -1 -1 -1 -1 + + -9.8356908559799194e-001 -5.7423096895217896e-001 + <_> + + 0 -1 43 -65 872413111 -2049 -1 -1 -1 -1 -1 + + -9.2525935173034668e-001 -1.3835857808589935e-001 + <_> + + 0 -1 111 -1 -5242881 -1 -524289 -4194305 -1 -1 -43148 + + -7.8076487779617310e-001 1.8362471461296082e-001 + <_> + + 0 -1 25 -145227841 868203194 -1627394049 935050171 + 2147483647 1006600191 -268439637 1002437615 + + -7.2554033994674683e-001 3.3393219113349915e-001 + <_> + + 0 -1 116 -214961408 50592514 -2128 1072162674 -1077940293 + -1084489966 -134219854 -1074790401 + + -6.1547595262527466e-001 3.9214438199996948e-001 + <_> + + 0 -1 3 -294987948 -1124421633 -73729 -268435841 -33654928 + 2122317823 -268599297 -33554945 + + -6.4863425493240356e-001 3.8784855604171753e-001 + <_> + + 0 -1 22 -525585 -26738821 -17895690 1123482236 1996455758 + -8519849 -252182980 -461898753 + + -5.5464369058609009e-001 4.4275921583175659e-001 + + <_> + 8 + -4.0009465217590332e+000 + + <_> + + 0 -1 82 -1 -1 -1 -1 -33685505 -1 -1 -1 + + -9.9707120656967163e-001 -8.9196771383285522e-001 + <_> + + 0 -1 84 -1 -1 -1 -1 2147446783 -1 -1 -1 + + -9.8670446872711182e-001 -7.5064390897750854e-001 + <_> + + 0 -1 79 -1 -1 -262145 -1 -252379137 -1 -1 -1 + + -8.9446705579757690e-001 7.0268943905830383e-002 + <_> + + 0 -1 61 -1 -8201 -1 -2097153 -16777217 -513 -16777217 + -1162149889 + + -7.2166109085083008e-001 2.9786801338195801e-001 + <_> + + 0 -1 30 -21569 -1069121 1006578211 -134238545 -16450 + -268599297 -21617 -14680097 + + -6.2449234724044800e-001 3.8551881909370422e-001 + <_> + + 0 -1 75 -268701913 -1999962377 1995165474 -453316822 + 1744684853 -2063597697 -134226057 -50336769 + + -5.5207914113998413e-001 4.2211884260177612e-001 + <_> + + 0 -1 21 -352321825 -526489 -420020626 -486605074 1155483470 + -110104705 -587840772 -25428801 + + -5.3324747085571289e-001 4.4535955786705017e-001 + <_> + + 0 -1 103 70270772 2012790229 -16810020 -245764 -1208090635 + -753667 -1073741828 -1363662420 + + -6.4402890205383301e-001 3.8995954394340515e-001 + + <_> + 8 + -4.6897511482238770e+000 + + <_> + + 0 -1 97 -1 -1 -1 -1 -524289 -524289 -1 -1 + + -9.9684870243072510e-001 -8.8232177495956421e-001 + <_> + + 0 -1 84 -1 -1 -1 -1 2147438591 -1 -1 -1 + + -9.8677414655685425e-001 -7.8965580463409424e-001 + <_> + + 0 -1 113 -1 -1 -1 -1 -1048577 -262149 -1048577 -35339 + + -9.2621946334838867e-001 -2.9984828829765320e-001 + <_> + + 0 -1 33 -2249 867434291 -32769 -33562753 -1 -1073758209 + -4165 -1 + + -7.2429555654525757e-001 2.2348840534687042e-001 + <_> + + 0 -1 98 1659068671 -142606337 587132538 -67108993 577718271 + -294921 -134479873 -129 + + -5.5495566129684448e-001 3.5419258475303650e-001 + <_> + + 0 -1 100 -268441813 788267007 -286265494 -486576145 -8920251 + 2138505075 -151652570 -2050 + + -5.3362584114074707e-001 3.9479774236679077e-001 + <_> + + 0 -1 51 -1368387212 -537102978 -98305 -163843 1065109500 + -16777217 -67321939 -1141359619 + + -5.6162708997726440e-001 3.8008108735084534e-001 + <_> + + 0 -1 127 -268435550 1781120906 -251658720 -143130698 + -1048605 -1887436825 1979700688 -1008730125 + + -5.1167154312133789e-001 4.0678605437278748e-001 + + <_> + 10 + -4.2179841995239258e+000 + + <_> + + 0 -1 97 -1 -1 -1 -1 -524289 -524289 -1 -1 + + -9.9685418605804443e-001 -8.8037383556365967e-001 + <_> + + 0 -1 90 -1 -1 -1 -1 -8912897 -524297 -8912897 -1 + + -9.7972750663757324e-001 -5.7626229524612427e-001 + <_> + + 0 -1 96 -1 -1 -1 -1 -1 -65 -1 -2249 + + -9.0239793062210083e-001 -1.7454113066196442e-001 + <_> + + 0 -1 71 -1 -4097 -1 -513 -16777217 -268468483 -16797697 + -1430589697 + + -7.4346423149108887e-001 9.4165161252021790e-002 + <_> + + 0 -1 37 1364588304 -581845274 -536936460 -3 -308936705 + -1074331649 -4196865 -134225953 + + -6.8877440690994263e-001 2.7647304534912109e-001 + <_> + + 0 -1 117 -37765187 -540675 -3 -327753 -1082458115 -65537 + 1071611901 536827253 + + -5.7555085420608521e-001 3.4339720010757446e-001 + <_> + + 0 -1 85 -269490650 -1561395522 -1343312090 -857083986 + -1073750223 -369098755 -50856110 -2065 + + -5.4036927223205566e-001 4.0065473318099976e-001 + <_> + + 0 -1 4 -425668880 -34427164 1879048177 -269570140 790740912 + -196740 2138535839 -536918145 + + -4.8439365625381470e-001 4.4630467891693115e-001 + <_> + + 0 -1 92 74726960 -1246482434 -1 -246017 -1078607916 + -1073947163 -1644231687 -1359211496 + + -5.6686979532241821e-001 3.6671569943428040e-001 + <_> + + 0 -1 11 -135274809 -1158173459 -353176850 540195262 + 2139086600 2071977814 -546898600 -96272673 + + -5.1499199867248535e-001 4.0788397192955017e-001 + + <_> + 9 + -4.0345416069030762e+000 + + <_> + + 0 -1 78 -1 -1 -1 -1 -8912897 -1 -8912897 -1 + + -9.9573624134063721e-001 -8.5452395677566528e-001 + <_> + + 0 -1 93 -1 -1 -1 -1 -148635649 -524297 -8912897 -1 + + -9.7307401895523071e-001 -5.2884924411773682e-001 + <_> + + 0 -1 77 -1 -8209 -1 -257 -772734977 -1 -201850881 -1 + + -8.6225658655166626e-001 4.3712578713893890e-002 + <_> + + 0 -1 68 -570427393 -16649 -69633 -131073 -536944677 -1 -8737 + -1435828225 + + -6.8078064918518066e-001 2.5120577216148376e-001 + <_> + + 0 -1 50 -1179697 -34082849 -3278356 -37429266 -1048578 + -555753474 -1015551096 -37489685 + + -6.1699724197387695e-001 3.0963841080665588e-001 + <_> + + 0 -1 129 -1931606992 -17548804 -16842753 -1075021827 + 1073667572 -81921 -1611073620 -1415047752 + + -6.0499197244644165e-001 3.0735063552856445e-001 + <_> + + 0 -1 136 -269754813 1761591286 -1073811523 2130378623 -17580 + -1082294665 -159514800 -1026883840 + + -5.6772041320800781e-001 3.5023149847984314e-001 + <_> + + 0 -1 65 2016561683 1528827871 -10258447 960184191 125476830 + -8511618 -1078239365 187648611 + + -5.5894804000854492e-001 3.4856522083282471e-001 + <_> + + 0 -1 13 -207423502 -333902 2013200231 -202348848 1042454451 + -16393 1073117139 2004162321 + + -5.7197356224060059e-001 3.2818377017974854e-001 + + <_> + 9 + -3.4892759323120117e+000 + + <_> + + 0 -1 78 -1 -1 -1 -1 -8912897 -1 -8912897 -1 + + -9.8917990922927856e-001 -7.3812037706375122e-001 + <_> + + 0 -1 93 -1 -1 -1 -1 -148635649 -524297 -8912897 -1 + + -9.3414896726608276e-001 -2.6945295929908752e-001 + <_> + + 0 -1 83 -1 -524289 -1 -1048577 1879011071 -32769 -524289 + -3178753 + + -7.6891708374023438e-001 5.2568886429071426e-002 + <_> + + 0 -1 9 -352329729 -17891329 -16810117 -486871042 -688128841 + -1358954675 -16777218 -219217968 + + -6.2337344884872437e-001 2.5143685936927795e-001 + <_> + + 0 -1 130 -2157 -1548812374 -1343233440 -418381854 -953155613 + -836960513 -713571200 -709888014 + + -4.7277018427848816e-001 3.9616456627845764e-001 + <_> + + 0 -1 121 -1094717701 -67240065 -65857 -32899 -5783756 + -136446081 -134285352 -2003298884 + + -5.1766264438629150e-001 3.5814732313156128e-001 + <_> + + 0 -1 23 -218830160 -119671186 5505075 1241491391 -1594469 + -2097185 2004828075 -67649541 + + -6.5394639968872070e-001 3.0377501249313354e-001 + <_> + + 0 -1 115 -551814749 2099511088 -1090732551 -2045546512 + -1086341441 1059848178 800042912 252705994 + + -5.2584588527679443e-001 3.3847147226333618e-001 + <_> + + 0 -1 99 -272651477 578776766 -285233490 -889225217 + 2147448656 377454463 2012701952 -68157761 + + -6.1836904287338257e-001 2.8922611474990845e-001 + + <_> + 9 + -3.0220029354095459e+000 + + <_> + + 0 -1 36 -1 -570425345 -1 -570425345 -1 -50331649 -6291457 -1 + + -9.7703826427459717e-001 -6.2527233362197876e-001 + <_> + + 0 -1 124 -1430602241 -33619969 -1 -3 -1074003969 -1073758209 + -1073741825 -1073768705 + + -8.9538317918777466e-001 -3.1887885928153992e-001 + <_> + + 0 -1 88 -1 -268439625 -65601 -268439569 -393809 -270532609 + -42076889 -288361721 + + -6.8733429908752441e-001 1.2978810071945190e-001 + <_> + + 0 -1 132 -755049252 2042563807 1795096575 465121071 + -1090585188 -20609 -1459691784 539672495 + + -5.7038843631744385e-001 3.0220884084701538e-001 + <_> + + 0 -1 20 -94377762 -25702678 1694167798 -231224662 1079955016 + -346144140 2029995743 -536918961 + + -5.3204691410064697e-001 3.4054222702980042e-001 + <_> + + 0 -1 47 2143026943 -285278225 -3 -612438281 -16403 -131074 + -1 -1430749256 + + -4.6176829934120178e-001 4.1114711761474609e-001 + <_> + + 0 -1 74 203424336 -25378820 -35667973 1073360894 -1912815660 + -573444 -356583491 -1365235056 + + -4.9911966919898987e-001 3.5335537791252136e-001 + <_> + + 0 -1 6 -1056773 -1508430 -558153 -102747408 2133997491 + -269043865 2004842231 -8947721 + + -4.0219521522521973e-001 4.3947893381118774e-001 + <_> + + 0 -1 70 -880809694 -1070282769 -1363162108 -838881281 + -680395161 -2064124929 -34244753 1173880701 + + -5.3891533613204956e-001 3.2062566280364990e-001 + + <_> + 8 + -2.5489892959594727e+000 + + <_> + + 0 -1 39 -1 -572522497 -8519681 -570425345 -4195329 -50333249 + -1 -1 + + -9.4647216796875000e-001 -3.3662387728691101e-001 + <_> + + 0 -1 124 -1430735362 -33619971 -8201 -3 -1677983745 + -1073762817 -1074003969 -1142979329 + + -8.0300611257553101e-001 -3.8466516882181168e-002 + <_> + + 0 -1 91 -67113217 -524289 -671482265 -786461 1677132031 + -268473345 -68005889 -70291765 + + -5.8367580175399780e-001 2.6507318019866943e-001 + <_> + + 0 -1 17 -277872641 -553910292 -268435458 -16843010 + 1542420439 -1342178311 -143132940 -2834 + + -4.6897178888320923e-001 3.7864661216735840e-001 + <_> + + 0 -1 137 -1312789 -290527285 -286326862 -5505280 -1712335966 + -2045979188 1165423617 -709363723 + + -4.6382644772529602e-001 3.6114525794982910e-001 + <_> + + 0 -1 106 1355856590 -109445156 -96665606 2066939898 + 1356084692 1549031917 -30146561 -16581701 + + -6.3095021247863770e-001 2.9294869303703308e-001 + <_> + + 0 -1 104 -335555328 118529 1860167712 -810680357 -33558656 + -1368391795 -402663552 -1343225921 + + -5.9658926725387573e-001 2.7228885889053345e-001 + <_> + + 0 -1 76 217581168 -538349634 1062631419 1039868926 + -1090707460 -2228359 -1078042693 -1147128518 + + -4.5812287926673889e-001 3.7063929438591003e-001 + + <_> + 9 + -2.5802578926086426e+000 + + <_> + + 0 -1 35 -513 -706873891 -270541825 1564475391 -120602625 + -118490145 -3162113 -1025 + + -8.9068460464477539e-001 -1.6470588743686676e-001 + <_> + + 0 -1 41 -1025 872144563 -2105361 -1078076417 -1048577 + -1145061461 -87557413 -1375993973 + + -7.1808964014053345e-001 2.2022204473614693e-002 + <_> + + 0 -1 95 -42467849 967946223 -811601986 1030598351 + -1212430676 270856533 -1392539508 147705039 + + -4.9424821138381958e-001 3.0048963427543640e-001 + <_> + + 0 -1 10 -218116370 -637284625 -87373174 -521998782 + -805355450 -615023745 -814267322 -12069282 + + -5.5306458473205566e-001 2.9137542843818665e-001 + <_> + + 0 -1 105 -275849241 -527897 -11052049 -69756067 -15794193 + -1141376839 -564771 -287095455 + + -4.6759819984436035e-001 3.6638516187667847e-001 + <_> + + 0 -1 24 -1900898096 -18985228 -44056577 -24675 -1074880639 + -283998 796335613 -1079041957 + + -4.2737138271331787e-001 3.9243003726005554e-001 + <_> + + 0 -1 139 -555790844 410735094 -32106513 406822863 -897632192 + -912830145 -117771560 -1204027649 + + -4.1896930336952209e-001 3.6744937300682068e-001 + <_> + + 0 -1 0 -1884822366 -1406613148 1135342180 -1979127580 + -68174862 246469804 1001386992 -708885872 + + -5.7093089818954468e-001 2.9880744218826294e-001 + <_> + + 0 -1 45 -469053950 1439068142 2117758841 2004671078 + 207931006 1265321675 970353931 1541343047 + + -6.0491901636123657e-001 2.4652053415775299e-001 + + <_> + 9 + -2.2425732612609863e+000 + + <_> + + 0 -1 58 1481987157 282547485 -14952129 421131223 -391065352 + -24212488 -100094241 -1157907473 + + -8.2822084426879883e-001 -2.1619293093681335e-001 + <_> + + 0 -1 126 -134217889 -543174305 -75497474 -16851650 -6685738 + -75834693 -2097200 -262146 + + -5.4628932476043701e-001 2.7662658691406250e-001 + <_> + + 0 -1 133 -220728227 -604288517 -661662214 413104863 + -627323700 -251915415 -626200872 -1157958657 + + -4.1643124818801880e-001 4.1700571775436401e-001 + <_> + + 0 -1 2 -186664033 -44236961 -1630262774 -65163606 -103237330 + -3083265 -1003729 2053105955 + + -5.4847818613052368e-001 2.9710745811462402e-001 + <_> + + 0 -1 62 -256115886 -237611873 -620250696 387061799 + 1437882671 274878849 -8684449 1494294023 + + -4.6202757954597473e-001 3.3915829658508301e-001 + <_> + + 0 -1 1 -309400577 -275864640 -1056864869 1737132756 + -272385089 1609671419 1740601343 1261376789 + + -4.6158722043037415e-001 3.3939516544342041e-001 + <_> + + 0 -1 102 818197248 -196324552 286970589 -573270699 + -1174099579 -662077381 -1165157895 -1626859296 + + -4.6193107962608337e-001 3.2456985116004944e-001 + <_> + + 0 -1 69 -1042550357 14675409 1367955200 -841482753 + 1642443255 8774277 1941304147 1099949563 + + -4.9091196060180664e-001 3.3870378136634827e-001 + <_> + + 0 -1 72 -639654997 1375720439 -2129542805 1614801090 + -626787937 -5779294 1488699183 -525406458 + + -4.9073097109794617e-001 3.0637946724891663e-001 + + <_> + 9 + -1.2258235216140747e+000 + + <_> + + 0 -1 118 302046707 -16744240 1360106207 -543735387 + 1025700851 -1079408512 1796961263 -6334981 + + -6.1358314752578735e-001 2.3539231717586517e-001 + <_> + + 0 -1 5 -144765953 -116448726 -653851877 1934829856 722021887 + 856564834 1933919231 -540838029 + + -5.1209545135498047e-001 3.2506987452507019e-001 + <_> + + 0 -1 140 -170132825 -1438923874 1879300370 -1689337194 + -695606496 285911565 -1044188928 -154210028 + + -5.1769560575485229e-001 3.2290914654731750e-001 + <_> + + 0 -1 131 -140776261 -355516414 822178224 -1039743806 + -1012208926 134887424 1438876097 -908591660 + + -5.0321841239929199e-001 3.0263835191726685e-001 + <_> + + 0 -1 64 -2137211696 -1634281249 1464325973 498569935 + -1580152080 -2001687927 721783561 265096035 + + -4.6532225608825684e-001 3.4638473391532898e-001 + <_> + + 0 -1 101 -255073589 -211824417 -972195129 -1063415417 + 1937994261 1363165220 -754733105 1967602541 + + -4.9611270427703857e-001 3.3260712027549744e-001 + <_> + + 0 -1 81 -548146862 -655567194 -2062466596 1164562721 + 416408236 -1591631712 -83637777 975344427 + + -4.9862930178642273e-001 3.2003280520439148e-001 + <_> + + 0 -1 55 -731904652 2147179896 2147442687 2112830847 -65604 + -131073 -42139667 -1074907393 + + -3.6636069416999817e-001 4.5651626586914063e-001 + <_> + + 0 -1 67 1885036886 571985932 -1784930633 724431327 + 1940422257 -1085746880 964888398 731867951 + + -5.2619713544845581e-001 3.2635414600372314e-001 + + <_> + 9 + -1.3604533672332764e+000 + + <_> + + 0 -1 8 -287609985 -965585953 -2146397793 -492129894 + -729029645 -544619901 -645693256 -6565484 + + -4.5212322473526001e-001 3.8910505175590515e-001 + <_> + + 0 -1 122 -102903523 -145031013 536899675 688195859 + -645291520 -1165359094 -905565928 171608223 + + -4.9594074487686157e-001 3.4109055995941162e-001 + <_> + + 0 -1 134 -790640459 487931983 1778450522 1036604041 + -904752984 -954040118 -2134707506 304866043 + + -4.1148442029953003e-001 3.9666590094566345e-001 + <_> + + 0 -1 141 -303829117 1726939070 922189815 -827983123 + 1567883042 1324809852 292710260 -942678754 + + -3.5154473781585693e-001 4.8011952638626099e-001 + <_> + + 0 -1 59 -161295376 -159215460 -1858041315 2140644499 + -2009065472 -133804007 -2003265301 1263206851 + + -4.2808216810226440e-001 3.9841541647911072e-001 + <_> + + 0 -1 34 -264248081 -667846464 1342624856 1381160835 + -2104716852 1342865409 -266612310 -165954877 + + -4.3293288350105286e-001 4.0339657664299011e-001 + <_> + + 0 -1 32 -1600388464 -40369901 285344639 1394344275 + -255680312 -100532214 -1031663944 -7471079 + + -4.1385015845298767e-001 4.5087572932243347e-001 + <_> + + 0 -1 15 1368521651 280207469 35779199 -105983261 1208124819 + -565870452 -1144024288 -591535344 + + -4.2956474423408508e-001 4.2176279425621033e-001 + <_> + + 0 -1 109 1623607527 -661513115 -1073217263 -2142994420 + -1339883309 -89816956 436308899 1426178059 + + -4.7764992713928223e-001 3.7551075220108032e-001 + + <_> + 9 + -4.2518746852874756e-001 + + <_> + + 0 -1 135 -116728032 -1154420809 -1350582273 746061691 + -1073758277 2138570623 2113797566 -138674182 + + -1.7125381529331207e-001 6.5421247482299805e-001 + <_> + + 0 -1 63 -453112432 -1795354691 -1342242964 494112553 + 209458404 -2114697500 1316830362 259213855 + + -3.9870172739028931e-001 4.5807033777236938e-001 + <_> + + 0 -1 52 -268172036 294715533 268575185 486785157 -1065303920 + -360185856 -2147476808 134777113 + + -5.3581339120864868e-001 3.5815808176994324e-001 + <_> + + 0 -1 86 -301996882 -345718921 1877946252 -940720129 + -58737369 -721944585 -92954835 -530449 + + -3.9938014745712280e-001 4.9603295326232910e-001 + <_> + + 0 -1 14 -853281886 -756895766 2130706352 -9519120 + -1921059862 394133373 2138453959 -538200841 + + -4.0230083465576172e-001 4.9537116289138794e-001 + <_> + + 0 -1 128 -2133448688 -641138493 1078022185 294060066 + -327122776 -2130640896 -2147466247 -1910634326 + + -5.8290809392929077e-001 3.4102553129196167e-001 + <_> + + 0 -1 53 587265978 -2071658479 1108361221 -578448765 + -1811905899 -2008965119 33900729 762301595 + + -4.5518967509269714e-001 4.7242793440818787e-001 + <_> + + 0 -1 138 -1022189373 -2139094976 16658 -1069445120 + -1073555454 -1073577856 1096068 -978351488 + + -4.7530207037925720e-001 4.3885371088981628e-001 + <_> + + 0 -1 7 -395352441 -1073541103 -1056964605 1053186 269111298 + -2012184576 1611208714 -360415095 + + -5.0448113679885864e-001 4.1588482260704041e-001 + + <_> + 7 + 2.7163455262780190e-002 + + <_> + + 0 -1 49 783189748 -137429026 -257 709557994 2130460236 + -196611 -9580 585428708 + + -2.0454545319080353e-001 7.9608374834060669e-001 + <_> + + 0 -1 108 1284360448 1057423155 1592696573 -852672655 + 1547382714 -1642594369 125705358 797134398 + + -3.6474677920341492e-001 6.0925579071044922e-001 + <_> + + 0 -1 94 1347680270 -527720448 1091567712 1073745933 + -1073180671 0 285745154 -511192438 + + -4.6406838297843933e-001 5.5626088380813599e-001 + <_> + + 0 -1 73 1705780944 -145486260 -115909 -281793505 -418072663 + -1681064068 1877454127 -1912330993 + + -4.7043186426162720e-001 5.8430361747741699e-001 + <_> + + 0 -1 110 -2118142016 339509033 -285260567 1417764573 + 68144392 -468879483 -2033291636 231451911 + + -4.8700931668281555e-001 5.4639810323715210e-001 + <_> + + 0 -1 119 -1888051818 489996135 -65539 849536890 2146716845 + -1107542088 -1275615746 -1119617586 + + -4.3356490135192871e-001 6.5175366401672363e-001 + <_> + + 0 -1 44 -1879021438 336830528 1073766659 1477541961 8560696 + -1207369568 8462472 1493893448 + + -5.4343086481094360e-001 5.2777874469757080e-001 + + <_> + 7 + 4.9174150824546814e-001 + + <_> + + 0 -1 57 644098 15758324 1995964260 -463011882 893285175 + 83156983 2004317989 16021237 + + -1.7073170840740204e-001 9.0782123804092407e-001 + <_> + + 0 -1 123 268632845 -2147450864 -2143240192 -2147401728 + 8523937 -1878523840 16777416 616824984 + + -4.8744434118270874e-001 7.3311311006546021e-001 + <_> + + 0 -1 120 -2110735872 803880886 989739810 1673281312 91564930 + -277454958 997709514 -581366443 + + -4.0291741490364075e-001 8.2450771331787109e-001 + <_> + + 0 -1 87 941753434 -1067128905 788512753 -1074450460 + 779101657 -1346552460 938805167 -2050424642 + + -3.6246949434280396e-001 8.7103593349456787e-001 + <_> + + 0 -1 60 208 1645217920 130 538263552 33595552 -1475870592 + 16783361 1375993867 + + -6.1472141742706299e-001 5.9707164764404297e-001 + <_> + + 0 -1 114 1860423179 1034692624 -285213187 -986681712 + 1576755092 -1408205463 -127714 -1246035687 + + -4.5621752738952637e-001 8.9482426643371582e-001 + <_> + + 0 -1 107 33555004 -1861746688 1073807361 -754909184 + 645922856 8388608 134250648 419635458 + + -5.2466005086898804e-001 7.1834069490432739e-001 + + <_> + 2 + 1.9084988832473755e+000 + + <_> + + 0 -1 16 536064 131072 -20971516 524288 576 1048577 0 40960 + + -8.0000001192092896e-001 9.8018401861190796e-001 + <_> + + 0 -1 56 67108864 0 4096 1074003968 8192 536870912 4 262144 + + -9.6610915660858154e-001 9.2831486463546753e-001 + + <_> + + 0 0 1 1 + <_> + + 0 0 3 2 + <_> + + 0 1 13 6 + <_> + + 0 2 3 14 + <_> + + 0 2 4 2 + <_> + + 0 6 2 3 + <_> + + 0 6 3 2 + <_> + + 0 16 1 3 + <_> + + 0 20 3 3 + <_> + + 0 22 2 3 + <_> + + 0 28 4 4 + <_> + + 0 35 2 3 + <_> + + 1 0 14 7 + <_> + + 1 5 3 2 + <_> + + 1 6 2 1 + <_> + + 1 14 10 9 + <_> + + 1 21 4 4 + <_> + + 1 23 4 2 + <_> + + 2 0 13 7 + <_> + + 2 0 14 7 + <_> + + 2 33 5 4 + <_> + + 2 36 4 3 + <_> + + 2 39 3 2 + <_> + + 3 1 13 11 + <_> + + 3 2 3 2 + <_> + + 4 0 7 8 + <_> + + 4 0 13 7 + <_> + + 5 0 12 6 + <_> + + 5 0 13 7 + <_> + + 5 1 10 13 + <_> + + 5 1 12 7 + <_> + + 5 2 7 13 + <_> + + 5 4 2 1 + <_> + + 5 8 7 4 + <_> + + 5 39 3 2 + <_> + + 6 3 5 2 + <_> + + 6 3 6 2 + <_> + + 6 5 4 12 + <_> + + 6 9 6 3 + <_> + + 7 3 5 2 + <_> + + 7 3 6 13 + <_> + + 7 5 6 4 + <_> + + 7 7 6 10 + <_> + + 7 8 6 4 + <_> + + 7 32 5 4 + <_> + + 7 33 5 4 + <_> + + 8 0 1 1 + <_> + + 8 0 2 1 + <_> + + 8 2 10 7 + <_> + + 9 0 6 2 + <_> + + 9 2 9 3 + <_> + + 9 4 1 1 + <_> + + 9 6 2 1 + <_> + + 9 28 6 4 + <_> + + 10 0 9 3 + <_> + + 10 3 1 1 + <_> + + 10 10 11 11 + <_> + + 10 15 4 3 + <_> + + 11 4 2 1 + <_> + + 11 27 4 3 + <_> + + 11 36 8 2 + <_> + + 12 0 2 2 + <_> + + 12 23 4 3 + <_> + + 12 25 4 3 + <_> + + 12 29 5 3 + <_> + + 12 33 3 4 + <_> + + 13 0 2 2 + <_> + + 13 36 8 3 + <_> + + 14 0 2 2 + <_> + + 15 15 2 2 + <_> + + 16 13 3 4 + <_> + + 17 0 1 3 + <_> + + 17 1 3 3 + <_> + + 17 31 5 3 + <_> + + 17 35 3 1 + <_> + + 18 13 2 3 + <_> + + 18 39 2 1 + <_> + + 19 0 7 15 + <_> + + 19 2 7 2 + <_> + + 19 3 7 13 + <_> + + 19 14 2 2 + <_> + + 19 24 7 4 + <_> + + 20 1 6 13 + <_> + + 20 8 7 3 + <_> + + 20 9 7 3 + <_> + + 20 13 1 1 + <_> + + 20 14 2 3 + <_> + + 20 30 3 2 + <_> + + 21 0 3 4 + <_> + + 21 0 6 8 + <_> + + 21 3 6 2 + <_> + + 21 6 6 4 + <_> + + 21 37 2 1 + <_> + + 22 3 6 2 + <_> + + 22 13 1 2 + <_> + + 22 22 4 3 + <_> + + 23 0 2 3 + <_> + + 23 3 6 2 + <_> + + 23 9 5 4 + <_> + + 23 11 1 1 + <_> + + 23 15 1 1 + <_> + + 23 16 3 2 + <_> + + 23 35 2 1 + <_> + + 23 36 1 1 + <_> + + 23 39 6 2 + <_> + + 24 0 2 3 + <_> + + 24 8 6 11 + <_> + + 24 28 2 2 + <_> + + 24 33 4 4 + <_> + + 25 16 4 3 + <_> + + 25 31 5 3 + <_> + + 26 0 1 2 + <_> + + 26 0 2 2 + <_> + + 26 0 3 2 + <_> + + 26 24 4 4 + <_> + + 27 30 4 5 + <_> + + 27 36 5 3 + <_> + + 28 0 2 2 + <_> + + 28 4 2 1 + <_> + + 28 21 2 5 + <_> + + 29 8 2 1 + <_> + + 33 0 2 1 + <_> + + 33 0 4 2 + <_> + + 33 0 4 6 + <_> + + 33 3 1 1 + <_> + + 33 6 4 12 + <_> + + 33 21 4 2 + <_> + + 33 36 4 3 + <_> + + 35 1 2 2 + <_> + + 36 5 1 1 + <_> + + 36 29 3 4 + <_> + + 36 39 2 2 + <_> + + 37 5 2 2 + <_> + + 38 6 2 1 + <_> + + 38 6 2 2 + <_> + + 39 1 2 12 + <_> + + 39 24 1 2 + <_> + + 39 36 2 2 + <_> + + 40 39 1 2 + <_> + + 42 4 1 1 + <_> + + 42 20 1 2 + <_> + + 42 29 1 2 + From be7d060ea4cfb8b022ae1ebe54f8296a7fd73c6b Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Wed, 21 Dec 2016 16:19:06 +0300 Subject: [PATCH 276/409] Merge pull request #7802 from terfendail:ovxhal_wrappers_migration * OpenVX HAL updated to use generic OpenVX wrappers * vxErr class from OpenVX HAL replaced with ivx::WrapperError * reduced usage of vxImage class from OpenVX HAL replaced with ivx::Image * vxImage class rewritten as ivx::Image subclass that calls swapHandle prior release * Fix OpenVX HAL build * Fix for review comments --- 3rdparty/openvx/hal/openvx_hal.cpp | 1086 ++++++++++++++++++++++++- 3rdparty/openvx/hal/openvx_hal.hpp | 1202 +--------------------------- 3rdparty/openvx/include/ivx.hpp | 46 +- modules/imgproc/src/deriv.cpp | 15 +- modules/imgproc/src/pyramids.cpp | 2 +- modules/imgproc/src/smooth.cpp | 31 +- 6 files changed, 1174 insertions(+), 1208 deletions(-) diff --git a/3rdparty/openvx/hal/openvx_hal.cpp b/3rdparty/openvx/hal/openvx_hal.cpp index e95bbb2b58..f7962d1eb5 100644 --- a/3rdparty/openvx/hal/openvx_hal.cpp +++ b/3rdparty/openvx/hal/openvx_hal.cpp @@ -1,8 +1,1088 @@ #include "openvx_hal.hpp" -vxContext * vxContext::getContext() +#define IVX_HIDE_INFO_WARNINGS +#include "ivx.hpp" + +#include +#include + +#include +#include +#include +#include + +//================================================================================================== +// utility +// ... + +#if 0 +#include +#define PRINT(...) printf(__VA_ARGS__) +#define PRINT_HALERR_MSG(type) PRINT("OpenVX HAL impl "#type" error: %s\n", e.what()) +#else +#define PRINT(...) +#define PRINT_HALERR_MSG(type) (void)e +#endif + + +#if __cplusplus >= 201103L +#include +struct Tick +{ + typedef std::chrono::time_point point_t; + point_t start; + point_t point; + Tick() + { + start = std::chrono::steady_clock::now(); + point = std::chrono::steady_clock::now(); + } + inline int one() + { + point_t old = point; + point = std::chrono::steady_clock::now(); + return std::chrono::duration_cast(point - old).count(); + } + inline int total() + { + return std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); + } +}; +#endif + +inline ivx::Context& getOpenVXHALContext() { // not thread safe - static vxContext instance; - return &instance; + static ivx::Context instance = ivx::Context::create(); + return instance; +} + +inline bool dimTooBig(int size) +{ + static vx_uint16 current_vendor = getOpenVXHALContext().vendorID(); + + if (current_vendor == VX_ID_KHRONOS || current_vendor == VX_ID_DEFAULT) + { + //OpenVX use uint32_t for image addressing + return ((unsigned)size > (UINT_MAX / VX_SCALE_UNITY)); + } + else + return false; +} + +inline void setConstantBorder(ivx::border_t &border, vx_uint8 val) +{ + border.mode = VX_BORDER_CONSTANT; +#if VX_VERSION > VX_VERSION_1_0 + border.constant_value.U8 = val; +#else + border.constant_value = val; +#endif +} + +inline void refineStep(int w, int h, int imgType, size_t& step) +{ + if (h == 1) + step = w * ((imgType == VX_DF_IMAGE_RGBX || + imgType == VX_DF_IMAGE_U32 || imgType == VX_DF_IMAGE_S32) ? 4 : + imgType == VX_DF_IMAGE_RGB ? 3 : + (imgType == VX_DF_IMAGE_U16 || imgType == VX_DF_IMAGE_S16 || + imgType == VX_DF_IMAGE_UYVY || imgType == VX_DF_IMAGE_YUYV) ? 2 : 1); +} + +//================================================================================================== +// ivx::Image wrapped to simplify call to swapHandle prior to release +// TODO update ivx::Image to handle swapHandle prior to release on the own + +class vxImage: public ivx::Image +{ +public: + vxImage(const ivx::Image &_img) : ivx::Image(_img) {} + + ~vxImage() + { +#if VX_VERSION > VX_VERSION_1_0 + swapHandle(); +#endif + } +}; + +//================================================================================================== +// real code starts here +// ... + +#define OVX_BINARY_OP(hal_func, ovx_call) \ +template \ +int ovx_hal_##hal_func(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h) \ +{ \ + if(dimTooBig(w) || dimTooBig(h)) \ + return CV_HAL_ERROR_NOT_IMPLEMENTED; \ + refineStep(w, h, ivx::TypeToEnum::imgType, astep); \ + refineStep(w, h, ivx::TypeToEnum::imgType, bstep); \ + refineStep(w, h, ivx::TypeToEnum::imgType, cstep); \ + try \ + { \ + ivx::Context ctx = getOpenVXHALContext(); \ + vxImage \ + ia = ivx::Image::createFromHandle(ctx, ivx::TypeToEnum::imgType, \ + ivx::Image::createAddressing(w, h, sizeof(T), (vx_int32)(astep)), (void*)a), \ + ib = ivx::Image::createFromHandle(ctx, ivx::TypeToEnum::imgType, \ + ivx::Image::createAddressing(w, h, sizeof(T), (vx_int32)(bstep)), (void*)b), \ + ic = ivx::Image::createFromHandle(ctx, ivx::TypeToEnum::imgType, \ + ivx::Image::createAddressing(w, h, sizeof(T), (vx_int32)(cstep)), (void*)c); \ + ovx_call \ + } \ + catch (ivx::RuntimeError & e) \ + { \ + PRINT_HALERR_MSG(runtime); \ + return CV_HAL_ERROR_UNKNOWN; \ + } \ + catch (ivx::WrapperError & e) \ + { \ + PRINT_HALERR_MSG(wrapper); \ + return CV_HAL_ERROR_UNKNOWN; \ + } \ + return CV_HAL_ERROR_OK; \ +} + +OVX_BINARY_OP(add, { ivx::IVX_CHECK_STATUS(vxuAdd(ctx, ia, ib, VX_CONVERT_POLICY_SATURATE, ic)); }) +OVX_BINARY_OP(sub, { ivx::IVX_CHECK_STATUS(vxuSubtract(ctx, ia, ib, VX_CONVERT_POLICY_SATURATE, ic)); }) + +OVX_BINARY_OP(absdiff, { ivx::IVX_CHECK_STATUS(vxuAbsDiff(ctx, ia, ib, ic)); }) + +OVX_BINARY_OP(and, { ivx::IVX_CHECK_STATUS(vxuAnd(ctx, ia, ib, ic)); }) +OVX_BINARY_OP(or , { ivx::IVX_CHECK_STATUS(vxuOr(ctx, ia, ib, ic)); }) +OVX_BINARY_OP(xor, { ivx::IVX_CHECK_STATUS(vxuXor(ctx, ia, ib, ic)); }) + +template +int ovx_hal_mul(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h, double scale) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, ivx::TypeToEnum::imgType, astep); + refineStep(w, h, ivx::TypeToEnum::imgType, bstep); + refineStep(w, h, ivx::TypeToEnum::imgType, cstep); +#ifdef _MSC_VER + const float MAGIC_SCALE = 0x0.01010102; +#else + const float MAGIC_SCALE = 0x1.010102p-8; +#endif + try + { + int rounding_policy = VX_ROUND_POLICY_TO_ZERO; + float fscale = (float)scale; + if (fabs(fscale - MAGIC_SCALE) > FLT_EPSILON) + { + int exp = 0; + double significand = frexp(fscale, &exp); + if ((significand != 0.5) || (exp > 1) || (exp < -14)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + else + { + fscale = MAGIC_SCALE; + rounding_policy = VX_ROUND_POLICY_TO_NEAREST_EVEN;// That's the only rounding that MUST be supported for 1/255 scale + } + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, ivx::TypeToEnum::imgType, + ivx::Image::createAddressing(w, h, sizeof(T), (vx_int32)(astep)), (void*)a), + ib = ivx::Image::createFromHandle(ctx, ivx::TypeToEnum::imgType, + ivx::Image::createAddressing(w, h, sizeof(T), (vx_int32)(bstep)), (void*)b), + ic = ivx::Image::createFromHandle(ctx, ivx::TypeToEnum::imgType, + ivx::Image::createAddressing(w, h, sizeof(T), (vx_int32)(cstep)), (void*)c); + ivx::IVX_CHECK_STATUS(vxuMultiply(ctx, ia, ib, fscale, VX_CONVERT_POLICY_SATURATE, rounding_policy, ic)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +template int ovx_hal_add(const uchar *a, size_t astep, const uchar *b, size_t bstep, uchar *c, size_t cstep, int w, int h); +template int ovx_hal_add(const short *a, size_t astep, const short *b, size_t bstep, short *c, size_t cstep, int w, int h); +template int ovx_hal_sub(const uchar *a, size_t astep, const uchar *b, size_t bstep, uchar *c, size_t cstep, int w, int h); +template int ovx_hal_sub(const short *a, size_t astep, const short *b, size_t bstep, short *c, size_t cstep, int w, int h); + +template int ovx_hal_absdiff(const uchar *a, size_t astep, const uchar *b, size_t bstep, uchar *c, size_t cstep, int w, int h); +template int ovx_hal_absdiff(const short *a, size_t astep, const short *b, size_t bstep, short *c, size_t cstep, int w, int h); + +template int ovx_hal_and(const uchar *a, size_t astep, const uchar *b, size_t bstep, uchar *c, size_t cstep, int w, int h); +template int ovx_hal_or(const uchar *a, size_t astep, const uchar *b, size_t bstep, uchar *c, size_t cstep, int w, int h); +template int ovx_hal_xor(const uchar *a, size_t astep, const uchar *b, size_t bstep, uchar *c, size_t cstep, int w, int h); + +template int ovx_hal_mul(const uchar *a, size_t astep, const uchar *b, size_t bstep, uchar *c, size_t cstep, int w, int h, double scale); +template int ovx_hal_mul(const short *a, size_t astep, const short *b, size_t bstep, short *c, size_t cstep, int w, int h, double scale); + +int ovx_hal_not(const uchar *a, size_t astep, uchar *c, size_t cstep, int w, int h) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, VX_DF_IMAGE_U8, astep); + refineStep(w, h, VX_DF_IMAGE_U8, cstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(w, h, 1, (vx_int32)(astep)), (void*)a), + ic = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(w, h, 1, (vx_int32)(cstep)), (void*)c); + ivx::IVX_CHECK_STATUS(vxuNot(ctx, ia, ic)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_merge8u(const uchar **src_data, uchar *dst_data, int len, int cn) +{ + if (dimTooBig(len)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + if (cn != 3 && cn != 4) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + try + { + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(len, 1, 1, (vx_int32)(len)), (void*)src_data[0]), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(len, 1, 1, (vx_int32)(len)), (void*)src_data[1]), + ic = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(len, 1, 1, (vx_int32)(len)), (void*)src_data[2]), + id = ivx::Image::createFromHandle(ctx, cn == 4 ? VX_DF_IMAGE_RGBX : VX_DF_IMAGE_RGB, + ivx::Image::createAddressing(len, 1, cn, (vx_int32)(len*cn)), (void*)dst_data); + ivx::IVX_CHECK_STATUS(vxuChannelCombine(ctx, ia, ib, ic, + cn == 4 ? (vx_image)(ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(len, 1, 1, (vx_int32)(len)), (void*)src_data[3])) : NULL, + id)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_resize(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, double inv_scale_x, double inv_scale_y, int interpolation) +{ + if (dimTooBig(aw) || dimTooBig(ah) || dimTooBig(bw) || dimTooBig(bh)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(aw, ah, VX_DF_IMAGE_U8, astep); + refineStep(bw, bh, VX_DF_IMAGE_U8, bstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(aw, ah, 1, (vx_int32)(astep)), (void*)a), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(bw, bh, 1, (vx_int32)(bstep)), (void*)b); + + if (!((atype == CV_8UC1 || atype == CV_8SC1) && + inv_scale_x > 0 && inv_scale_y > 0 && + (bw - 0.5) / inv_scale_x - 0.5 < aw && (bh - 0.5) / inv_scale_y - 0.5 < ah && + (bw + 0.5) / inv_scale_x + 0.5 >= aw && (bh + 0.5) / inv_scale_y + 0.5 >= ah && + std::abs(bw / inv_scale_x - aw) < 0.1 && std::abs(bh / inv_scale_y - ah) < 0.1)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + int mode; + if (interpolation == CV_HAL_INTER_LINEAR) + { + mode = VX_INTERPOLATION_BILINEAR; + if (inv_scale_x > 1 || inv_scale_y > 1) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + else if (interpolation == CV_HAL_INTER_AREA) + return CV_HAL_ERROR_NOT_IMPLEMENTED; //mode = VX_INTERPOLATION_AREA; + else if (interpolation == CV_HAL_INTER_NEAREST) + return CV_HAL_ERROR_NOT_IMPLEMENTED; //mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; + else + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + ivx::IVX_CHECK_STATUS(vxuScaleImage(ctx, ia, ib, mode)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_warpAffine(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[6], int interpolation, int borderType, const double borderValue[4]) +{ + if (dimTooBig(aw) || dimTooBig(ah) || dimTooBig(bw) || dimTooBig(bh)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(aw, ah, VX_DF_IMAGE_U8, astep); + refineStep(bw, bh, VX_DF_IMAGE_U8, bstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(aw, ah, 1, (vx_int32)(astep)), (void*)a), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(bw, bh, 1, (vx_int32)(bstep)), (void*)b); + + if (!(atype == CV_8UC1 || atype == CV_8SC1)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if(borderType != CV_HAL_BORDER_CONSTANT) // Neither 1.0 nor 1.1 OpenVX support BORDER_REPLICATE for warpings + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + int mode; + if (interpolation == CV_HAL_INTER_LINEAR) + mode = VX_INTERPOLATION_BILINEAR; + //AREA interpolation is unsupported + //else if (interpolation == CV_HAL_INTER_AREA) + // mode = VX_INTERPOLATION_AREA; + else if (interpolation == CV_HAL_INTER_NEAREST) + mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; + else + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + std::vector data; + data.reserve(6); + for (int j = 0; j < 3; ++j) + for (int i = 0; i < 2; ++i) + data.push_back((float)(M[i * 3 + j])); + + ivx::Matrix mtx = ivx::Matrix::create(ctx, VX_TYPE_FLOAT32, 2, 3); + mtx.copyFrom(data); + //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 + ivx::border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(VX_BORDER_CONSTANT, (vx_uint8)borderValue[0]); + ivx::IVX_CHECK_STATUS(vxuWarpAffine(ctx, ia, mtx, mode, ib)); + ctx.setImmediateBorder(prevBorder); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_warpPerspectve(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[9], int interpolation, int borderType, const double borderValue[4]) +{ + if (dimTooBig(aw) || dimTooBig(ah) || dimTooBig(bw) || dimTooBig(bh)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(aw, ah, VX_DF_IMAGE_U8, astep); + refineStep(bw, bh, VX_DF_IMAGE_U8, bstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(aw, ah, 1, (vx_int32)(astep)), (void*)a), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(bw, bh, 1, (vx_int32)(bstep)), (void*)b); + + if (!(atype == CV_8UC1 || atype == CV_8SC1)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (borderType != CV_HAL_BORDER_CONSTANT) // Neither 1.0 nor 1.1 OpenVX support BORDER_REPLICATE for warpings + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + int mode; + if (interpolation == CV_HAL_INTER_LINEAR) + mode = VX_INTERPOLATION_BILINEAR; + //AREA interpolation is unsupported + //else if (interpolation == CV_HAL_INTER_AREA) + // mode = VX_INTERPOLATION_AREA; + else if (interpolation == CV_HAL_INTER_NEAREST) + mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; + else + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + std::vector data; + data.reserve(9); + for (int j = 0; j < 3; ++j) + for (int i = 0; i < 3; ++i) + data.push_back((float)(M[i * 3 + j])); + + ivx::Matrix mtx = ivx::Matrix::create(ctx, VX_TYPE_FLOAT32, 3, 3); + mtx.copyFrom(data); + //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 + ivx::border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(VX_BORDER_CONSTANT, (vx_uint8)borderValue[0]); + ivx::IVX_CHECK_STATUS(vxuWarpPerspective(ctx, ia, mtx, mode, ib)); + ctx.setImmediateBorder(prevBorder); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +struct cvhalFilter2D; + +struct FilterCtx +{ + ivx::Convolution cnv; + int dst_type; + ivx::border_t border; + FilterCtx(ivx::Context &ctx, const std::vector data, int w, int h, int _dst_type, ivx::border_t & _border) : + cnv(ivx::Convolution::create(ctx, w, h)), dst_type(_dst_type), border(_border) { + cnv.copyFrom(data); + } +}; + +int ovx_hal_filterInit(cvhalFilter2D **filter_context, uchar *kernel_data, size_t kernel_step, int kernel_type, int kernel_width, int kernel_height, + int, int, int src_type, int dst_type, int borderType, double delta, int anchor_x, int anchor_y, bool allowSubmatrix, bool allowInplace) +{ + if (!filter_context || !kernel_data || allowSubmatrix || allowInplace || delta != 0 || + src_type != CV_8UC1 || (dst_type != CV_8UC1 && dst_type != CV_16SC1) || + kernel_width % 2 == 0 || kernel_height % 2 == 0 || anchor_x != kernel_width / 2 || anchor_y != kernel_height / 2) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + ivx::border_t border; + switch (borderType) + { + case CV_HAL_BORDER_CONSTANT: + setConstantBorder(border, 0); + break; + case CV_HAL_BORDER_REPLICATE: + border.mode = VX_BORDER_REPLICATE; + break; + default: + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + ivx::Context ctx = getOpenVXHALContext(); + + std::vector data; + data.reserve(kernel_width*kernel_height); + switch (kernel_type) + { + case CV_8UC1: + for (int j = 0; j < kernel_height; ++j) + { + uchar * row = (uchar*)(kernel_data + kernel_step*j); + for (int i = 0; i < kernel_width; ++i) + data.push_back(row[i]); + } + break; + case CV_8SC1: + for (int j = 0; j < kernel_height; ++j) + { + schar * row = (schar*)(kernel_data + kernel_step*j); + for (int i = 0; i < kernel_width; ++i) + data.push_back(row[i]); + } + break; + case CV_16SC1: + for (int j = 0; j < kernel_height; ++j) + { + short * row = (short*)(kernel_data + kernel_step*j); + for (int i = 0; i < kernel_width; ++i) + data.push_back(row[i]); + } + default: + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + FilterCtx* cnv = new FilterCtx(ctx, data, kernel_width, kernel_height, dst_type, border); + if (!cnv) + return CV_HAL_ERROR_UNKNOWN; + + *filter_context = (cvhalFilter2D*)(cnv); + return CV_HAL_ERROR_OK; +} + +int ovx_hal_filterFree(cvhalFilter2D *filter_context) +{ + if (filter_context) + { + delete (FilterCtx*)filter_context; + return CV_HAL_ERROR_OK; + } + else + { + return CV_HAL_ERROR_UNKNOWN; + } +} + +int ovx_hal_filter(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int, int, int, int) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + try + { + FilterCtx* cnv = (FilterCtx*)filter_context; + if (!cnv) + throw ivx::WrapperError("Bad HAL context"); + refineStep(w, h, VX_DF_IMAGE_U8, astep); + refineStep(w, h, cnv->dst_type == CV_16SC1 ? VX_DF_IMAGE_S16 : VX_DF_IMAGE_U8, bstep); + + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(w, h, 1, (vx_int32)(astep)), (void*)a), + ib = ivx::Image::createFromHandle(ctx, cnv->dst_type == CV_16SC1 ? VX_DF_IMAGE_S16 : VX_DF_IMAGE_U8, + ivx::Image::createAddressing(w, h, cnv->dst_type == CV_16SC1 ? 2 : 1, (vx_int32)(bstep)), (void*)b); + + //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 + ivx::border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(cnv->border); + ivx::IVX_CHECK_STATUS(vxuConvolve(ctx, ia, cnv->cnv, ib)); + ctx.setImmediateBorder(prevBorder); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_sepFilterInit(cvhalFilter2D **filter_context, int src_type, int dst_type, + int kernel_type, uchar *kernelx_data, int kernelx_length, uchar *kernely_data, int kernely_length, + int anchor_x, int anchor_y, double delta, int borderType) +{ + if (!filter_context || !kernelx_data || !kernely_data || delta != 0 || + src_type != CV_8UC1 || (dst_type != CV_8UC1 && dst_type != CV_16SC1) || + kernelx_length % 2 == 0 || kernely_length % 2 == 0 || anchor_x != kernelx_length / 2 || anchor_y != kernely_length / 2) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + ivx::border_t border; + switch (borderType) + { + case CV_HAL_BORDER_CONSTANT: + setConstantBorder(border, 0); + break; + case CV_HAL_BORDER_REPLICATE: + border.mode = VX_BORDER_REPLICATE; + break; + default: + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + ivx::Context ctx = getOpenVXHALContext(); + + //At the moment OpenVX doesn't support separable filters natively so combine kernels to generic convolution + std::vector data; + data.reserve(kernelx_length*kernely_length); + switch (kernel_type) + { + case CV_8UC1: + for (int j = 0; j < kernely_length; ++j) + for (int i = 0; i < kernelx_length; ++i) + data.push_back((short)(kernely_data[j]) * kernelx_data[i]); + break; + case CV_8SC1: + for (int j = 0; j < kernely_length; ++j) + for (int i = 0; i < kernelx_length; ++i) + data.push_back((short)(((schar*)kernely_data)[j]) * ((schar*)kernelx_data)[i]); + break; + default: + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + FilterCtx* cnv = new FilterCtx(ctx, data, kernelx_length, kernely_length, dst_type, border); + if (!cnv) + return CV_HAL_ERROR_UNKNOWN; + + *filter_context = (cvhalFilter2D*)(cnv); + return CV_HAL_ERROR_OK; +} + +#if VX_VERSION > VX_VERSION_1_0 + +struct MorphCtx +{ + ivx::Matrix mask; + int operation; + ivx::border_t border; + MorphCtx(ivx::Context &ctx, const std::vector data, int w, int h, int _operation, ivx::border_t & _border) : + mask(ivx::Matrix::create(ctx, ivx::TypeToEnum::value, w, h)), operation(_operation), border(_border) { + mask.copyFrom(data); + } +}; + +int ovx_hal_morphInit(cvhalFilter2D **filter_context, int operation, 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 allowSubmatrix, bool allowInplace) +{ + if (!filter_context || !kernel_data || allowSubmatrix || allowInplace || iterations != 1 || + src_type != CV_8UC1 || dst_type != CV_8UC1 || + kernel_width % 2 == 0 || kernel_height % 2 == 0 || anchor_x != kernel_width / 2 || anchor_y != kernel_height / 2) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + ivx::border_t border; + switch (borderType) + { + case CV_HAL_BORDER_CONSTANT: + if (borderValue[0] == DBL_MAX && borderValue[1] == DBL_MAX && borderValue[2] == DBL_MAX && borderValue[3] == DBL_MAX) + { + if (operation == MORPH_ERODE) + setConstantBorder(border, UCHAR_MAX); + else + setConstantBorder(border, 0); + } + else + { + int rounded = (int)round(borderValue[0]); + setConstantBorder(border, (vx_uint8)((unsigned)rounded <= UCHAR_MAX ? rounded : rounded > 0 ? UCHAR_MAX : 0)); + } + break; + case CV_HAL_BORDER_REPLICATE: + border.mode = VX_BORDER_REPLICATE; + break; + default: + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + ivx::Context ctx = getOpenVXHALContext(); + + vx_size maxKernelDim = ctx.nonlinearMaxDimension(); + if ((vx_size)kernel_width > maxKernelDim || (vx_size)kernel_height > maxKernelDim) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + std::vector kernel_mat; + kernel_mat.reserve(kernel_width * kernel_height); + switch (CV_MAT_DEPTH(kernel_type)) + { + case CV_8U: + case CV_8S: + for (int j = 0; j < kernel_height; ++j) + { + uchar * kernel_row = kernel_data + j * kernel_step; + for (int i = 0; i < kernel_width; ++i) + kernel_mat.push_back(kernel_row[i] ? 255 : 0); + } + break; + case CV_16U: + case CV_16S: + for (int j = 0; j < kernel_height; ++j) + { + short * kernel_row = (short*)(kernel_data + j * kernel_step); + for (int i = 0; i < kernel_width; ++i) + kernel_mat.push_back(kernel_row[i] ? 255 : 0); + } + break; + case CV_32S: + for (int j = 0; j < kernel_height; ++j) + { + int * kernel_row = (int*)(kernel_data + j * kernel_step); + for (int i = 0; i < kernel_width; ++i) + kernel_mat.push_back(kernel_row[i] ? 255 : 0); + } + break; + case CV_32F: + for (int j = 0; j < kernel_height; ++j) + { + float * kernel_row = (float*)(kernel_data + j * kernel_step); + for (int i = 0; i < kernel_width; ++i) + kernel_mat.push_back(kernel_row[i] ? 255 : 0); + } + break; + case CV_64F: + for (int j = 0; j < kernel_height; ++j) + { + double * kernel_row = (double*)(kernel_data + j * kernel_step); + for (int i = 0; i < kernel_width; ++i) + kernel_mat.push_back(kernel_row[i] ? 255 : 0); + } + break; + default: + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + MorphCtx* mat; + switch (operation) + { + case MORPH_ERODE: + mat = new MorphCtx(ctx, kernel_mat, kernel_width, kernel_height, VX_NONLINEAR_FILTER_MIN, border); + break; + case MORPH_DILATE: + mat = new MorphCtx(ctx, kernel_mat, kernel_width, kernel_height, VX_NONLINEAR_FILTER_MAX, border); + break; + default: + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + if (!mat) + return CV_HAL_ERROR_UNKNOWN; + + *filter_context = (cvhalFilter2D*)(mat); + return CV_HAL_ERROR_OK; +} + +int ovx_hal_morphFree(cvhalFilter2D *filter_context) +{ + if (filter_context) + { + delete (MorphCtx*)filter_context; + return CV_HAL_ERROR_OK; + } + else + { + return CV_HAL_ERROR_UNKNOWN; + } +} + +int ovx_hal_morph(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int, int, int, int, int, int, int, int) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, VX_DF_IMAGE_U8, astep); + refineStep(w, h, VX_DF_IMAGE_U8, bstep); + try + { + MorphCtx* mat = (MorphCtx*)filter_context; + if (!mat) + throw ivx::WrapperError("Bad HAL context"); + + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(w, h, 1, (vx_int32)(astep)), (void*)a), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(w, h, 1, (vx_int32)(bstep)), (void*)b); + + //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 + ivx::border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(mat->border); + ivx::IVX_CHECK_STATUS(vxuNonLinearFilter(ctx, mat->operation, ia, mat->mask, ib)); + ctx.setImmediateBorder(prevBorder); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +#endif // 1.0 guard + +int ovx_hal_cvtBGRtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int depth, int acn, int bcn, bool swapBlue) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + if (depth != CV_8U || swapBlue || acn == bcn || (acn != 3 && acn != 4) || (bcn != 3 && bcn != 4)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (w & 1 || h & 1) // It's strange but sample implementation unable to convert odd sized images + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, acn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, astep); + refineStep(w, h, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, bstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, acn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, + ivx::Image::createAddressing(w, h, acn, (vx_int32)astep), (void*)a), + ib = ivx::Image::createFromHandle(ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, + ivx::Image::createAddressing(w, h, bcn, (vx_int32)bstep), b); + ivx::IVX_CHECK_STATUS(vxuColorConvert(ctx, ia, ib)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_cvtGraytoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int depth, int bcn) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + if (depth != CV_8U || (bcn != 3 && bcn != 4)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, VX_DF_IMAGE_U8, astep); + refineStep(w, h, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, bstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(w, h, 1, (vx_int32)astep), const_cast(a)), + ib = ivx::Image::createFromHandle(ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, + ivx::Image::createAddressing(w, h, bcn, (vx_int32)bstep), b); + ivx::IVX_CHECK_STATUS(vxuChannelCombine(ctx, ia, ia, ia, + bcn == 4 ? (vx_image)(ivx::Image::createUniform(ctx, w, h, VX_DF_IMAGE_U8, vx_uint8(255))) : NULL, + ib)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_cvtTwoPlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + if (!swapBlue || (bcn != 3 && bcn != 4)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (w & 1 || h & 1) // It's not described in spec but sample implementation unable to convert odd sized images + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, uIdx ? VX_DF_IMAGE_NV21 : VX_DF_IMAGE_NV12, astep); + refineStep(w, h, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, bstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + + std::vector addr; + std::vector ptrs; + addr.push_back(ivx::Image::createAddressing(w, h, 1, (vx_int32)astep)); + ptrs.push_back((void*)a); + addr.push_back(ivx::Image::createAddressing(w / 2, h / 2, 2, (vx_int32)astep)); + ptrs.push_back((void*)(a + h * astep)); + + vxImage + ia = ivx::Image::createFromHandle(ctx, uIdx ? VX_DF_IMAGE_NV21 : VX_DF_IMAGE_NV12, addr, ptrs); + if (ia.range() == VX_CHANNEL_RANGE_FULL) + return CV_HAL_ERROR_NOT_IMPLEMENTED; // OpenCV store NV12/NV21 as RANGE_RESTRICTED while OpenVX expect RANGE_FULL + vxImage + ib = ivx::Image::createFromHandle(ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, + ivx::Image::createAddressing(w, h, bcn, (vx_int32)bstep), b); + ivx::IVX_CHECK_STATUS(vxuColorConvert(ctx, ia, ib)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_cvtThreePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + if (!swapBlue || (bcn != 3 && bcn != 4) || uIdx || (size_t)w / 2 != astep - (size_t)w / 2) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (w & 1 || h & 1) // It's not described in spec but sample implementation unable to convert odd sized images + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, VX_DF_IMAGE_IYUV, astep); + refineStep(w, h, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, bstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + + std::vector addr; + std::vector ptrs; + addr.push_back(ivx::Image::createAddressing(w, h, 1, (vx_int32)astep)); + ptrs.push_back((void*)a); + addr.push_back(ivx::Image::createAddressing(w / 2, h / 2, 1, w / 2)); + ptrs.push_back((void*)(a + h * astep)); + if (addr[1].dim_x != (astep - addr[1].dim_x)) + throw ivx::WrapperError("UV planes use variable stride"); + addr.push_back(ivx::Image::createAddressing(w / 2, h / 2, 1, w / 2)); + ptrs.push_back((void*)(a + h * astep + addr[1].dim_y * addr[1].stride_y)); + + vxImage + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_IYUV, addr, ptrs); + if (ia.range() == VX_CHANNEL_RANGE_FULL) + return CV_HAL_ERROR_NOT_IMPLEMENTED; // OpenCV store NV12/NV21 as RANGE_RESTRICTED while OpenVX expect RANGE_FULL + vxImage + ib = ivx::Image::createFromHandle(ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, + ivx::Image::createAddressing(w, h, bcn, (vx_int32)bstep), b); + ivx::IVX_CHECK_STATUS(vxuColorConvert(ctx, ia, ib)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_cvtBGRtoThreePlaneYUV(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int acn, bool swapBlue, int uIdx) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + if (!swapBlue || (acn != 3 && acn != 4) || uIdx || (size_t)w / 2 != bstep - (size_t)w / 2) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (w & 1 || h & 1) // It's not described in spec but sample implementation unable to convert odd sized images + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, acn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, astep); + refineStep(w, h, VX_DF_IMAGE_IYUV, bstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, acn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, + ivx::Image::createAddressing(w, h, acn, (vx_int32)astep), (void*)a); + + std::vector addr; + std::vector ptrs; + addr.push_back(ivx::Image::createAddressing(w, h, 1, (vx_int32)bstep)); + ptrs.push_back((void*)b); + addr.push_back(ivx::Image::createAddressing(w / 2, h / 2, 1, w / 2)); + ptrs.push_back((void*)(b + h * bstep)); + if (addr[1].dim_x != (bstep - addr[1].dim_x)) + throw ivx::WrapperError("UV planes use variable stride"); + addr.push_back(ivx::Image::createAddressing(w / 2, h / 2, 1, w / 2)); + ptrs.push_back((void*)(b + h * bstep + addr[1].dim_y * addr[1].stride_y)); + + vxImage + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_IYUV, addr, ptrs); + ivx::IVX_CHECK_STATUS(vxuColorConvert(ctx, ia, ib)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx, int ycn) +{ + if (dimTooBig(w) || dimTooBig(h)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + if (!swapBlue || (bcn != 3 && bcn != 4) || uIdx) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (w & 1) // It's not described in spec but sample implementation unable to convert odd sized images + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, ycn ? VX_DF_IMAGE_UYVY : VX_DF_IMAGE_YUYV, astep); + refineStep(w, h, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, bstep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + vxImage + ia = ivx::Image::createFromHandle(ctx, ycn ? VX_DF_IMAGE_UYVY : VX_DF_IMAGE_YUYV, + ivx::Image::createAddressing(w, h, 2, (vx_int32)astep), (void*)a); + if (ia.range() == VX_CHANNEL_RANGE_FULL) + return CV_HAL_ERROR_NOT_IMPLEMENTED; // OpenCV store NV12/NV21 as RANGE_RESTRICTED while OpenVX expect RANGE_FULL + vxImage + ib = ivx::Image::createFromHandle(ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, + ivx::Image::createAddressing(w, h, bcn, (vx_int32)bstep), b); + ivx::IVX_CHECK_STATUS(vxuColorConvert(ctx, ia, ib)); + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + return CV_HAL_ERROR_OK; +} + +int ovx_hal_integral(int depth, int sdepth, int, const uchar * a, size_t astep, uchar * b, size_t bstep, uchar * c, size_t, uchar * d, size_t, int w, int h, int cn) +{ + if (depth != CV_8U || sdepth != CV_32S || c != NULL || d != NULL || cn != 1) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + refineStep(w, h, VX_DF_IMAGE_U8, astep); + try + { + ivx::Context ctx = getOpenVXHALContext(); + ivx::Image + ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, + ivx::Image::createAddressing(w, h, 1, (vx_int32)astep), const_cast(a)), + ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U32, + ivx::Image::createAddressing(w, h, 1, (vx_int32)bstep), (unsigned int *)(b + bstep + sizeof(unsigned int))); + ivx::IVX_CHECK_STATUS(vxuIntegralImage(ctx, ia, ib)); + memset(b, 0, (w + 1) * sizeof(unsigned int)); + b += bstep; + for (int i = 0; i < h; i++, b += bstep) + { + *((unsigned int*)b) = 0; + } + } + catch (ivx::RuntimeError & e) + { + PRINT_HALERR_MSG(runtime); + return CV_HAL_ERROR_UNKNOWN; + } + catch (ivx::WrapperError & e) + { + PRINT_HALERR_MSG(wrapper); + return CV_HAL_ERROR_UNKNOWN; + } + + return CV_HAL_ERROR_OK; } diff --git a/3rdparty/openvx/hal/openvx_hal.hpp b/3rdparty/openvx/hal/openvx_hal.hpp index 7dd89addb2..63da1fa5b3 100644 --- a/3rdparty/openvx/hal/openvx_hal.hpp +++ b/3rdparty/openvx/hal/openvx_hal.hpp @@ -5,1185 +5,55 @@ #include "opencv2/imgproc/hal/interface.h" #include "VX/vx.h" -#include "VX/vxu.h" - -#include -#include - -#include -#include -#include -#include - -#ifndef VX_VENDOR_ID -#define VX_VENDOR_ID VX_ID_DEFAULT -#endif - -#if VX_VERSION == VX_VERSION_1_0 - -static const vx_enum VX_MEMORY_TYPE_HOST = VX_IMPORT_TYPE_HOST; -static const vx_enum VX_INTERPOLATION_BILINEAR = VX_INTERPOLATION_TYPE_BILINEAR; -static const vx_enum VX_INTERPOLATION_AREA = VX_INTERPOLATION_TYPE_AREA; -static const vx_enum VX_INTERPOLATION_NEAREST_NEIGHBOR = VX_INTERPOLATION_TYPE_NEAREST_NEIGHBOR; -static const vx_enum VX_IMAGE_RANGE = VX_IMAGE_ATTRIBUTE_RANGE; -static const vx_enum VX_IMAGE_SPACE = VX_IMAGE_ATTRIBUTE_SPACE; -typedef vx_border_mode_t vx_border_t; -static const vx_enum VX_BORDER_CONSTANT = VX_BORDER_MODE_CONSTANT; -static const vx_enum VX_BORDER_REPLICATE = VX_BORDER_MODE_REPLICATE; -static const vx_enum VX_CONTEXT_IMMEDIATE_BORDER = VX_CONTEXT_ATTRIBUTE_IMMEDIATE_BORDER_MODE; - -#endif - -//================================================================================================== -// utility -// ... - -#if 0 -#include -#define PRINT(...) printf(__VA_ARGS__) -#else -#define PRINT(...) -#endif - -#if __cplusplus >= 201103L -#include -struct Tick -{ - typedef std::chrono::time_point point_t; - point_t start; - point_t point; - Tick() - { - start = std::chrono::steady_clock::now(); - point = std::chrono::steady_clock::now(); - } - inline int one() - { - point_t old = point; - point = std::chrono::steady_clock::now(); - return std::chrono::duration_cast(point - old).count(); - } - inline int total() - { - return std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); - } -}; -#endif - -inline bool dimTooBig(int size) -{ - if (VX_VENDOR_ID == VX_ID_KHRONOS || VX_VENDOR_ID == VX_ID_DEFAULT) - { - //OpenVX use uint32_t for image addressing - return ((unsigned)size > (UINT_MAX / VX_SCALE_UNITY)); - } - else - return false; -} - -//================================================================================================== -// One more OpenVX C++ binding :-) -// ... template -struct VX_Traits -{ - enum { - ImgType = 0, - DataType = 0 - }; -}; - -template <> -struct VX_Traits -{ - enum { - ImgType = VX_DF_IMAGE_U8, - DataType = VX_TYPE_UINT8 - }; -}; - -template <> -struct VX_Traits -{ - enum { - ImgType = VX_DF_IMAGE_U16, - DataType = VX_TYPE_UINT16 - }; -}; - -template <> -struct VX_Traits -{ - enum { - ImgType = VX_DF_IMAGE_S16, - DataType = VX_TYPE_INT16 - }; -}; - -template <> -struct VX_Traits -{ - enum { - ImgType = VX_DF_IMAGE_U32, - DataType = VX_TYPE_UINT32 - }; -}; - -template <> -struct VX_Traits -{ - enum { - ImgType = VX_DF_IMAGE_S32, - DataType = VX_TYPE_INT32 - }; -}; - -template <> -struct VX_Traits -{ - enum { - ImgType = 0, - DataType = VX_TYPE_FLOAT32 - }; -}; - - -struct vxContext; -struct vxImage; -struct vxErr; - - -struct vxErr -{ - vx_status status; - std::string msg; - vxErr(vx_status status_, const std::string & msg_) : status(status_), msg(msg_) {} - void check() - { - if (status != VX_SUCCESS) - throw *this; - } - void print() - { - PRINT("OpenVX HAL impl error: %d (%s)\n", status, msg.c_str()); - } - static void check(vx_context ctx) - { - vxErr(vxGetStatus((vx_reference)ctx), "context check").check(); - } - static void check(vx_image img) - { - vxErr(vxGetStatus((vx_reference)img), "image check").check(); - } - static void check(vx_matrix mtx) - { - vxErr(vxGetStatus((vx_reference)mtx), "matrix check").check(); - } - static void check(vx_convolution cnv) - { - vxErr(vxGetStatus((vx_reference)cnv), "convolution check").check(); - } - static void check(vx_status s) - { - vxErr(s, "status check").check(); - } -}; - - -struct vxContext -{ - vx_context ctx; - static vxContext * getContext(); -private: - vxContext() - { - ctx = vxCreateContext(); - vxErr::check(ctx); - } - ~vxContext() - { - vxReleaseContext(&ctx); - } -}; - - -struct vxImage -{ - vx_image img; - - template - vxImage(vxContext &ctx, const T *data, size_t step, int w, int h) - { - if (h == 1) - step = w * sizeof(T); - vx_imagepatch_addressing_t addr; - addr.dim_x = w; - addr.dim_y = h; - addr.stride_x = sizeof(T); - addr.stride_y = (vx_int32)step; - void *ptrs[] = { (void*)data }; - img = vxCreateImageFromHandle(ctx.ctx, VX_Traits::ImgType, &addr, ptrs, VX_MEMORY_TYPE_HOST); - vxErr::check(img); - swapMemory = true; - } - template - vxImage(vxContext &ctx, T value, int w, int h) - { -#if VX_VERSION > VX_VERSION_1_0 - vx_pixel_value_t pixel; - switch ((int)(VX_Traits::DataType)) - { - case VX_TYPE_UINT8:pixel.U8 = value; break; - case VX_TYPE_UINT16:pixel.U16 = value; break; - case VX_TYPE_INT16:pixel.S16 = value; break; - default:vxErr(VX_ERROR_INVALID_PARAMETERS, "uniform image creation").check(); - } - img = vxCreateUniformImage(ctx.ctx, w, h, VX_Traits::ImgType, &pixel); -#else - img = vxCreateUniformImage(ctx.ctx, w, h, VX_Traits::ImgType, &value); -#endif - vxErr::check(img); - swapMemory = false; - } - vxImage(vxContext &ctx, int imgType, const uchar *data, size_t step, int w, int h) - { - if (h == 1) - step = w * ((imgType == VX_DF_IMAGE_RGBX || - imgType == VX_DF_IMAGE_U32 || imgType == VX_DF_IMAGE_S32) ? 4 : - imgType == VX_DF_IMAGE_RGB ? 3 : - (imgType == VX_DF_IMAGE_U16 || imgType == VX_DF_IMAGE_S16 || - imgType == VX_DF_IMAGE_UYVY || imgType == VX_DF_IMAGE_YUYV) ? 2 : 1); - - vx_imagepatch_addressing_t addr[4]; - void *ptrs[4]; - switch (imgType) - { - case VX_DF_IMAGE_U8: - case VX_DF_IMAGE_U16: - case VX_DF_IMAGE_S16: - case VX_DF_IMAGE_U32: - case VX_DF_IMAGE_S32: - case VX_DF_IMAGE_RGB: - case VX_DF_IMAGE_RGBX: - case VX_DF_IMAGE_UYVY: - case VX_DF_IMAGE_YUYV: - addr[0].dim_x = w; - addr[0].dim_y = h; - addr[0].stride_x = imgType == VX_DF_IMAGE_U8 ? 1 : - imgType == VX_DF_IMAGE_RGB ? 3 : - (imgType == VX_DF_IMAGE_U16 || imgType == VX_DF_IMAGE_S16 || - imgType == VX_DF_IMAGE_UYVY || imgType == VX_DF_IMAGE_YUYV) ? 2 : 4; - addr[0].stride_y = (vx_int32)step; - ptrs[0] = (void*)data; - break; - case VX_DF_IMAGE_NV12: - case VX_DF_IMAGE_NV21: - addr[0].dim_x = w; - addr[0].dim_y = h; - addr[0].stride_x = 1; - addr[0].stride_y = (vx_int32)step; - ptrs[0] = (void*)data; - addr[1].dim_x = w / 2; - addr[1].dim_y = h / 2; - addr[1].stride_x = 2; - addr[1].stride_y = (vx_int32)step; - ptrs[1] = (void*)(data + h * step); - break; - case VX_DF_IMAGE_IYUV: - case VX_DF_IMAGE_YUV4: - addr[0].dim_x = w; - addr[0].dim_y = h; - addr[0].stride_x = 1; - addr[0].stride_y = (vx_int32)step; - ptrs[0] = (void*)data; - addr[1].dim_x = imgType == VX_DF_IMAGE_YUV4 ? w : w / 2; - addr[1].dim_y = imgType == VX_DF_IMAGE_YUV4 ? h : h / 2; - if (addr[1].dim_x != (step - addr[1].dim_x)) - vxErr(VX_ERROR_INVALID_PARAMETERS, "UV planes use variable stride").check(); - addr[1].stride_x = 1; - addr[1].stride_y = (vx_int32)addr[1].dim_x; - ptrs[1] = (void*)(data + h * step); - addr[2].dim_x = addr[1].dim_x; - addr[2].dim_y = addr[1].dim_y; - addr[2].stride_x = 1; - addr[2].stride_y = addr[1].stride_y; - ptrs[2] = (void*)(data + h * step + addr[1].dim_y * addr[1].stride_y); - break; - default: - vxErr(VX_ERROR_INVALID_PARAMETERS, "Bad image format").check(); - } - img = vxCreateImageFromHandle(ctx.ctx, imgType, addr, ptrs, VX_MEMORY_TYPE_HOST); - vxErr::check(img); - swapMemory = true; - } - ~vxImage() - { -#if VX_VERSION > VX_VERSION_1_0 - if (swapMemory) - vxErr::check(vxSwapImageHandle(img, NULL, NULL, 1)); -#endif - vxReleaseImage(&img); - } -private: - bool swapMemory; -}; - -struct vxMatrix -{ - vx_matrix mtx; - - template - vxMatrix(vxContext &ctx, const T *data, int w, int h) - { - mtx = vxCreateMatrix(ctx.ctx, VX_Traits::DataType, w, h); - vxErr::check(mtx); -#if VX_VERSION > VX_VERSION_1_0 - vxErr::check(vxCopyMatrix(mtx, const_cast(data), VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST)); -#else - vxErr::check(vxWriteMatrix(mtx, const_cast(data))); -#endif - } - ~vxMatrix() - { - vxReleaseMatrix(&mtx); - } -}; - -struct vxConvolution -{ - vx_convolution cnv; - - vxConvolution(vxContext &ctx, const short *data, int w, int h) - { - cnv = vxCreateConvolution(ctx.ctx, w, h); - vxErr::check(cnv); -#if VX_VERSION > VX_VERSION_1_0 - vxErr::check(vxCopyConvolutionCoefficients(cnv, const_cast(data), VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST)); -#else - vxErr::check(vxWriteConvolutionCoefficients(cnv, const_cast(data))); -#endif - } - ~vxConvolution() - { - vxReleaseConvolution(&cnv); - } -}; - -inline void setConstantBorder(vx_border_t &border, vx_uint8 val) -{ - border.mode = VX_BORDER_CONSTANT; -#if VX_VERSION > VX_VERSION_1_0 - border.constant_value.U8 = val; -#else - border.constant_value = val; -#endif -} - -//================================================================================================== -// real code starts here -// ... - -#define OVX_BINARY_OP(hal_func, ovx_call) \ -template \ -inline int ovx_hal_##hal_func(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h) \ -{ \ - if(dimTooBig(w) || dimTooBig(h)) \ - return CV_HAL_ERROR_NOT_IMPLEMENTED; \ - try \ - { \ - vxContext * ctx = vxContext::getContext(); \ - vxImage ia(*ctx, a, astep, w, h); \ - vxImage ib(*ctx, b, bstep, w, h); \ - vxImage ic(*ctx, c, cstep, w, h); \ - ovx_call \ - } \ - catch (vxErr & e) \ - { \ - e.print(); \ - return CV_HAL_ERROR_UNKNOWN; \ - } \ - return CV_HAL_ERROR_OK; \ -} - -OVX_BINARY_OP(add, {vxErr::check(vxuAdd(ctx->ctx, ia.img, ib.img, VX_CONVERT_POLICY_SATURATE, ic.img));}) -OVX_BINARY_OP(sub, {vxErr::check(vxuSubtract(ctx->ctx, ia.img, ib.img, VX_CONVERT_POLICY_SATURATE, ic.img));}) - -OVX_BINARY_OP(absdiff, {vxErr::check(vxuAbsDiff(ctx->ctx, ia.img, ib.img, ic.img));}) - -OVX_BINARY_OP(and, {vxErr::check(vxuAnd(ctx->ctx, ia.img, ib.img, ic.img));}) -OVX_BINARY_OP(or, {vxErr::check(vxuOr(ctx->ctx, ia.img, ib.img, ic.img));}) -OVX_BINARY_OP(xor, {vxErr::check(vxuXor(ctx->ctx, ia.img, ib.img, ic.img));}) +int ovx_hal_add(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h); +template +int ovx_hal_sub(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h); template -inline int ovx_hal_mul(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h, double scale) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; -#ifdef _MSC_VER - const float MAGIC_SCALE = 0x0.01010102; -#else - const float MAGIC_SCALE = 0x1.010102p-8; -#endif - try - { - int rounding_policy = VX_ROUND_POLICY_TO_ZERO; - float fscale = (float)scale; - if (fabs(fscale - MAGIC_SCALE) > FLT_EPSILON) - { - int exp = 0; - double significand = frexp(fscale, &exp); - if((significand != 0.5) || (exp > 1) || (exp < -14)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - else - { - fscale = MAGIC_SCALE; - rounding_policy = VX_ROUND_POLICY_TO_NEAREST_EVEN;// That's the only rounding that MUST be supported for 1/255 scale - } - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, a, astep, w, h); - vxImage ib(*ctx, b, bstep, w, h); - vxImage ic(*ctx, c, cstep, w, h); - vxErr::check(vxuMultiply(ctx->ctx, ia.img, ib.img, fscale, VX_CONVERT_POLICY_SATURATE, rounding_policy, ic.img)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} +int ovx_hal_absdiff(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h); -inline int ovx_hal_not(const uchar *a, size_t astep, uchar *c, size_t cstep, int w, int h) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, a, astep, w, h); - vxImage ic(*ctx, c, cstep, w, h); - vxErr::check(vxuNot(ctx->ctx, ia.img, ic.img)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} +template +int ovx_hal_and(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h); +template +int ovx_hal_or(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h); +template +int ovx_hal_xor(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h); +int ovx_hal_not(const uchar *a, size_t astep, uchar *c, size_t cstep, int w, int h); -inline int ovx_hal_merge8u(const uchar **src_data, uchar *dst_data, int len, int cn) -{ - if(dimTooBig(len)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - if (cn != 3 && cn != 4) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, src_data[0], len, len, 1); - vxImage ib(*ctx, src_data[1], len, len, 1); - vxImage ic(*ctx, src_data[2], len, len, 1); - vxImage id(*ctx, cn == 4 ? VX_DF_IMAGE_RGBX : VX_DF_IMAGE_RGB, dst_data, len, len, 1); - vxErr::check(vxuChannelCombine(ctx->ctx, ia.img, ib.img, ic.img, - cn == 4 ? vxImage(*ctx, src_data[3], len, len, 1).img : NULL, - id.img)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} +template +int ovx_hal_mul(const T *a, size_t astep, const T *b, size_t bstep, T *c, size_t cstep, int w, int h, double scale); -inline int ovx_hal_resize(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, double inv_scale_x, double inv_scale_y, int interpolation) -{ - if(dimTooBig(aw) || dimTooBig(ah) || dimTooBig(bw) || dimTooBig(bh)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, a, astep, aw, ah); - vxImage ib(*ctx, b, bstep, bw, bh); - - if(!((atype == CV_8UC1 || atype == CV_8SC1) && - inv_scale_x > 0 && inv_scale_y > 0 && - (bw - 0.5) / inv_scale_x - 0.5 < aw && (bh - 0.5) / inv_scale_y - 0.5 < ah && - (bw + 0.5) / inv_scale_x + 0.5 >= aw && (bh + 0.5) / inv_scale_y + 0.5 >= ah && - std::abs(bw / inv_scale_x - aw) < 0.1 && std::abs(bh / inv_scale_y - ah) < 0.1 )) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - int mode; - if (interpolation == CV_HAL_INTER_LINEAR) - { - mode = VX_INTERPOLATION_BILINEAR; - if (inv_scale_x > 1 || inv_scale_y > 1) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - else if (interpolation == CV_HAL_INTER_AREA) - return CV_HAL_ERROR_NOT_IMPLEMENTED; //mode = VX_INTERPOLATION_AREA; - else if (interpolation == CV_HAL_INTER_NEAREST) - return CV_HAL_ERROR_NOT_IMPLEMENTED; //mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; - else - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - vxErr::check( vxuScaleImage(ctx->ctx, ia.img, ib.img, mode)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_warpAffine(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[6], int interpolation, int borderType, const double borderValue[4]) -{ - if(dimTooBig(aw) || dimTooBig(ah) || dimTooBig(bw) || dimTooBig(bh)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, a, astep, aw, ah); - vxImage ib(*ctx, b, bstep, bw, bh); - - if (!(atype == CV_8UC1 || atype == CV_8SC1)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - vx_border_t border; - switch (borderType) - { - case CV_HAL_BORDER_CONSTANT: - setConstantBorder(border, (vx_uint8)borderValue[0]); - break; - case CV_HAL_BORDER_REPLICATE: - // Neither 1.0 nor 1.1 OpenVX support BORDER_REPLICATE for warpings - default: - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - - int mode; - if (interpolation == CV_HAL_INTER_LINEAR) - mode = VX_INTERPOLATION_BILINEAR; - //AREA interpolation is unsupported - //else if (interpolation == CV_HAL_INTER_AREA) - // mode = VX_INTERPOLATION_AREA; - else if (interpolation == CV_HAL_INTER_NEAREST) - mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; - else - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - std::vector data; - data.reserve(6); - for (int j = 0; j < 3; ++j) - for (int i = 0; i < 2; ++i) - data.push_back((float)(M[i*3+j])); - - vxMatrix mtx(*ctx, data.data(), 2, 3); - //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; - vxErr::check(vxQueryContext(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &prevBorder, sizeof(prevBorder))); - vxErr::check(vxSetContextAttribute(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &border, sizeof(border))); - vxErr::check(vxuWarpAffine(ctx->ctx, ia.img, mtx.mtx, mode, ib.img)); - vxErr::check(vxSetContextAttribute(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &prevBorder, sizeof(prevBorder))); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_warpPerspectve(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[9], int interpolation, int borderType, const double borderValue[4]) -{ - if(dimTooBig(aw) || dimTooBig(ah) || dimTooBig(bw) || dimTooBig(bh)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, a, astep, aw, ah); - vxImage ib(*ctx, b, bstep, bw, bh); - - if (!(atype == CV_8UC1 || atype == CV_8SC1)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - vx_border_t border; - switch (borderType) - { - case CV_HAL_BORDER_CONSTANT: - setConstantBorder(border, (vx_uint8)borderValue[0]); - break; - case CV_HAL_BORDER_REPLICATE: - // Neither 1.0 nor 1.1 OpenVX support BORDER_REPLICATE for warpings - default: - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - - int mode; - if (interpolation == CV_HAL_INTER_LINEAR) - mode = VX_INTERPOLATION_BILINEAR; - //AREA interpolation is unsupported - //else if (interpolation == CV_HAL_INTER_AREA) - // mode = VX_INTERPOLATION_AREA; - else if (interpolation == CV_HAL_INTER_NEAREST) - mode = VX_INTERPOLATION_NEAREST_NEIGHBOR; - else - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - std::vector data; - data.reserve(9); - for (int j = 0; j < 3; ++j) - for (int i = 0; i < 3; ++i) - data.push_back((float)(M[i * 3 + j])); - - vxMatrix mtx(*ctx, data.data(), 3, 3); - //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; - vxErr::check(vxQueryContext(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &prevBorder, sizeof(prevBorder))); - vxErr::check(vxSetContextAttribute(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &border, sizeof(border))); - vxErr::check(vxuWarpPerspective(ctx->ctx, ia.img, mtx.mtx, mode, ib.img)); - vxErr::check(vxSetContextAttribute(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &prevBorder, sizeof(prevBorder))); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} +int ovx_hal_merge8u(const uchar **src_data, uchar *dst_data, int len, int cn); +int ovx_hal_resize(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, double inv_scale_x, double inv_scale_y, int interpolation); +int ovx_hal_warpAffine(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[6], int interpolation, int borderType, const double borderValue[4]); +int ovx_hal_warpPerspectve(int atype, const uchar *a, size_t astep, int aw, int ah, uchar *b, size_t bstep, int bw, int bh, const double M[9], int interpolation, int borderType, const double borderValue[4]); struct cvhalFilter2D; - -struct FilterCtx -{ - vxConvolution cnv; - int dst_type; - vx_border_t border; - FilterCtx(vxContext &ctx, const short *data, int w, int h, int _dst_type, vx_border_t & _border) : - cnv(ctx, data, w, h), dst_type(_dst_type), border(_border) {} -}; - -inline int ovx_hal_filterInit(cvhalFilter2D **filter_context, uchar *kernel_data, size_t kernel_step, int kernel_type, int kernel_width, int kernel_height, - int , int , int src_type, int dst_type, int borderType, double delta, int anchor_x, int anchor_y, bool allowSubmatrix, bool allowInplace) -{ - if (!filter_context || !kernel_data || allowSubmatrix || allowInplace || delta != 0 || - src_type != CV_8UC1 || (dst_type != CV_8UC1 && dst_type != CV_16SC1) || - kernel_width % 2 == 0 || kernel_height % 2 == 0 || anchor_x != kernel_width / 2 || anchor_y != kernel_height / 2) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - vx_border_t border; - switch (borderType) - { - case CV_HAL_BORDER_CONSTANT: - setConstantBorder(border, 0); - break; - case CV_HAL_BORDER_REPLICATE: - border.mode = VX_BORDER_REPLICATE; - break; - default: - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - - vxContext * ctx = vxContext::getContext(); - - std::vector data; - data.reserve(kernel_width*kernel_height); - switch (kernel_type) - { - case CV_8UC1: - for (int j = 0; j < kernel_height; ++j) - { - uchar * row = (uchar*)(kernel_data + kernel_step*j); - for (int i = 0; i < kernel_width; ++i) - data.push_back(row[i]); - } - break; - case CV_8SC1: - for (int j = 0; j < kernel_height; ++j) - { - schar * row = (schar*)(kernel_data + kernel_step*j); - for (int i = 0; i < kernel_width; ++i) - data.push_back(row[i]); - } - break; - case CV_16SC1: - for (int j = 0; j < kernel_height; ++j) - { - short * row = (short*)(kernel_data + kernel_step*j); - for (int i = 0; i < kernel_width; ++i) - data.push_back(row[i]); - } - default: - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - - FilterCtx* cnv = new FilterCtx(*ctx, data.data(), kernel_width, kernel_height, dst_type, border); - if (!cnv) - return CV_HAL_ERROR_UNKNOWN; - - *filter_context = (cvhalFilter2D*)(cnv); - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_filterFree(cvhalFilter2D *filter_context) -{ - if (filter_context) - { - delete (FilterCtx*)filter_context; - return CV_HAL_ERROR_OK; - } - else - { - return CV_HAL_ERROR_UNKNOWN; - } -} - -inline int ovx_hal_filter(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int , int , int , int ) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - try - { - FilterCtx* cnv = (FilterCtx*)filter_context; - if(!cnv) - vxErr(VX_ERROR_INVALID_PARAMETERS, "Bad HAL context").check(); - - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, a, astep, w, h); - - //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; - vxErr::check(vxQueryContext(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &prevBorder, sizeof(prevBorder))); - vxErr::check(vxSetContextAttribute(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &(cnv->border), sizeof(cnv->border))); - if (cnv->dst_type == CV_16SC1) - { - vxImage ib(*ctx, (short*)b, bstep, w, h); - vxErr::check(vxuConvolve(ctx->ctx, ia.img, cnv->cnv.cnv, ib.img)); - } - else - { - vxImage ib(*ctx, b, bstep, w, h); - vxErr::check(vxuConvolve(ctx->ctx, ia.img, cnv->cnv.cnv, ib.img)); - } - vxErr::check(vxSetContextAttribute(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &prevBorder, sizeof(prevBorder))); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_sepFilterInit(cvhalFilter2D **filter_context, int src_type, int dst_type, - int kernel_type, uchar *kernelx_data, int kernelx_length, uchar *kernely_data, int kernely_length, - int anchor_x, int anchor_y, double delta, int borderType) -{ - if (!filter_context || !kernelx_data || !kernely_data || delta != 0 || - src_type != CV_8UC1 || (dst_type != CV_8UC1 && dst_type != CV_16SC1) || - kernelx_length % 2 == 0 || kernely_length % 2 == 0 || anchor_x != kernelx_length / 2 || anchor_y != kernely_length / 2) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - vx_border_t border; - switch (borderType) - { - case CV_HAL_BORDER_CONSTANT: - setConstantBorder(border, 0); - break; - case CV_HAL_BORDER_REPLICATE: - border.mode = VX_BORDER_REPLICATE; - break; - default: - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - - vxContext * ctx = vxContext::getContext(); - - //At the moment OpenVX doesn't support separable filters natively so combine kernels to generic convolution - std::vector data; - data.reserve(kernelx_length*kernely_length); - switch (kernel_type) - { - case CV_8UC1: - for (int j = 0; j < kernely_length; ++j) - for (int i = 0; i < kernelx_length; ++i) - data.push_back((short)(kernely_data[j]) * kernelx_data[i]); - break; - case CV_8SC1: - for (int j = 0; j < kernely_length; ++j) - for (int i = 0; i < kernelx_length; ++i) - data.push_back((short)(((schar*)kernely_data)[j]) * ((schar*)kernelx_data)[i]); - break; - default: - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - - FilterCtx* cnv = new FilterCtx(*ctx, data.data(), kernelx_length, kernely_length, dst_type, border); - if (!cnv) - return CV_HAL_ERROR_UNKNOWN; - - *filter_context = (cvhalFilter2D*)(cnv); - return CV_HAL_ERROR_OK; -} +int ovx_hal_filterInit(cvhalFilter2D **filter_context, uchar *kernel_data, size_t kernel_step, int kernel_type, int kernel_width, int kernel_height, + int, int, int src_type, int dst_type, int borderType, double delta, int anchor_x, int anchor_y, bool allowSubmatrix, bool allowInplace); +int ovx_hal_filterFree(cvhalFilter2D *filter_context); +int ovx_hal_filter(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int, int, int, int); +int ovx_hal_sepFilterInit(cvhalFilter2D **filter_context, int src_type, int dst_type, + int kernel_type, uchar *kernelx_data, int kernelx_length, uchar *kernely_data, int kernely_length, + int anchor_x, int anchor_y, double delta, int borderType); #if VX_VERSION > VX_VERSION_1_0 - -struct MorphCtx -{ - vxMatrix mask; - int operation; - vx_border_t border; - MorphCtx(vxContext &ctx, const uchar *data, int w, int h, int _operation, vx_border_t & _border) : - mask(ctx, data, w, h), operation(_operation), border(_border) {} -}; - -inline int ovx_hal_morphInit(cvhalFilter2D **filter_context, int operation, 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 allowSubmatrix, bool allowInplace) -{ - if (!filter_context || !kernel_data || allowSubmatrix || allowInplace || iterations != 1 || - src_type != CV_8UC1 || dst_type != CV_8UC1 || - kernel_width % 2 == 0 || kernel_height % 2 == 0 || anchor_x != kernel_width / 2 || anchor_y != kernel_height / 2) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - vx_border_t border; - switch (borderType) - { - case CV_HAL_BORDER_CONSTANT: - if (borderValue[0] == DBL_MAX && borderValue[1] == DBL_MAX && borderValue[2] == DBL_MAX && borderValue[3] == DBL_MAX) - { - if (operation == MORPH_ERODE) - setConstantBorder(border, UCHAR_MAX); - else - setConstantBorder(border, 0); - } - else - { - int rounded = (int)round(borderValue[0]); - setConstantBorder(border, (vx_uint8)((unsigned)rounded <= UCHAR_MAX ? rounded : rounded > 0 ? UCHAR_MAX : 0)); - } - break; - case CV_HAL_BORDER_REPLICATE: - border.mode = VX_BORDER_REPLICATE; - break; - default: - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - - vxContext * ctx = vxContext::getContext(); - - vx_size maxKernelDim; - vxErr::check(vxQueryContext(ctx->ctx, VX_CONTEXT_NONLINEAR_MAX_DIMENSION, &maxKernelDim, sizeof(maxKernelDim))); - if ((vx_size)kernel_width > maxKernelDim || (vx_size)kernel_height > maxKernelDim) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - std::vector kernel_mat; - kernel_mat.reserve(kernel_width * kernel_height); - switch (CV_MAT_DEPTH(kernel_type)) - { - case CV_8U: - case CV_8S: - for (int j = 0; j < kernel_height; ++j) - { - uchar * kernel_row = kernel_data + j * kernel_step; - for (int i = 0; i < kernel_width; ++i) - kernel_mat.push_back(kernel_row[i] ? 255 : 0); - } - break; - case CV_16U: - case CV_16S: - for (int j = 0; j < kernel_height; ++j) - { - short * kernel_row = (short*)(kernel_data + j * kernel_step); - for (int i = 0; i < kernel_width; ++i) - kernel_mat.push_back(kernel_row[i] ? 255 : 0); - } - break; - case CV_32S: - for (int j = 0; j < kernel_height; ++j) - { - int * kernel_row = (int*)(kernel_data + j * kernel_step); - for (int i = 0; i < kernel_width; ++i) - kernel_mat.push_back(kernel_row[i] ? 255 : 0); - } - break; - case CV_32F: - for (int j = 0; j < kernel_height; ++j) - { - float * kernel_row = (float*)(kernel_data + j * kernel_step); - for (int i = 0; i < kernel_width; ++i) - kernel_mat.push_back(kernel_row[i] ? 255 : 0); - } - break; - case CV_64F: - for (int j = 0; j < kernel_height; ++j) - { - double * kernel_row = (double*)(kernel_data + j * kernel_step); - for (int i = 0; i < kernel_width; ++i) - kernel_mat.push_back(kernel_row[i] ? 255 : 0); - } - break; - default: - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - - MorphCtx* mat; - switch (operation) - { - case MORPH_ERODE: - mat = new MorphCtx(*ctx, kernel_mat.data(), kernel_width, kernel_height, VX_NONLINEAR_FILTER_MIN, border); - break; - case MORPH_DILATE: - mat = new MorphCtx(*ctx, kernel_mat.data(), kernel_width, kernel_height, VX_NONLINEAR_FILTER_MAX, border); - break; - default: - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } - if (!mat) - return CV_HAL_ERROR_UNKNOWN; - - *filter_context = (cvhalFilter2D*)(mat); - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_morphFree(cvhalFilter2D *filter_context) -{ - if (filter_context) - { - delete (MorphCtx*)filter_context; - return CV_HAL_ERROR_OK; - } - else - { - return CV_HAL_ERROR_UNKNOWN; - } -} - -inline int ovx_hal_morph(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int , int , int , int , int , int , int , int ) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - try - { - MorphCtx* mat = (MorphCtx*)filter_context; - if (!mat) - vxErr(VX_ERROR_INVALID_PARAMETERS, "Bad HAL context").check(); - - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, a, astep, w, h); - vxImage ib(*ctx, b, bstep, w, h); - - //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; - vxErr::check(vxQueryContext(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &prevBorder, sizeof(prevBorder))); - vxErr::check(vxSetContextAttribute(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &(mat->border), sizeof(mat->border))); - vxErr::check(vxuNonLinearFilter(ctx->ctx, mat->operation, ia.img, mat->mask.mtx, ib.img)); - vxErr::check(vxSetContextAttribute(ctx->ctx, VX_CONTEXT_IMMEDIATE_BORDER, &prevBorder, sizeof(prevBorder))); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - +int ovx_hal_morphInit(cvhalFilter2D **filter_context, int operation, 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 allowSubmatrix, bool allowInplace); +int ovx_hal_morphFree(cvhalFilter2D *filter_context); +int ovx_hal_morph(cvhalFilter2D *filter_context, uchar *a, size_t astep, uchar *b, size_t bstep, int w, int h, int , int , int , int , int , int , int , int ); #endif // 1.0 guard -inline int ovx_hal_cvtBGRtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int depth, int acn, int bcn, bool swapBlue) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - if (depth != CV_8U || swapBlue || acn == bcn || (acn != 3 && acn != 4) || (bcn != 3 && bcn != 4)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - if (w & 1 || h & 1) // It's strange but sample implementation unable to convert odd sized images - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, acn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, a, astep, w, h); - vxImage ib(*ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, b, bstep, w, h); - vxErr::check(vxuColorConvert(ctx->ctx, ia.img, ib.img)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_cvtGraytoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int depth, int bcn) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - if (depth != CV_8U || (bcn != 3 && bcn != 4)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, a, astep, w, h); - vxImage ib(*ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, b, bstep, w, h); - vxErr::check(vxuChannelCombine(ctx->ctx, ia.img, ia.img, ia.img, - bcn == 4 ? vxImage(*ctx, uchar(255), w, h).img : NULL, - ib.img)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_cvtTwoPlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - if (!swapBlue || (bcn != 3 && bcn != 4)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - if (w & 1 || h & 1) // It's not described in spec but sample implementation unable to convert odd sized images - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, uIdx ? VX_DF_IMAGE_NV21 : VX_DF_IMAGE_NV12, a, astep, w, h); - vx_channel_range_e cRange; - vxErr::check(vxQueryImage(ia.img, VX_IMAGE_RANGE, &cRange, sizeof(cRange))); - if (cRange == VX_CHANNEL_RANGE_FULL) - return CV_HAL_ERROR_NOT_IMPLEMENTED; // OpenCV store NV12/NV21 as RANGE_RESTRICTED while OpenVX expect RANGE_FULL - vxImage ib(*ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, b, bstep, w, h); - vxErr::check(vxuColorConvert(ctx->ctx, ia.img, ib.img)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_cvtThreePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - if (!swapBlue || (bcn != 3 && bcn != 4) || uIdx || (size_t)w / 2 != astep - (size_t)w / 2) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - if (w & 1 || h & 1) // It's not described in spec but sample implementation unable to convert odd sized images - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, VX_DF_IMAGE_IYUV, a, astep, w, h); - vx_channel_range_e cRange; - vxErr::check(vxQueryImage(ia.img, VX_IMAGE_RANGE, &cRange, sizeof(cRange))); - if (cRange == VX_CHANNEL_RANGE_FULL) - return CV_HAL_ERROR_NOT_IMPLEMENTED; // OpenCV store NV12/NV21 as RANGE_RESTRICTED while OpenVX expect RANGE_FULL - vxImage ib(*ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, b, bstep, w, h); - vxErr::check(vxuColorConvert(ctx->ctx, ia.img, ib.img)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_cvtBGRtoThreePlaneYUV(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int acn, bool swapBlue, int uIdx) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - if (!swapBlue || (acn != 3 && acn != 4) || uIdx || (size_t)w / 2 != bstep - (size_t)w / 2) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - if (w & 1 || h & 1) // It's not described in spec but sample implementation unable to convert odd sized images - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, acn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, a, astep, w, h); - vxImage ib(*ctx, VX_DF_IMAGE_IYUV, b, bstep, w, h); - vxErr::check(vxuColorConvert(ctx->ctx, ia.img, ib.img)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx, int ycn) -{ - if(dimTooBig(w) || dimTooBig(h)) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - if (!swapBlue || (bcn != 3 && bcn != 4) || uIdx) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - if (w & 1) // It's not described in spec but sample implementation unable to convert odd sized images - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, ycn ? VX_DF_IMAGE_UYVY : VX_DF_IMAGE_YUYV, a, astep, w, h); - vx_channel_range_e cRange; - vxErr::check(vxQueryImage(ia.img, VX_IMAGE_RANGE, &cRange, sizeof(cRange))); - if (cRange == VX_CHANNEL_RANGE_FULL) - return CV_HAL_ERROR_NOT_IMPLEMENTED; // OpenCV store NV12/NV21 as RANGE_RESTRICTED while OpenVX expect RANGE_FULL - vxImage ib(*ctx, bcn == 3 ? VX_DF_IMAGE_RGB : VX_DF_IMAGE_RGBX, b, bstep, w, h); - vxErr::check(vxuColorConvert(ctx->ctx, ia.img, ib.img)); - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} - -inline int ovx_hal_integral(int depth, int sdepth, int , const uchar * a, size_t astep, uchar * b, size_t bstep, uchar * c, size_t , uchar * d, size_t , int w, int h, int cn) -{ - if (depth != CV_8U || sdepth != CV_32S || c != NULL || d != NULL || cn != 1) - return CV_HAL_ERROR_NOT_IMPLEMENTED; - - try - { - vxContext * ctx = vxContext::getContext(); - vxImage ia(*ctx, a, astep, w, h); - vxImage ib(*ctx, (unsigned int *)(b+bstep+sizeof(unsigned int)), bstep, w, h); - vxErr::check(vxuIntegralImage(ctx->ctx, ia.img, ib.img)); - memset(b, 0, (w+1)*sizeof(unsigned int)); - b += bstep; - for (int i = 0; i < h; i++, b += bstep) - { - *((unsigned int*)b) = 0; - } - } - catch (vxErr & e) - { - e.print(); - return CV_HAL_ERROR_UNKNOWN; - } - return CV_HAL_ERROR_OK; -} +int ovx_hal_cvtBGRtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int depth, int acn, int bcn, bool swapBlue); +int ovx_hal_cvtGraytoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int depth, int bcn); +int ovx_hal_cvtTwoPlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx); +int ovx_hal_cvtThreePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx); +int ovx_hal_cvtBGRtoThreePlaneYUV(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int acn, bool swapBlue, int uIdx); +int ovx_hal_cvtOnePlaneYUVtoBGR(const uchar * a, size_t astep, uchar * b, size_t bstep, int w, int h, int bcn, bool swapBlue, int uIdx, int ycn); +int ovx_hal_integral(int depth, int sdepth, int, const uchar * a, size_t astep, uchar * b, size_t bstep, uchar * c, size_t, uchar * d, size_t, int w, int h, int cn); //================================================================================================== // functions redefinition diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index e943961590..47213e6d5c 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -22,6 +22,18 @@ Details: TBD #include #include +#ifndef VX_VERSION_1_1 +// 1.1 to 1.0 backward compatibility defines + +static const vx_enum VX_INTERPOLATION_BILINEAR = VX_INTERPOLATION_TYPE_BILINEAR; +static const vx_enum VX_INTERPOLATION_AREA = VX_INTERPOLATION_TYPE_AREA; +static const vx_enum VX_INTERPOLATION_NEAREST_NEIGHBOR = VX_INTERPOLATION_TYPE_NEAREST_NEIGHBOR; + +static const vx_enum VX_BORDER_CONSTANT = VX_BORDER_MODE_CONSTANT; +static const vx_enum VX_BORDER_REPLICATE = VX_BORDER_MODE_REPLICATE; + +#endif + #ifndef IVX_USE_CXX98 // checking compiler #if __cplusplus < 201103L && (!defined(_MSC_VER) || _MSC_VER < 1800) @@ -56,6 +68,7 @@ Details: TBD #include #include #include +#include #ifndef IVX_USE_CXX98 #include @@ -190,14 +203,14 @@ inline vx_size enumToTypeSize(vx_enum type) template struct TypeToEnum {}; template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_CHAR; }; template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_INT8; }; -template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_UINT8; }; -template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_INT16; }; -template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_UINT16; }; -template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_INT32; }; -template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_UINT32; }; +template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_UINT8, imgType = VX_DF_IMAGE_U8; }; +template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_INT16, imgType = VX_DF_IMAGE_S16; }; +template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_UINT16, imgType = VX_DF_IMAGE_U16; }; +template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_INT32, imgType = VX_DF_IMAGE_S32; }; +template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_UINT32, imgType = VX_DF_IMAGE_U32; }; template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_INT64; }; template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_UINT64; }; -template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_FLOAT32; }; +template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_FLOAT32, imgType = VX_DF_IMAGE('F', '0', '3', '2'); }; template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_FLOAT64; }; template<> struct TypeToEnum { static const vx_enum value = VX_TYPE_BOOL; }; template<> struct TypeToEnum {static const vx_enum value = VX_TYPE_KEYPOINT; }; @@ -1376,6 +1389,25 @@ public: static Image createUniform(vx_context context, vx_uint32 width, vx_uint32 height, vx_df_image format, const void* value) { return Image(vxCreateUniformImage(context, width, height, format, value)); } #endif + template + static Image createUniform(vx_context context, vx_uint32 width, vx_uint32 height, vx_df_image format, const T value) + { +#if VX_VERSION > VX_VERSION_1_0 + vx_pixel_value_t pixel; + switch (format) + { + case VX_DF_IMAGE_U8:pixel.U8 = (vx_uint8)value; break; + case VX_DF_IMAGE_S16:pixel.S16 = (vx_int16)value; break; + case VX_DF_IMAGE_U16:pixel.U16 = (vx_uint16)value; break; + case VX_DF_IMAGE_S32:pixel.S32 = (vx_int32)value; break; + case VX_DF_IMAGE_U32:pixel.U32 = (vx_uint32)value; break; + default:throw ivx::WrapperError("uniform image type unsupported by this call"); + } + return Image(vxCreateUniformImage(context, width, height, format, &pixel)); +#else + return Image(vxCreateUniformImage(context, width, height, format, &value)); +#endif + } /// Planes number for the specified image format (fourcc) /// \return 0 for unknown formats @@ -1887,7 +1919,9 @@ public: swap(_planeIdx, p._planeIdx); #endif swap(_img, p._img); +#ifdef IVX_USE_OPENCV swap(_m, p._m); +#endif } #endif diff --git a/modules/imgproc/src/deriv.cpp b/modules/imgproc/src/deriv.cpp index 24c5760a00..01352be333 100644 --- a/modules/imgproc/src/deriv.cpp +++ b/modules/imgproc/src/deriv.cpp @@ -221,19 +221,14 @@ namespace cv if ((borderType & BORDER_ISOLATED) == 0 && src.isSubmatrix()) return false; //Process isolated borders only - vx_border_t border; + vx_enum border; switch (borderType & ~BORDER_ISOLATED) { case BORDER_CONSTANT: - border.mode = VX_BORDER_CONSTANT; -#if VX_VERSION > VX_VERSION_1_0 - border.constant_value.U8 = (vx_uint8)(0); -#else - border.constant_value = (vx_uint32)(0); -#endif + border = VX_BORDER_CONSTANT; break; case BORDER_REPLICATE: - border.mode = VX_BORDER_REPLICATE; + border = VX_BORDER_REPLICATE; break; default: return false; @@ -259,8 +254,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.immediateBorder(); - ctx.setImmediateBorder(border); + ivx::border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(border, (vx_uint8)(0)); if (dtype == CV_16SC1 && ksize == 3 && ((dx | dy) == 1) && (dx + dy) == 1) { if(dx) diff --git a/modules/imgproc/src/pyramids.cpp b/modules/imgproc/src/pyramids.cpp index 866a17f8eb..c0e18c13bf 100644 --- a/modules/imgproc/src/pyramids.cpp +++ b/modules/imgproc/src/pyramids.cpp @@ -1278,7 +1278,7 @@ static bool openvx_pyrDown( InputArray _src, OutputArray _dst, const Size& _dsz, // The only border mode which is supported by both cv::pyrDown() and OpenVX // and produces predictable results - vx_border_t borderMode; + ivx::border_t borderMode; borderMode.mode = VX_BORDER_REPLICATE; _dst.create( acceptableSize, srcMat.type() ); diff --git a/modules/imgproc/src/smooth.cpp b/modules/imgproc/src/smooth.cpp index 76444398ed..d8514da01b 100644 --- a/modules/imgproc/src/smooth.cpp +++ b/modules/imgproc/src/smooth.cpp @@ -1662,19 +1662,14 @@ namespace cv if ((borderType & BORDER_ISOLATED) == 0 && src.isSubmatrix()) return false; //Process isolated borders only - vx_border_t border; + vx_enum border; switch (borderType & ~BORDER_ISOLATED) { case BORDER_CONSTANT: - border.mode = VX_BORDER_CONSTANT; -#if VX_VERSION > VX_VERSION_1_0 - border.constant_value.U8 = (vx_uint8)(0); -#else - border.constant_value = (vx_uint32)(0); -#endif + border = VX_BORDER_CONSTANT; break; case BORDER_REPLICATE: - border.mode = VX_BORDER_REPLICATE; + border = VX_BORDER_REPLICATE; break; default: return false; @@ -1701,7 +1696,7 @@ 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 ivx::border_t prevBorder = ctx.immediateBorder(); - ctx.setImmediateBorder(border); + ctx.setImmediateBorder(border, (vx_uint8)(0)); if (ddepth == CV_8U && ksize.width == 3 && ksize.height == 3 && normalize) { ivx::IVX_CHECK_STATUS(vxuBox3x3(ctx, ia, ib)); @@ -2229,19 +2224,14 @@ static bool openvx_gaussianBlur(InputArray _src, OutputArray _dst, Size ksize, if ((borderType & BORDER_ISOLATED) == 0 && src.isSubmatrix()) return false; //Process isolated borders only - vx_border_t border; + vx_enum border; switch (borderType & ~BORDER_ISOLATED) { case BORDER_CONSTANT: - border.mode = VX_BORDER_CONSTANT; -#if VX_VERSION > VX_VERSION_1_0 - border.constant_value.U8 = (vx_uint8)(0); -#else - border.constant_value = (vx_uint32)(0); -#endif + border = VX_BORDER_CONSTANT; break; case BORDER_REPLICATE: - border.mode = VX_BORDER_REPLICATE; + border = VX_BORDER_REPLICATE; break; default: return false; @@ -2268,7 +2258,7 @@ static bool openvx_gaussianBlur(InputArray _src, OutputArray _dst, Size ksize, //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 ivx::border_t prevBorder = ctx.immediateBorder(); - ctx.setImmediateBorder(border); + ctx.setImmediateBorder(border, (vx_uint8)(0)); if (ksize.width == 3 && ksize.height == 3 && (sigma1 == 0.0 || (sigma1 - 0.8) < DBL_EPSILON) && (sigma2 == 0.0 || (sigma2 - 0.8) < DBL_EPSILON)) { ivx::IVX_CHECK_STATUS(vxuGaussian3x3(ctx, ia, ib)); @@ -3369,9 +3359,6 @@ namespace cv Mat src = _src.getMat(); Mat dst = _dst.getMat(); - vx_border_t border; - border.mode = VX_BORDER_REPLICATE; - try { ivx::Context ctx = ivx::Context::create(); @@ -3395,7 +3382,7 @@ 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 ivx::border_t prevBorder = ctx.immediateBorder(); - ctx.setImmediateBorder(border); + ctx.setImmediateBorder(VX_BORDER_REPLICATE); #ifdef VX_VERSION_1_1 if (ksize == 3) #endif From ca6beb9ca8965630ab03641248c2788e5ade63fa Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 22 Dec 2016 03:11:53 +0300 Subject: [PATCH 277/409] calib3d: fix InputArray -> CvMat --- modules/calib3d/src/calibinit.cpp | 2 +- modules/calib3d/src/calibration.cpp | 130 +++++++----------- .../calib3d/test/test_cameracalibration.cpp | 3 +- 3 files changed, 52 insertions(+), 83 deletions(-) diff --git a/modules/calib3d/src/calibinit.cpp b/modules/calib3d/src/calibinit.cpp index 1bd896463c..2e9f07a274 100644 --- a/modules/calib3d/src/calibinit.cpp +++ b/modules/calib3d/src/calibinit.cpp @@ -2085,7 +2085,7 @@ void cv::drawChessboardCorners( InputOutputArray _image, Size patternSize, Mat corners = _corners.getMat(); if( corners.empty() ) return; - Mat image = _image.getMat(); CvMat c_image = _image.getMat(); + Mat image = _image.getMat(); CvMat c_image = image; int nelems = corners.checkVector(2, CV_32F, true); CV_Assert(nelems >= 0); cvDrawChessboardCorners( &c_image, patternSize, corners.ptr(), diff --git a/modules/calib3d/src/calibration.cpp b/modules/calib3d/src/calibration.cpp index c4c38be95f..b90e71c245 100644 --- a/modules/calib3d/src/calibration.cpp +++ b/modules/calib3d/src/calibration.cpp @@ -3234,54 +3234,20 @@ void cv::composeRT( InputArray _rvec1, InputArray _tvec1, c_tvec2 = tvec2, c_rvec3 = rvec3, c_tvec3 = tvec3; CvMat c_dr3dr1, c_dr3dt1, c_dr3dr2, c_dr3dt2, c_dt3dr1, c_dt3dt1, c_dt3dr2, c_dt3dt2; CvMat *p_dr3dr1=0, *p_dr3dt1=0, *p_dr3dr2=0, *p_dr3dt2=0, *p_dt3dr1=0, *p_dt3dt1=0, *p_dt3dr2=0, *p_dt3dt2=0; - - if( _dr3dr1.needed() ) - { - _dr3dr1.create(3, 3, rtype); - p_dr3dr1 = &(c_dr3dr1 = _dr3dr1.getMat()); +#define CV_COMPOSE_RT_PARAM(name) \ + Mat name; \ + if (_ ## name.needed())\ + { \ + _ ## name.create(3, 3, rtype); \ + name = _ ## name.getMat(); \ + p_ ## name = &(c_ ## name = name); \ } - if( _dr3dt1.needed() ) - { - _dr3dt1.create(3, 3, rtype); - p_dr3dt1 = &(c_dr3dt1 = _dr3dt1.getMat()); - } - - if( _dr3dr2.needed() ) - { - _dr3dr2.create(3, 3, rtype); - p_dr3dr2 = &(c_dr3dr2 = _dr3dr2.getMat()); - } - - if( _dr3dt2.needed() ) - { - _dr3dt2.create(3, 3, rtype); - p_dr3dt2 = &(c_dr3dt2 = _dr3dt2.getMat()); - } - - if( _dt3dr1.needed() ) - { - _dt3dr1.create(3, 3, rtype); - p_dt3dr1 = &(c_dt3dr1 = _dt3dr1.getMat()); - } - - if( _dt3dt1.needed() ) - { - _dt3dt1.create(3, 3, rtype); - p_dt3dt1 = &(c_dt3dt1 = _dt3dt1.getMat()); - } - - if( _dt3dr2.needed() ) - { - _dt3dr2.create(3, 3, rtype); - p_dt3dr2 = &(c_dt3dr2 = _dt3dr2.getMat()); - } - - if( _dt3dt2.needed() ) - { - _dt3dt2.create(3, 3, rtype); - p_dt3dt2 = &(c_dt3dt2 = _dt3dt2.getMat()); - } + CV_COMPOSE_RT_PARAM(dr3dr1); CV_COMPOSE_RT_PARAM(dr3dt1); + CV_COMPOSE_RT_PARAM(dr3dr2); CV_COMPOSE_RT_PARAM(dr3dt2); + CV_COMPOSE_RT_PARAM(dt3dr1); CV_COMPOSE_RT_PARAM(dt3dt1); + CV_COMPOSE_RT_PARAM(dt3dr2); CV_COMPOSE_RT_PARAM(dt3dt2); +#undef CV_COMPOSE_RT_PARAM cvComposeRT(&c_rvec1, &c_tvec1, &c_rvec2, &c_tvec2, &c_rvec3, &c_tvec3, p_dr3dr1, p_dr3dt1, p_dr3dr2, p_dr3dt2, @@ -3306,7 +3272,8 @@ void cv::projectPoints( InputArray _opoints, CvMat *pdpdrot=0, *pdpdt=0, *pdpdf=0, *pdpdc=0, *pdpddist=0; _ipoints.create(npoints, 1, CV_MAKETYPE(depth, 2), -1, true); - CvMat c_imagePoints = _ipoints.getMat(); + Mat imagePoints = _ipoints.getMat(); + CvMat c_imagePoints(imagePoints); CvMat c_objectPoints = opoints; Mat cameraMatrix = _cameraMatrix.getMat(); @@ -3674,24 +3641,25 @@ cv::Vec3d cv::RQDecomp3x3( InputArray _Mmat, Mat M = _Mmat.getMat(); _Rmat.create(3, 3, M.type()); _Qmat.create(3, 3, M.type()); + Mat Rmat = _Rmat.getMat(); + Mat Qmat = _Qmat.getMat(); Vec3d eulerAngles; - CvMat matM = M, matR = _Rmat.getMat(), matQ = _Qmat.getMat(), Qx, Qy, Qz, *pQx=0, *pQy=0, *pQz=0; - if( _Qx.needed() ) - { - _Qx.create(3, 3, M.type()); - pQx = &(Qx = _Qx.getMat()); - } - if( _Qy.needed() ) - { - _Qy.create(3, 3, M.type()); - pQy = &(Qy = _Qy.getMat()); - } - if( _Qz.needed() ) - { - _Qz.create(3, 3, M.type()); - pQz = &(Qz = _Qz.getMat()); + CvMat matM = M, matR = Rmat, matQ = Qmat; +#define CV_RQDecomp3x3_PARAM(name) \ + Mat name; \ + CvMat c_ ## name, *p ## name = NULL; \ + if( _ ## name.needed() ) \ + { \ + _ ## name.create(3, 3, M.type()); \ + name = _ ## name.getMat(); \ + c_ ## name = name; p ## name = &c_ ## name; \ } + + CV_RQDecomp3x3_PARAM(Qx); + CV_RQDecomp3x3_PARAM(Qy); + CV_RQDecomp3x3_PARAM(Qz); +#undef CV_RQDecomp3x3_PARAM cvRQDecomp3x3(&matM, &matR, &matQ, pQx, pQy, pQz, (CvPoint3D64f*)&eulerAngles[0]); return eulerAngles; } @@ -3709,28 +3677,28 @@ void cv::decomposeProjectionMatrix( InputArray _projMatrix, OutputArray _cameraM _cameraMatrix.create(3, 3, type); _rotMatrix.create(3, 3, type); _transVect.create(4, 1, type); - CvMat c_projMatrix = projMatrix, c_cameraMatrix = _cameraMatrix.getMat(); - CvMat c_rotMatrix = _rotMatrix.getMat(), c_transVect = _transVect.getMat(); - CvMat c_rotMatrixX, *p_rotMatrixX = 0; - CvMat c_rotMatrixY, *p_rotMatrixY = 0; - CvMat c_rotMatrixZ, *p_rotMatrixZ = 0; + Mat cameraMatrix = _cameraMatrix.getMat(); + Mat rotMatrix = _rotMatrix.getMat(); + Mat transVect = _transVect.getMat(); + CvMat c_projMatrix = projMatrix, c_cameraMatrix = cameraMatrix; + CvMat c_rotMatrix = rotMatrix, c_transVect = transVect; CvPoint3D64f *p_eulerAngles = 0; - if( _rotMatrixX.needed() ) - { - _rotMatrixX.create(3, 3, type); - p_rotMatrixX = &(c_rotMatrixX = _rotMatrixX.getMat()); - } - if( _rotMatrixY.needed() ) - { - _rotMatrixY.create(3, 3, type); - p_rotMatrixY = &(c_rotMatrixY = _rotMatrixY.getMat()); - } - if( _rotMatrixZ.needed() ) - { - _rotMatrixZ.create(3, 3, type); - p_rotMatrixZ = &(c_rotMatrixZ = _rotMatrixZ.getMat()); +#define CV_decomposeProjectionMatrix_PARAM(name) \ + Mat name; \ + CvMat c_ ## name, *p_ ## name = NULL; \ + if( _ ## name.needed() ) \ + { \ + _ ## name.create(3, 3, type); \ + name = _ ## name.getMat(); \ + c_ ## name = name; p_ ## name = &c_ ## name; \ } + + CV_decomposeProjectionMatrix_PARAM(rotMatrixX); + CV_decomposeProjectionMatrix_PARAM(rotMatrixY); + CV_decomposeProjectionMatrix_PARAM(rotMatrixZ); +#undef CV_decomposeProjectionMatrix_PARAM + if( _eulerAngles.needed() ) { _eulerAngles.create(3, 1, CV_64F, -1, true); diff --git a/modules/calib3d/test/test_cameracalibration.cpp b/modules/calib3d/test/test_cameracalibration.cpp index 55618c27e4..0f0740510b 100644 --- a/modules/calib3d/test/test_cameracalibration.cpp +++ b/modules/calib3d/test/test_cameracalibration.cpp @@ -1306,7 +1306,8 @@ void CV_ProjectPointsTest_C::project( const Mat& opoints, const Mat& rvec, const dpdf.create(npoints*2, 2, CV_64F); dpdc.create(npoints*2, 2, CV_64F); dpddist.create(npoints*2, distCoeffs.rows + distCoeffs.cols - 1, CV_64F); - CvMat _objectPoints = opoints, _imagePoints = Mat(ipoints); + Mat imagePoints(ipoints); + CvMat _objectPoints = opoints, _imagePoints = imagePoints; CvMat _rvec = rvec, _tvec = tvec, _cameraMatrix = cameraMatrix, _distCoeffs = distCoeffs; CvMat _dpdrot = dpdrot, _dpdt = dpdt, _dpdf = dpdf, _dpdc = dpdc, _dpddist = dpddist; From dc31ddc80a6601e071d5ce3fab2f2797ae3cb73b Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 22 Dec 2016 04:15:14 +0300 Subject: [PATCH 278/409] photo: fix InputArray -> CvMat --- modules/photo/src/inpaint.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/photo/src/inpaint.cpp b/modules/photo/src/inpaint.cpp index 0b96f1c057..4ae3cf311c 100644 --- a/modules/photo/src/inpaint.cpp +++ b/modules/photo/src/inpaint.cpp @@ -814,6 +814,7 @@ void cv::inpaint( InputArray _src, InputArray _mask, OutputArray _dst, Mat src = _src.getMat(), mask = _mask.getMat(); _dst.create( src.size(), src.type() ); - CvMat c_src = src, c_mask = mask, c_dst = _dst.getMat(); + Mat dst = _dst.getMat(); + CvMat c_src = src, c_mask = mask, c_dst = dst; cvInpaint( &c_src, &c_mask, &c_dst, inpaintRange, flags ); } From ad74fdd7cc7d1c6f9f0a01486f387ec10d51a5c9 Mon Sep 17 00:00:00 2001 From: Vadim Pisarevsky Date: Thu, 22 Dec 2016 22:57:44 +0300 Subject: [PATCH 279/409] moved BLAS/LAPACK detection scripts from opencv_contrib/dnn to the main repository (#7918) * moved BLAS/LAPACK detection scripts from opencv_contrib/dnn to the main repository. * trying to fix the bug with undefined symbols sgesdd_ and dgesdd_ * removed extra whitespaces; disabled LAPACK on IOS --- CMakeLists.txt | 5 +- cmake/OpenCVFindAtlas.cmake | 97 +++++++++++++++++++++ cmake/OpenCVFindLAPACK.cmake | 78 +++++++++++++++++ cmake/OpenCVFindLibsPerf.cmake | 50 +++++------ cmake/OpenCVFindMKL.cmake | 136 ++++++++++++++++++++++++++++++ cmake/OpenCVFindOpenBLAS.cmake | 106 +++++++++++++++++++++++ cmake/templates/cvconfig.h.in | 6 -- modules/core/src/hal_internal.cpp | 10 +-- 8 files changed, 444 insertions(+), 44 deletions(-) create mode 100644 cmake/OpenCVFindAtlas.cmake create mode 100644 cmake/OpenCVFindLAPACK.cmake create mode 100644 cmake/OpenCVFindMKL.cmake create mode 100644 cmake/OpenCVFindOpenBLAS.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a746764f7..cc45f6f394 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,7 @@ OCV_OPTION(WITH_VA "Include VA support" OFF OCV_OPTION(WITH_VA_INTEL "Include Intel VA-API/OpenCL support" OFF IF (UNIX AND NOT ANDROID) ) OCV_OPTION(WITH_GDAL "Include GDAL Support" OFF IF (NOT ANDROID AND NOT IOS AND NOT WINRT) ) OCV_OPTION(WITH_GPHOTO2 "Include gPhoto2 library support" ON IF (UNIX AND NOT ANDROID) ) -OCV_OPTION(WITH_LAPACK "Include Lapack library support" ON IF (NOT ANDROID) ) +OCV_OPTION(WITH_LAPACK "Include Lapack library support" ON IF (NOT ANDROID AND NOT IOS) ) # OpenCV build components # =================================================== @@ -556,6 +556,7 @@ include(cmake/OpenCVFindLibsGrfmt.cmake) include(cmake/OpenCVFindLibsGUI.cmake) include(cmake/OpenCVFindLibsVideo.cmake) include(cmake/OpenCVFindLibsPerf.cmake) +include(cmake/OpenCVFindLAPACK.cmake) # ---------------------------------------------------------------------------- # Detect other 3rd-party libraries/tools @@ -1222,7 +1223,7 @@ status(" Use Intel VA-API/OpenCL:" HAVE_VA_INTEL THEN "YES (MSDK: ${VA endif(DEFINED WITH_VA_INTEL) if(DEFINED WITH_LAPACK) -status(" Use Lapack:" HAVE_LAPACK THEN "YES" ELSE NO) +status(" Use Lapack:" HAVE_LAPACK THEN "YES (${LAPACK_LIBRARIES})" ELSE NO) endif(DEFINED WITH_LAPACK) status(" Use Eigen:" HAVE_EIGEN THEN "YES (ver ${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION})" ELSE NO) diff --git a/cmake/OpenCVFindAtlas.cmake b/cmake/OpenCVFindAtlas.cmake new file mode 100644 index 0000000000..4fc7317749 --- /dev/null +++ b/cmake/OpenCVFindAtlas.cmake @@ -0,0 +1,97 @@ +#COPYRIGHT +# +#All contributions by the University of California: +#Copyright (c) 2014, 2015, The Regents of the University of California (Regents) +#All rights reserved. +# +#All other contributions: +#Copyright (c) 2014, 2015, the respective contributors +#All rights reserved. +# +#Caffe uses a shared copyright model: each contributor holds copyright over +#their contributions to Caffe. The project versioning records all such +#contribution and copyright details. If a contributor wants to further mark +#their specific copyright on a particular contribution, they should indicate +#their copyright solely in the commit message of the change when it is +#committed. +# +#LICENSE +# +#Redistribution and use in source and binary forms, with or without +#modification, are permitted provided that the following conditions are met: +# +#1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +#2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +#ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +#(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +#ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +#SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +#CONTRIBUTION AGREEMENT +# +#By contributing to the BVLC/caffe repository through pull-request, comment, +#or otherwise, the contributor releases their content to the +#license and copyright terms herein. + + +# Find the Atlas (and Lapack) libraries +# +# The following variables are optionally searched for defaults +# Atlas_ROOT_DIR: Base directory where all Atlas components are found +# +# The following are set after configuration is done: +# Atlas_FOUND +# Atlas_INCLUDE_DIRS +# Atlas_LIBRARIES +# Atlas_LIBRARYRARY_DIRS + +set(Atlas_INCLUDE_SEARCH_PATHS + /usr/include/atlas + /usr/include/atlas-base + $ENV{Atlas_ROOT_DIR} + $ENV{Atlas_ROOT_DIR}/include +) + +set(Atlas_LIB_SEARCH_PATHS + /usr/lib/atlas + /usr/lib/atlas-base + $ENV{Atlas_ROOT_DIR} + $ENV{Atlas_ROOT_DIR}/lib +) + +find_path(Atlas_CBLAS_INCLUDE_DIR NAMES cblas.h PATHS ${Atlas_INCLUDE_SEARCH_PATHS}) +find_path(Atlas_CLAPACK_INCLUDE_DIR NAMES lapacke.h PATHS ${Atlas_INCLUDE_SEARCH_PATHS}) + +find_library(Atlas_CBLAS_LIBRARY NAMES ptcblas_r ptcblas cblas_r cblas PATHS ${Atlas_LIB_SEARCH_PATHS}) +find_library(Atlas_BLAS_LIBRARY NAMES atlas_r atlas PATHS ${Atlas_LIB_SEARCH_PATHS}) +find_library(Atlas_LAPACK_LIBRARY NAMES lapack alapack_r alapack lapack_atlas PATHS ${Atlas_LIB_SEARCH_PATHS}) + +set(LOOKED_FOR + Atlas_CBLAS_INCLUDE_DIR + Atlas_CLAPACK_INCLUDE_DIR + + Atlas_CBLAS_LIBRARY + Atlas_BLAS_LIBRARY + Atlas_LAPACK_LIBRARY +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Atlas DEFAULT_MSG ${LOOKED_FOR}) + +if(ATLAS_FOUND) + set(Atlas_INCLUDE_DIR ${Atlas_CBLAS_INCLUDE_DIR} ${Atlas_CLAPACK_INCLUDE_DIR}) + set(Atlas_LIBRARIES ${Atlas_LAPACK_LIBRARY} ${Atlas_CBLAS_LIBRARY} ${Atlas_BLAS_LIBRARY}) + mark_as_advanced(${LOOKED_FOR}) + + message(STATUS "Found Atlas (include: ${Atlas_CBLAS_INCLUDE_DIR}, library: ${Atlas_BLAS_LIBRARY})") +endif(ATLAS_FOUND) \ No newline at end of file diff --git a/cmake/OpenCVFindLAPACK.cmake b/cmake/OpenCVFindLAPACK.cmake new file mode 100644 index 0000000000..8e6e82ff3b --- /dev/null +++ b/cmake/OpenCVFindLAPACK.cmake @@ -0,0 +1,78 @@ +macro(_find_file_in_dirs VAR NAME DIRS) + find_path(${VAR} ${NAME} ${DIRS} NO_DEFAULT_PATH) + set(${VAR} ${${VAR}}/${NAME}) + unset(${VAR} CACHE) +endmacro() + +if(WITH_LAPACK) + ocv_update(LAPACK_IMPL "Unknown") + if(NOT LAPACK_LIBRARIES) + include(cmake/OpenCVFindMKL.cmake) + if(HAVE_MKL) + set(LAPACK_INCLUDE_DIR ${MKL_INCLUDE_DIRS}) + set(LAPACK_LIBRARIES ${MKL_LIBRARIES} ) + set(LAPACK_CBLAS_H "mkl_cblas.h" ) + set(LAPACK_LAPACKE_H "mkl_lapacke.h" ) + set(LAPACK_IMPL "MKL") + endif() + endif() + #if(NOT LAPACK_LIBRARIES) + # include(cmake/OpenCVFindOpenBLAS.cmake) + # if(OpenBLAS_FOUND) + # set(LAPACK_INCLUDE_DIR ${OpenBLAS_INCLUDE_DIR} ) + # set(LAPACK_LIBRARIES ${OpenBLAS_LIB} ) + # set(LAPACK_CBLAS_H "cblas.h" ) + # set(LAPACK_LAPACKE_H "lapacke.h" ) + # set(LAPACK_IMPL "OpenBLAS") + # endif() + #endif() + if(NOT LAPACK_LIBRARIES AND UNIX) + include(cmake/OpenCVFindAtlas.cmake) + if(ATLAS_FOUND) + set(LAPACK_INCLUDE_DIR ${Atlas_INCLUDE_DIR}) + set(LAPACK_LIBRARIES ${Atlas_LIBRARIES} ) + set(LAPACK_CBLAS_H "cblas.h" ) + set(LAPACK_LAPACKE_H "lapacke.h" ) + set(LAPACK_IMPL "Atlas") + endif() + endif() + + if(NOT LAPACK_LIBRARIES AND APPLE) + set(LAPACK_INCLUDE_DIR "Accelerate") + set(LAPACK_LIBRARIES "-framework Accelerate") + set(LAPACK_CBLAS_H "cblas.h" ) + set(LAPACK_LAPACKE_H "lapacke.h" ) + set(LAPACK_IMPL "Apple") + endif() + + set(LAPACK_INCLUDE_DIR ${LAPACK_INCLUDE_DIR} CACHE PATH "Path to BLAS include dir" FORCE) + set(LAPACK_CBLAS_H ${LAPACK_CBLAS_H} CACHE STRING "Alternative name of cblas.h" FORCE) + set(LAPACK_LAPACKE_H ${LAPACK_LAPACKE_H} CACHE STRING "Alternative name of lapacke.h" FORCE) + set(LAPACK_LIBRARIES ${LAPACK_LIBRARIES} CACHE STRING "Names of BLAS & LAPACK binaries (.so, .dll, .a, .lib)" FORCE) + set(LAPACK_IMPL ${LAPACK_IMPL} CACHE STRING "Lapack implementation id" FORCE) + + if(LAPACK_LIBRARIES) #adding proxy cblas.h header + message(STATUS "LAPACK_IMPL: ${LAPACK_IMPL}, LAPACK_LIBRARIES: ${LAPACK_LIBRARIES}") + if("${LAPACK_IMPL}" STREQUAL "Apple") + set(CBLAS_H_PATH "Accelerate/Accelerate.h") + set(LAPACKE_H_PATH "Accelerate/Accelerate.h") + else() + _find_file_in_dirs(CBLAS_H_PATH "${LAPACK_CBLAS_H}" "${LAPACK_INCLUDE_DIR}") + _find_file_in_dirs(LAPACKE_H_PATH "${LAPACK_LAPACKE_H}" "${LAPACK_INCLUDE_DIR}") + endif() + if(NOT CBLAS_H_PATH OR NOT LAPACKE_H_PATH) + message(WARNING "CBLAS/LAPACK headers are not found in '${LAPACK_INCLUDE_DIR}'") + endif() + ocv_include_directories(${LAPACK_INCLUDE_DIR}) + list(APPEND OPENCV_LINKER_LIBS ${LAPACK_LIBRARIES}) + set(HAVE_LAPACK 1) + + set(CBLAS_H_PROXY_PATH ${CMAKE_BINARY_DIR}/opencv_lapack.h) + set(_include_str "\#include \"${CBLAS_H_PATH}\"") + if("${CBLAS_H_PATH}" STREQUAL "${LAPACKE_H_PATH}") + else() + set(_include_str "${_include_str}\n\#include \"${LAPACKE_H_PATH}\"") + endif() + file(WRITE ${CBLAS_H_PROXY_PATH} ${_include_str}) + endif() +endif() diff --git a/cmake/OpenCVFindLibsPerf.cmake b/cmake/OpenCVFindLibsPerf.cmake index 82afd390ae..70b6cf543e 100644 --- a/cmake/OpenCVFindLibsPerf.cmake +++ b/cmake/OpenCVFindLibsPerf.cmake @@ -3,33 +3,29 @@ # ---------------------------------------------------------------------------- # --- Lapack --- -if(WITH_LAPACK) - if(WIN32) - set(BLA_STATIC 1) - endif() - find_package(LAPACK) - if(LAPACK_FOUND) - find_path(LAPACKE_INCLUDE_DIR "lapacke.h") - find_path(MKL_LAPACKE_INCLUDE_DIR "mkl_lapack.h") - if(LAPACKE_INCLUDE_DIR OR MKL_LAPACKE_INCLUDE_DIR) - find_path(CBLAS_INCLUDE_DIR "cblas.h") - find_path(MKL_CBLAS_INCLUDE_DIR "mkl_cblas.h") - - if(CBLAS_INCLUDE_DIR OR MKL_CBLAS_INCLUDE_DIR) - set(HAVE_LAPACK 1) - - if(CBLAS_INCLUDE_DIR) - ocv_include_directories(${LAPACKE_INCLUDE_DIR} ${CBLAS_INCLUDE_DIR}) - set(HAVE_LAPACK_GENERIC 1) - elseif(MKL_CBLAS_INCLUDE_DIR) - ocv_include_directories(${MKL_LAPACKE_INCLUDE_DIR} ${MKL_CBLAS_INCLUDE_DIR}) - set(HAVE_LAPACK_MKL 1) - endif() - list(APPEND OPENCV_LINKER_LIBS ${LAPACK_LIBRARIES}) - endif() - endif() - endif() -endif() +# if(WITH_LAPACK) +# if(WIN32) +# set(BLA_STATIC 1) +# endif() +# find_package(LAPACK) +# if(LAPACK_FOUND) +# find_path(LAPACKE_INCLUDE_DIR "lapacke.h") +# find_path(MKL_LAPACKE_INCLUDE_DIR "mkl_lapack.h") +# if(LAPACKE_INCLUDE_DIR) +# ocv_include_directories(${LAPACKE_INCLUDE_DIR}) +# set(HAVE_LAPACK 1) +# set(HAVE_LAPACK_GENERIC 1) +# elseif(MKL_LAPACKE_INCLUDE_DIR) +# ocv_include_directories(${MKL_LAPACKE_INCLUDE_DIR}) +# set(HAVE_LAPACK 1) +# set(HAVE_LAPACK_MKL 1) +# elseif(APPLE) +# set(HAVE_LAPACK 1) +# set(HAVE_LAPACK_APPLE 1) +# endif() +# list(APPEND OPENCV_LINKER_LIBS ${LAPACK_LIBRARIES}) +# endif() +# endif() # --- TBB --- if(WITH_TBB) diff --git a/cmake/OpenCVFindMKL.cmake b/cmake/OpenCVFindMKL.cmake new file mode 100644 index 0000000000..f43ce9c286 --- /dev/null +++ b/cmake/OpenCVFindMKL.cmake @@ -0,0 +1,136 @@ +# +# The script to detect Intel(R) Math Kernel Library (MKL) +# installation/package +# +# Parameters: +# MKL_WITH_TBB +# +# On return this will define: +# +# HAVE_MKL - True if Intel IPP found +# MKL_ROOT_DIR - root of IPP installation +# MKL_INCLUDE_DIRS - IPP include folder +# MKL_LIBRARIES - IPP libraries that are used by OpenCV +# + +macro (mkl_find_lib VAR NAME DIRS) + find_path(${VAR} ${NAME} ${DIRS} NO_DEFAULT_PATH) + set(${VAR} ${${VAR}}/${NAME}) + unset(${VAR} CACHE) +endmacro() + +macro(mkl_fail) + set(HAVE_MKL OFF CACHE BOOL "True if MKL found") + set(MKL_ROOT_DIR ${MKL_ROOT_DIR} CACHE PATH "Path to MKL directory") + unset(MKL_INCLUDE_DIRS CACHE) + unset(MKL_LIBRARIES CACHE) + return() +endmacro() + +macro(get_mkl_version VERSION_FILE) + # read MKL version info from file + file(STRINGS ${VERSION_FILE} STR1 REGEX "__INTEL_MKL__") + file(STRINGS ${VERSION_FILE} STR2 REGEX "__INTEL_MKL_MINOR__") + file(STRINGS ${VERSION_FILE} STR3 REGEX "__INTEL_MKL_UPDATE__") + #file(STRINGS ${VERSION_FILE} STR4 REGEX "INTEL_MKL_VERSION") + + # extract info and assign to variables + string(REGEX MATCHALL "[0-9]+" MKL_VERSION_MAJOR ${STR1}) + string(REGEX MATCHALL "[0-9]+" MKL_VERSION_MINOR ${STR2}) + string(REGEX MATCHALL "[0-9]+" MKL_VERSION_UPDATE ${STR3}) + set(MKL_VERSION_STR "${MKL_VERSION_MAJOR}.${MKL_VERSION_MINOR}.${MKL_VERSION_UPDATE}" CACHE STRING "MKL version" FORCE) +endmacro() + + +if(NOT DEFINED MKL_USE_MULTITHREAD) + OCV_OPTION(MKL_WITH_TBB "Use MKL with TBB multithreading" OFF)#ON IF WITH_TBB) + OCV_OPTION(MKL_WITH_OPENMP "Use MKL with OpenMP multithreading" OFF)#ON IF WITH_OPENMP) +endif() + +#check current MKL_ROOT_DIR +if(NOT MKL_ROOT_DIR OR NOT EXISTS ${MKL_ROOT_DIR}/include/mkl.h) + set(mkl_root_paths ${MKL_ROOT_DIR}) + if(DEFINED $ENV{MKLROOT}) + list(APPEND mkl_root_paths $ENV{MKLROOT}) + endif() + if(WIN32) + set(ProgramFilesx86 "ProgramFiles(x86)") + list(APPEND mkl_root_paths $ENV{${ProgramFilesx86}}/IntelSWTools/compilers_and_libraries/windows/mkl) + endif() + if(UNIX) + list(APPEND mkl_root_paths "/opt/intel/mkl") + endif() + + find_path(MKL_ROOT_DIR include/mkl.h PATHS ${mkl_root_paths}) +endif() + +if(NOT MKL_ROOT_DIR) + mkl_fail() +endif() + +set(MKL_INCLUDE_DIRS ${MKL_ROOT_DIR}/include) +get_mkl_version(${MKL_INCLUDE_DIRS}/mkl_version.h) + +#determine arch +if(CMAKE_CXX_SIZEOF_DATA_PTR EQUAL 8) + set(MKL_X64 1) + set(MKL_ARCH "intel64") + + include(CheckTypeSize) + CHECK_TYPE_SIZE(int _sizeof_int) + if (_sizeof_int EQUAL 4) + set(MKL_LP64 "lp64") + else() + set(MKL_LP64 "ilp64") + endif() +else() + set(MKL_ARCH "ia32") +endif() + +if(${MKL_VERSION_STR} VERSION_GREATER "11.3.0" OR ${MKL_VERSION_STR} VERSION_EQUAL "11.3.0") + set(mkl_lib_find_paths + ${MKL_ROOT_DIR}/lib + ${MKL_ROOT_DIR}/lib/${MKL_ARCH} ${MKL_ROOT_DIR}/../tbb/lib/${MKL_ARCH}) + + set(mkl_lib_list + mkl_core + mkl_intel_${MKL_LP64}) + + if(MKL_WITH_TBB) + list(APPEND mkl_lib_list mkl_tbb_thread tbb) + elseif(MKL_WITH_OPENMP) + if(MSVC) + list(APPEND mkl_lib_list mkl_intel_thread libiomp5md) + else() + list(APPEND mkl_lib_list libmkl_gnu_thread) + endif() + else() + list(APPEND mkl_lib_list mkl_sequential) + endif() +else() + message(STATUS "MKL version ${MKL_VERSION_STR} is not supported") + mkl_fail() +endif() + + +set(MKL_LIBRARIES "") +foreach(lib ${mkl_lib_list}) + find_library(${lib} ${lib} ${mkl_lib_find_paths}) + mark_as_advanced(${lib}) + if(NOT ${lib}) + mkl_fail() + endif() + list(APPEND MKL_LIBRARIES ${${lib}}) +endforeach() + +message(STATUS "Found MKL ${MKL_VERSION_STR} at: ${MKL_ROOT_DIR}") +set(HAVE_MKL ON CACHE BOOL "True if MKL found") +set(MKL_ROOT_DIR ${MKL_ROOT_DIR} CACHE PATH "Path to MKL directory") +set(MKL_INCLUDE_DIRS ${MKL_INCLUDE_DIRS} CACHE PATH "Path to MKL include directory") +if(NOT UNIX) + set(MKL_LIBRARIES ${MKL_LIBRARIES} CACHE FILEPATH "MKL libarries") +else() + #it's ugly but helps to avoid cyclic lib problem + set(MKL_LIBRARIES ${MKL_LIBRARIES} ${MKL_LIBRARIES} ${MKL_LIBRARIES} "-lpthread" "-lm" "-ldl") + set(MKL_LIBRARIES ${MKL_LIBRARIES} CACHE STRING "MKL libarries") +endif() \ No newline at end of file diff --git a/cmake/OpenCVFindOpenBLAS.cmake b/cmake/OpenCVFindOpenBLAS.cmake new file mode 100644 index 0000000000..60594dee46 --- /dev/null +++ b/cmake/OpenCVFindOpenBLAS.cmake @@ -0,0 +1,106 @@ +#COPYRIGHT +# +#All contributions by the University of California: +#Copyright (c) 2014, 2015, The Regents of the University of California (Regents) +#All rights reserved. +# +#All other contributions: +#Copyright (c) 2014, 2015, the respective contributors +#All rights reserved. +# +#Caffe uses a shared copyright model: each contributor holds copyright over +#their contributions to Caffe. The project versioning records all such +#contribution and copyright details. If a contributor wants to further mark +#their specific copyright on a particular contribution, they should indicate +#their copyright solely in the commit message of the change when it is +#committed. +# +#LICENSE +# +#Redistribution and use in source and binary forms, with or without +#modification, are permitted provided that the following conditions are met: +# +#1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +#2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +#ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +#(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +#ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +#SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +#CONTRIBUTION AGREEMENT +# +#By contributing to the BVLC/caffe repository through pull-request, comment, +#or otherwise, the contributor releases their content to the +#license and copyright terms herein. + +SET(Open_BLAS_INCLUDE_SEARCH_PATHS + /usr/include + /usr/include/openblas + /usr/include/openblas-base + /usr/local/include + /usr/local/include/openblas + /usr/local/include/openblas-base + /opt/OpenBLAS/include + $ENV{OpenBLAS_HOME} + $ENV{OpenBLAS_HOME}/include +) + +SET(Open_BLAS_LIB_SEARCH_PATHS + /lib/ + /lib/openblas-base + /lib64/ + /usr/lib + /usr/lib/openblas-base + /usr/lib64 + /usr/local/lib + /usr/local/lib64 + /opt/OpenBLAS/lib + $ENV{OpenBLAS}cd + $ENV{OpenBLAS}/lib + $ENV{OpenBLAS_HOME} + $ENV{OpenBLAS_HOME}/lib + ) + +FIND_PATH(OpenBLAS_INCLUDE_DIR NAMES cblas.h PATHS ${Open_BLAS_INCLUDE_SEARCH_PATHS}) +FIND_LIBRARY(OpenBLAS_LIB NAMES openblas PATHS ${Open_BLAS_LIB_SEARCH_PATHS}) + +SET(OpenBLAS_FOUND ON) + +# Check include files +IF(NOT OpenBLAS_INCLUDE_DIR) + SET(OpenBLAS_FOUND OFF) + MESSAGE(STATUS "Could not find OpenBLAS include. Turning OpenBLAS_FOUND off") +ENDIF() + +# Check libraries +IF(NOT OpenBLAS_LIB) + SET(OpenBLAS_FOUND OFF) + MESSAGE(STATUS "Could not find OpenBLAS lib. Turning OpenBLAS_FOUND off") +ENDIF() + +IF (OpenBLAS_FOUND) + IF (NOT OpenBLAS_FIND_QUIETLY) + MESSAGE(STATUS "Found OpenBLAS libraries: ${OpenBLAS_LIB}") + MESSAGE(STATUS "Found OpenBLAS include: ${OpenBLAS_INCLUDE_DIR}") + ENDIF (NOT OpenBLAS_FIND_QUIETLY) +ELSE (OpenBLAS_FOUND) + IF (OpenBLAS_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find OpenBLAS") + ENDIF (OpenBLAS_FIND_REQUIRED) +ENDIF (OpenBLAS_FOUND) + +MARK_AS_ADVANCED( + OpenBLAS_INCLUDE_DIR + OpenBLAS_LIB + OpenBLAS +) \ No newline at end of file diff --git a/cmake/templates/cvconfig.h.in b/cmake/templates/cvconfig.h.in index 514d1f5c70..05add9e2c5 100644 --- a/cmake/templates/cvconfig.h.in +++ b/cmake/templates/cvconfig.h.in @@ -198,12 +198,6 @@ /* Lapack */ #cmakedefine HAVE_LAPACK -/* Lapack Generic */ -#cmakedefine HAVE_LAPACK_GENERIC - -/* Lapack MKL */ -#cmakedefine HAVE_LAPACK_MKL - /* FP16 */ #cmakedefine HAVE_FP16 diff --git a/modules/core/src/hal_internal.cpp b/modules/core/src/hal_internal.cpp index 0c6b7c01f8..b2b6dc3626 100644 --- a/modules/core/src/hal_internal.cpp +++ b/modules/core/src/hal_internal.cpp @@ -46,15 +46,7 @@ #ifdef HAVE_LAPACK -#ifdef HAVE_LAPACK_MKL - #include - #include -#endif - -#ifdef HAVE_LAPACK_GENERIC - #include - #include -#endif +#include "opencv_lapack.h" #include #include From d0f883524e70b9c93e509c2fcde964d4bf296652 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 23 Dec 2016 10:30:17 +0200 Subject: [PATCH 280/409] cmake(osx): fix CoreImage error (#7916) --- modules/imgcodecs/CMakeLists.txt | 2 +- modules/videoio/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index 5c709afaef..78006403d8 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -90,7 +90,7 @@ file(GLOB imgcodecs_ext_hdrs if(IOS) list(APPEND imgcodecs_srcs ${CMAKE_CURRENT_LIST_DIR}/src/ios_conversions.mm) - list(APPEND IMGCODECS_LIBRARIES "-framework Accelerate" "-framework CoreGraphics" "-framework CoreImage" "-framework QuartzCore" "-framework AssetsLibrary") + list(APPEND IMGCODECS_LIBRARIES "-framework Accelerate" "-framework CoreGraphics" "-framework QuartzCore" "-framework AssetsLibrary") endif() if(UNIX) diff --git a/modules/videoio/CMakeLists.txt b/modules/videoio/CMakeLists.txt index d079634b9c..fb64894899 100644 --- a/modules/videoio/CMakeLists.txt +++ b/modules/videoio/CMakeLists.txt @@ -181,7 +181,7 @@ if(HAVE_AVFOUNDATION) list(APPEND VIDEOIO_LIBRARIES "-framework AVFoundation" "-framework QuartzCore") else() list(APPEND videoio_srcs ${CMAKE_CURRENT_LIST_DIR}/src/cap_avfoundation_mac.mm) - list(APPEND VIDEOIO_LIBRARIES "-framework Cocoa" "-framework Accelerate" "-framework AVFoundation" "-framework CoreGraphics" "-framework CoreImage" "-framework CoreMedia" "-framework CoreVideo" "-framework QuartzCore") + list(APPEND VIDEOIO_LIBRARIES "-framework Cocoa" "-framework Accelerate" "-framework AVFoundation" "-framework CoreGraphics" "-framework CoreMedia" "-framework CoreVideo" "-framework QuartzCore") endif() endif() From 0bd68671bd90b03089aab25924150160aa79c804 Mon Sep 17 00:00:00 2001 From: Vadim Pisarevsky Date: Fri, 23 Dec 2016 15:40:51 +0300 Subject: [PATCH 281/409] restored OpenBLAS detection; fixed MKL's lapack header (#7921) --- cmake/OpenCVFindLAPACK.cmake | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cmake/OpenCVFindLAPACK.cmake b/cmake/OpenCVFindLAPACK.cmake index 8e6e82ff3b..dfacf24328 100644 --- a/cmake/OpenCVFindLAPACK.cmake +++ b/cmake/OpenCVFindLAPACK.cmake @@ -12,20 +12,20 @@ if(WITH_LAPACK) set(LAPACK_INCLUDE_DIR ${MKL_INCLUDE_DIRS}) set(LAPACK_LIBRARIES ${MKL_LIBRARIES} ) set(LAPACK_CBLAS_H "mkl_cblas.h" ) - set(LAPACK_LAPACKE_H "mkl_lapacke.h" ) + set(LAPACK_LAPACKE_H "mkl_lapack.h" ) set(LAPACK_IMPL "MKL") endif() endif() - #if(NOT LAPACK_LIBRARIES) - # include(cmake/OpenCVFindOpenBLAS.cmake) - # if(OpenBLAS_FOUND) - # set(LAPACK_INCLUDE_DIR ${OpenBLAS_INCLUDE_DIR} ) - # set(LAPACK_LIBRARIES ${OpenBLAS_LIB} ) - # set(LAPACK_CBLAS_H "cblas.h" ) - # set(LAPACK_LAPACKE_H "lapacke.h" ) - # set(LAPACK_IMPL "OpenBLAS") - # endif() - #endif() + if(NOT LAPACK_LIBRARIES) + include(cmake/OpenCVFindOpenBLAS.cmake) + if(OpenBLAS_FOUND) + set(LAPACK_INCLUDE_DIR ${OpenBLAS_INCLUDE_DIR} ) + set(LAPACK_LIBRARIES ${OpenBLAS_LIB} ) + set(LAPACK_CBLAS_H "cblas.h" ) + set(LAPACK_LAPACKE_H "lapacke.h" ) + set(LAPACK_IMPL "OpenBLAS") + endif() + endif() if(NOT LAPACK_LIBRARIES AND UNIX) include(cmake/OpenCVFindAtlas.cmake) if(ATLAS_FOUND) From 70bbf17b133496bd7d54d034b0f94bd869e0e810 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 23 Dec 2016 15:54:44 +0300 Subject: [PATCH 282/409] OpenCV Version++ 3.2.0 --- modules/core/include/opencv2/core/version.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/version.hpp b/modules/core/include/opencv2/core/version.hpp index 1794dba4c0..b4c8f25e29 100644 --- a/modules/core/include/opencv2/core/version.hpp +++ b/modules/core/include/opencv2/core/version.hpp @@ -53,7 +53,7 @@ #define CV_VERSION_MAJOR 3 #define CV_VERSION_MINOR 2 #define CV_VERSION_REVISION 0 -#define CV_VERSION_STATUS "-rc" +#define CV_VERSION_STATUS "" #define CVAUX_STR_EXP(__A) #__A #define CVAUX_STR(__A) CVAUX_STR_EXP(__A) From b7a053b0e5682f4c2bbfa9c7365df0cbbf080203 Mon Sep 17 00:00:00 2001 From: Raj Natarajan Date: Mon, 26 Dec 2016 00:44:43 -0600 Subject: [PATCH 283/409] Issue-7310: Updated imwrite doc to correct default setting for IMWRITE_PNG_COMPRESSION and clarified existing doc a bit. --- modules/imgcodecs/include/opencv2/imgcodecs.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 6359de6e51..79805b2ed3 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -85,7 +85,7 @@ enum ImwriteFlags { IMWRITE_JPEG_RST_INTERVAL = 4, //!< JPEG restart interval, 0 - 65535, default is 0 - no restart. IMWRITE_JPEG_LUMA_QUALITY = 5, //!< Separate luma quality level, 0 - 100, default is 0 - don't use. IMWRITE_JPEG_CHROMA_QUALITY = 6, //!< Separate chroma quality level, 0 - 100, default is 0 - don't use. - IMWRITE_PNG_COMPRESSION = 16, //!< For PNG, it can be the compression level from 0 to 9. A higher value means a smaller size and longer compression time. Default value is 3. Also strategy is changed to IMWRITE_PNG_STRATEGY_DEFAULT (Z_DEFAULT_STRATEGY). + IMWRITE_PNG_COMPRESSION = 16, //!< For PNG, it can be the compression level from 0 to 9. A higher value means a smaller size and longer compression time. If specified, strategy is changed to IMWRITE_PNG_STRATEGY_DEFAULT (Z_DEFAULT_STRATEGY). Default value is 1 (best speed setting). IMWRITE_PNG_STRATEGY = 17, //!< One of cv::ImwritePNGFlags, default is IMWRITE_PNG_STRATEGY_DEFAULT. IMWRITE_PNG_BILEVEL = 18, //!< Binary level PNG, 0 or 1, default is 0. IMWRITE_PXM_BINARY = 32, //!< For PPM, PGM, or PBM, it can be a binary format flag, 0 or 1. Default value is 1. From d015b55e6bdd5fb923acbd6d5f868be22c8e91d5 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 28 Dec 2016 13:06:28 +0300 Subject: [PATCH 284/409] highgui: preserve waitKey -1 return value --- modules/highgui/src/window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index 9306f8ef5e..35ce8ca265 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -218,7 +218,7 @@ int cv::waitKey(int delay) if (use_legacy > 0) return code; #endif - return code & 0xff; + return (code != -1) ? (code & 0xff) : -1; } int cv::createTrackbar(const String& trackbarName, const String& winName, From 5fad2a3f24b6b1e41a4f6783decaf08967de5c0d Mon Sep 17 00:00:00 2001 From: wykvictor Date: Thu, 29 Dec 2016 16:46:35 +0800 Subject: [PATCH 285/409] Speedup MedianFilter::apply() when calling repeatedly Speedup MedianFilter::apply(), avoid to newly create the two GpuMat every time calling filter->apply(), which is very time consuming. --- modules/cudafilters/src/filtering.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/cudafilters/src/filtering.cpp b/modules/cudafilters/src/filtering.cpp index 21efde0103..d01504b2dd 100644 --- a/modules/cudafilters/src/filtering.cpp +++ b/modules/cudafilters/src/filtering.cpp @@ -1068,6 +1068,8 @@ namespace private: int windowSize; int partitions; + GpuMat devHist; + GpuMat devCoarseHist; }; MedianFilter::MedianFilter(int srcType, int _windowSize, int _partitions) : @@ -1098,10 +1100,13 @@ namespace // Note - these are hardcoded in the actual GPU kernel. Do not change these values. int histSize=256, histCoarseSize=8; - - BufferPool pool(_stream); - GpuMat devHist = pool.getBuffer(1, src.cols*histSize*partitions,CV_32SC1); - GpuMat devCoarseHist = pool.getBuffer(1,src.cols*histCoarseSize*partitions,CV_32SC1); + int devHistCols = src.cols*histSize*partitions, devCoarseHistCols = src.cols*histCoarseSize*partitions; + if(devHist.empty() || devCoarseHist.empty() || devHist.cols != devHistCols || devCoarseHist.cols != devCoarseHistCols) + { + BufferPool pool(_stream); + devHist = pool.getBuffer(1, devHistCols, CV_32SC1); + devCoarseHist = pool.getBuffer(1, devCoarseHistCols, CV_32SC1); + } devHist.setTo(0, _stream); devCoarseHist.setTo(0, _stream); From c6f666a02bfc30e6dff6f2687551424701e33063 Mon Sep 17 00:00:00 2001 From: Yakun Wang Date: Thu, 29 Dec 2016 18:29:44 +0800 Subject: [PATCH 286/409] BufferPool is used for temporary buffer, use mat create directly --- modules/cudafilters/src/filtering.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/modules/cudafilters/src/filtering.cpp b/modules/cudafilters/src/filtering.cpp index d01504b2dd..1afd9cd763 100644 --- a/modules/cudafilters/src/filtering.cpp +++ b/modules/cudafilters/src/filtering.cpp @@ -1100,13 +1100,9 @@ namespace // Note - these are hardcoded in the actual GPU kernel. Do not change these values. int histSize=256, histCoarseSize=8; - int devHistCols = src.cols*histSize*partitions, devCoarseHistCols = src.cols*histCoarseSize*partitions; - if(devHist.empty() || devCoarseHist.empty() || devHist.cols != devHistCols || devCoarseHist.cols != devCoarseHistCols) - { - BufferPool pool(_stream); - devHist = pool.getBuffer(1, devHistCols, CV_32SC1); - devCoarseHist = pool.getBuffer(1, devCoarseHistCols, CV_32SC1); - } + + devHist.create(1, src.cols*histSize*partitions, CV_32SC1); + devCoarseHist.create(1, src.cols*histCoarseSize*partitions, CV_32SC1); devHist.setTo(0, _stream); devCoarseHist.setTo(0, _stream); From 8a6fabed83b3bf7eea5413a7de62f5e12c1afbe8 Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 29 Dec 2016 17:23:40 -0800 Subject: [PATCH 287/409] Avoid segmentation fault Change `<=` to `<` to avoid accessing the first character of an empty string --- modules/core/src/command_line_parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/command_line_parser.cpp b/modules/core/src/command_line_parser.cpp index 542b231ff9..4fc62238b8 100644 --- a/modules/core/src/command_line_parser.cpp +++ b/modules/core/src/command_line_parser.cpp @@ -10,7 +10,7 @@ static const char* noneValue = ""; static String cat_string(const String& str) { int left = 0, right = (int)str.length(); - while( left <= right && str[left] == ' ' ) + while( left < right && str[left] == ' ' ) left++; while( right > left && str[right-1] == ' ' ) right--; From 579ffbf97e35527becf95f56571dce46e1182ab1 Mon Sep 17 00:00:00 2001 From: utibenkei Date: Sat, 31 Dec 2016 01:54:07 +0900 Subject: [PATCH 288/409] Added Java wrapping for tracking module --- .../misc/java/src/java/core+MatOfRect2d.java | 81 ++++++++++++++ .../core/misc/java/src/java/core+Rect2d.java | 100 ++++++++++++++++++ modules/java/generator/gen_java.py | 6 +- modules/java/generator/src/cpp/converters.cpp | 13 +++ modules/java/generator/src/cpp/converters.h | 3 + .../generator/src/java/utils+Converters.java | 37 +++++++ 6 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 modules/core/misc/java/src/java/core+MatOfRect2d.java create mode 100644 modules/core/misc/java/src/java/core+Rect2d.java diff --git a/modules/core/misc/java/src/java/core+MatOfRect2d.java b/modules/core/misc/java/src/java/core+MatOfRect2d.java new file mode 100644 index 0000000000..71c4b1aef6 --- /dev/null +++ b/modules/core/misc/java/src/java/core+MatOfRect2d.java @@ -0,0 +1,81 @@ +package org.opencv.core; + +import java.util.Arrays; +import java.util.List; + + +public class MatOfRect2d extends Mat { + // 64FC4 + private static final int _depth = CvType.CV_64F; + private static final int _channels = 4; + + public MatOfRect2d() { + super(); + } + + protected MatOfRect2d(long addr) { + super(addr); + if( !empty() && checkVector(_channels, _depth) < 0 ) + throw new IllegalArgumentException("Incompatible Mat"); + //FIXME: do we need release() here? + } + + public static MatOfRect2d fromNativeAddr(long addr) { + return new MatOfRect2d(addr); + } + + public MatOfRect2d(Mat m) { + super(m, Range.all()); + if( !empty() && checkVector(_channels, _depth) < 0 ) + throw new IllegalArgumentException("Incompatible Mat"); + //FIXME: do we need release() here? + } + + public MatOfRect2d(Rect2d...a) { + super(); + fromArray(a); + } + + public void alloc(int elemNumber) { + if(elemNumber>0) + super.create(elemNumber, 1, CvType.makeType(_depth, _channels)); + } + + public void fromArray(Rect2d...a) { + if(a==null || a.length==0) + return; + int num = a.length; + alloc(num); + double buff[] = new double[num * _channels]; + for(int i=0; i lr) { + Rect2d ap[] = lr.toArray(new Rect2d[0]); + fromArray(ap); + } + + public List toList() { + Rect2d[] ar = toArray(); + return Arrays.asList(ar); + } +} diff --git a/modules/core/misc/java/src/java/core+Rect2d.java b/modules/core/misc/java/src/java/core+Rect2d.java new file mode 100644 index 0000000000..cb83a97727 --- /dev/null +++ b/modules/core/misc/java/src/java/core+Rect2d.java @@ -0,0 +1,100 @@ +package org.opencv.core; + +//javadoc:Rect2d_ +public class Rect2d { + + public double x, y, width, height; + + public Rect2d(double x, double y, double width, double height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public Rect2d() { + this(0, 0, 0, 0); + } + + public Rect2d(Point p1, Point p2) { + x = (double) (p1.x < p2.x ? p1.x : p2.x); + y = (double) (p1.y < p2.y ? p1.y : p2.y); + width = (double) (p1.x > p2.x ? p1.x : p2.x) - x; + height = (double) (p1.y > p2.y ? p1.y : p2.y) - y; + } + + public Rect2d(Point p, Size s) { + this((double) p.x, (double) p.y, (double) s.width, (double) s.height); + } + + public Rect2d(double[] vals) { + set(vals); + } + + public void set(double[] vals) { + if (vals != null) { + x = vals.length > 0 ? (double) vals[0] : 0; + y = vals.length > 1 ? (double) vals[1] : 0; + width = vals.length > 2 ? (double) vals[2] : 0; + height = vals.length > 3 ? (double) vals[3] : 0; + } else { + x = 0; + y = 0; + width = 0; + height = 0; + } + } + + public Rect2d clone() { + return new Rect2d(x, y, width, height); + } + + public Point tl() { + return new Point(x, y); + } + + public Point br() { + return new Point(x + width, y + height); + } + + public Size size() { + return new Size(width, height); + } + + public double area() { + return width * height; + } + + public boolean contains(Point p) { + return x <= p.x && p.x < x + width && y <= p.y && p.y < y + height; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(height); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(width); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(x); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(y); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Rect2d)) return false; + Rect2d it = (Rect2d) obj; + return x == it.x && y == it.y && width == it.width && height == it.height; + } + + @Override + public String toString() { + return "{" + x + ", " + y + ", " + width + "x" + height + "}"; + } +} diff --git a/modules/java/generator/gen_java.py b/modules/java/generator/gen_java.py index fc8cc1440b..3c8da0b450 100755 --- a/modules/java/generator/gen_java.py +++ b/modules/java/generator/gen_java.py @@ -207,6 +207,7 @@ type_dict = { "vector_KeyPoint" : { "j_type" : "MatOfKeyPoint", "jn_type" : "long", "jni_type" : "jlong", "jni_var" : "std::vector %(n)s", "suffix" : "J" }, "vector_DMatch" : { "j_type" : "MatOfDMatch", "jn_type" : "long", "jni_type" : "jlong", "jni_var" : "std::vector %(n)s", "suffix" : "J" }, "vector_Rect" : { "j_type" : "MatOfRect", "jn_type" : "long", "jni_type" : "jlong", "jni_var" : "std::vector %(n)s", "suffix" : "J" }, + "vector_Rect2d" : { "j_type" : "MatOfRect2d", "jn_type" : "long", "jni_type" : "jlong", "jni_var" : "std::vector %(n)s", "suffix" : "J" }, "vector_uchar" : { "j_type" : "MatOfByte", "jn_type" : "long", "jni_type" : "jlong", "jni_var" : "std::vector %(n)s", "suffix" : "J" }, "vector_char" : { "j_type" : "MatOfByte", "jn_type" : "long", "jni_type" : "jlong", "jni_var" : "std::vector %(n)s", "suffix" : "J" }, "vector_int" : { "j_type" : "MatOfInt", "jn_type" : "long", "jni_type" : "jlong", "jni_var" : "std::vector %(n)s", "suffix" : "J" }, @@ -261,6 +262,9 @@ type_dict = { "Rect" : { "j_type" : "Rect", "jn_args" : (("int", ".x"), ("int", ".y"), ("int", ".width"), ("int", ".height")), "jni_var" : "Rect %(n)s(%(n)s_x, %(n)s_y, %(n)s_width, %(n)s_height)", "jni_type" : "jdoubleArray", "suffix" : "IIII"}, + "Rect2d" : { "j_type" : "Rect2d", "jn_args" : (("double", ".x"), ("double", ".y"), ("double", ".width"), ("double", ".height")), + "jni_var" : "Rect %(n)s(%(n)s_x, %(n)s_y, %(n)s_width, %(n)s_height)", "jni_type" : "jdoubleArray", + "suffix" : "DDDD"}, "Size" : { "j_type" : "Size", "jn_args" : (("double", ".width"), ("double", ".height")), "jni_var" : "Size %(n)s((int)%(n)s_width, (int)%(n)s_height)", "jni_type" : "jdoubleArray", "suffix" : "DD"}, @@ -825,7 +829,7 @@ class ClassInfo(GeneralInfo): j_type = type_dict[ctype]['j_type'] elif ctype in ("Algorithm"): j_type = ctype - if j_type in ( "CvType", "Mat", "Point", "Point3", "Range", "Rect", "RotatedRect", "Scalar", "Size", "TermCriteria", "Algorithm" ): + if j_type in ( "CvType", "Mat", "Point", "Point3", "Range", "Rect", "Rect2d", "RotatedRect", "Scalar", "Size", "TermCriteria", "Algorithm" ): self.imports.add("org.opencv.core." + j_type) if j_type == 'String': self.imports.add("java.lang.String") diff --git a/modules/java/generator/src/cpp/converters.cpp b/modules/java/generator/src/cpp/converters.cpp index 3c771ce734..7ce7456525 100644 --- a/modules/java/generator/src/cpp/converters.cpp +++ b/modules/java/generator/src/cpp/converters.cpp @@ -92,6 +92,19 @@ void vector_Rect_to_Mat(std::vector& v_rect, Mat& mat) mat = Mat(v_rect, true); } +//vector_Rect2d + +void Mat_to_vector_Rect2d(Mat& mat, std::vector& v_rect) +{ + v_rect.clear(); + CHECK_MAT(mat.type()==CV_64FC4 && mat.cols==1); + v_rect = (std::vector) mat; +} + +void vector_Rect2d_to_Mat(std::vector& v_rect, Mat& mat) +{ + mat = Mat(v_rect, true); +} //vector_Point void Mat_to_vector_Point(Mat& mat, std::vector& v_point) diff --git a/modules/java/generator/src/cpp/converters.h b/modules/java/generator/src/cpp/converters.h index 257f9449e3..25077b10c2 100644 --- a/modules/java/generator/src/cpp/converters.h +++ b/modules/java/generator/src/cpp/converters.h @@ -19,6 +19,9 @@ void vector_char_to_Mat(std::vector& v_char, cv::Mat& mat); void Mat_to_vector_Rect(cv::Mat& mat, std::vector& v_rect); void vector_Rect_to_Mat(std::vector& v_rect, cv::Mat& mat); +void Mat_to_vector_Rect2d(cv::Mat& mat, std::vector& v_rect); +void vector_Rect2d_to_Mat(std::vector& v_rect, cv::Mat& mat); + void Mat_to_vector_Point(cv::Mat& mat, std::vector& v_point); void Mat_to_vector_Point2f(cv::Mat& mat, std::vector& v_point); diff --git a/modules/java/generator/src/java/utils+Converters.java b/modules/java/generator/src/java/utils+Converters.java index bd3bb64927..c0575a6665 100644 --- a/modules/java/generator/src/java/utils+Converters.java +++ b/modules/java/generator/src/java/utils+Converters.java @@ -14,6 +14,7 @@ import org.opencv.core.MatOfPoint3f; import org.opencv.core.Point; import org.opencv.core.Point3; import org.opencv.core.Rect; +import org.opencv.core.Rect2d; import org.opencv.core.DMatch; import org.opencv.core.KeyPoint; @@ -435,6 +436,42 @@ public class Converters { } } + public static Mat vector_Rect2d_to_Mat(List rs) { + Mat res; + int count = (rs != null) ? rs.size() : 0; + if (count > 0) { + res = new Mat(count, 1, CvType.CV_64FC4); + double[] buff = new double[4 * count]; + for (int i = 0; i < count; i++) { + Rect2d r = rs.get(i); + buff[4 * i] = r.x; + buff[4 * i + 1] = r.y; + buff[4 * i + 2] = r.width; + buff[4 * i + 3] = r.height; + } + res.put(0, 0, buff); + } else { + res = new Mat(); + } + return res; + } + + public static void Mat_to_vector_Rect2d(Mat m, List rs) { + if (rs == null) + throw new java.lang.IllegalArgumentException("rs == null"); + int count = m.rows(); + if (CvType.CV_64FC4 != m.type() || m.cols() != 1) + throw new java.lang.IllegalArgumentException( + "CvType.CV_64FC4 != m.type() || m.rows()!=1\n" + m); + + rs.clear(); + double[] buff = new double[4 * count]; + m.get(0, 0, buff); + for (int i = 0; i < count; i++) { + rs.add(new Rect2d(buff[4 * i], buff[4 * i + 1], buff[4 * i + 2], buff[4 * i + 3])); + } + } + public static Mat vector_KeyPoint_to_Mat(List kps) { Mat res; int count = (kps != null) ? kps.size() : 0; From d1f727191fbe44e8d260e93204e8886e52e065f2 Mon Sep 17 00:00:00 2001 From: BadrinathS Date: Sat, 31 Dec 2016 16:37:51 +0530 Subject: [PATCH 289/409] Resolving issue #7924 --- modules/ml/src/lr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ml/src/lr.cpp b/modules/ml/src/lr.cpp index 99692f3d16..57fcac7ef0 100644 --- a/modules/ml/src/lr.cpp +++ b/modules/ml/src/lr.cpp @@ -311,7 +311,7 @@ Mat LogisticRegressionImpl::calc_sigmoid(const Mat& data) const double LogisticRegressionImpl::compute_cost(const Mat& _data, const Mat& _labels, const Mat& _init_theta) { - int llambda = 0; + float llambda = 0; /*changed llambda from int to float to solve issue #7924*/ int m; int n; double cost = 0; From c3a8db6d6c7ab1abd78d9c1b5ba3de16842a65d1 Mon Sep 17 00:00:00 2001 From: Matt Bennett Date: Mon, 2 Jan 2017 20:28:07 +0000 Subject: [PATCH 290/409] Merge pull request #7952 from mattmyne:JSONWriteFixTrailingDecimalPoint Append zero to trailing decimal place for FileStorage JSON write of a float or double value (#7952) * Fix for FileStorage JSON write of a float or double value that has no fractional part; appends a zero character after the trailing decimal place to meet JSON standard. * strlen return to size_t type rather than unnecessary cast to int --- modules/core/src/persistence.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index ccdb135ec5..bf16c2f398 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -4025,7 +4025,14 @@ static void icvJSONWriteReal( CvFileStorage* fs, const char* key, double value ) { char buf[128]; - icvJSONWrite( fs, key, icvDoubleToString( buf, value )); + size_t len = strlen( icvDoubleToString( buf, value ) ); + if( len > 0 && buf[len-1] == '.' ) + { + // append zero if string ends with decimal place to match JSON standard + buf[len] = '0'; + buf[len+1] = '\0'; + } + icvJSONWrite( fs, key, buf ); } @@ -4829,6 +4836,17 @@ cvWriteRawData( CvFileStorage* fs, const void* _data, int len, const char* dt ) } else { + if( elem_type == CV_32F || elem_type == CV_64F ) + { + size_t buf_len = strlen(ptr); + if( buf_len > 0 && ptr[buf_len-1] == '.' ) + { + // append zero if CV_32F or CV_64F string ends with decimal place to match JSON standard + // ptr will point to buf, so can write to buf given ptr is const + buf[buf_len] = '0'; + buf[buf_len+1] = '\0'; + } + } icvJSONWrite( fs, 0, ptr ); } } From a85b4b5805ab97a6afad5618f799fffeb5cfcf53 Mon Sep 17 00:00:00 2001 From: Bryce Evans Date: Mon, 2 Jan 2017 19:00:12 -0800 Subject: [PATCH 291/409] Merge pull request #7942 from bae43:fix-typos Fix Documentation Typos (#7942) --- .../windows_install/windows_install.markdown | 20 +++++++------- .../windows_visual_studio_Opencv.markdown | 27 ++++++++++--------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/doc/tutorials/introduction/windows_install/windows_install.markdown b/doc/tutorials/introduction/windows_install/windows_install.markdown index 179f430e23..e4e969f432 100644 --- a/doc/tutorials/introduction/windows_install/windows_install.markdown +++ b/doc/tutorials/introduction/windows_install/windows_install.markdown @@ -67,14 +67,14 @@ of them, you need to download and install them on your system. - [Numpy](http://numpy.scipy.org/) is a scientific computing package for Python. Required for the *Python interface*. - [Intel Threading Building Blocks (*TBB*)](http://threadingbuildingblocks.org/file.php?fid=77) is used inside OpenCV for parallel code snippets. Using this will make sure that the OpenCV library will take advantage of all the cores - you have in your systems CPU. + you have in your system's CPU. - [Intel Integrated Performance Primitives (*IPP*)](http://software.intel.com/en-us/articles/intel-ipp/) may be used to improve the performance of color conversion, Haar training and DFT functions of the OpenCV library. Watch out, since this isn't a free service. - [Intel IPP Asynchronous C/C++](http://software.intel.com/en-us/intel-ipp-preview) is currently focused delivering Intel Graphics support for advanced image processing and computer vision functions. - OpenCV offers a somewhat fancier and more useful graphical user interface, than the default one - by using the [Qt framework](http://qt.nokia.com/downloads). For a quick overview of what this has to offer look into the + by using the [Qt framework](http://qt.nokia.com/downloads). For a quick overview of what this has to offer, look into the documentations *highgui* module, under the *Qt New Functions* section. Version 4.6 or later of the framework is required. - [Eigen](http://eigen.tuxfamily.org/index.php?title=Main_Page#Download) is a C++ template library for linear algebra. @@ -83,7 +83,7 @@ of them, you need to download and install them on your system. more of our algorithms to work on the GPUs is a constant effort of the OpenCV team. - [OpenEXR](http://www.openexr.com/downloads.html) source files are required for the library to work with this high dynamic range (HDR) image file format. -- The OpenNI Framework contains a set of open source APIs that provide support for natural interaction with devices via methods such as voice command recognition, hand gestures and body +- The OpenNI Framework contains a set of open source APIs that provide support for natural interaction with devices via methods such as voice command recognition, hand gestures, and body motion tracking. Prebuilt binaries can be found [here](http://structure.io/openni). The source code of [OpenNI](https://github.com/OpenNI/OpenNI) and [OpenNI2](https://github.com/OpenNI/OpenNI2) are also available on Github. - [Miktex]( http://miktex.org/2.9/setup) is the best [TEX](https://secure.wikimedia.org/wikipedia/en/wiki/TeX) implementation on the Windows OS. It is required to build the *OpenCV documentation*. @@ -203,7 +203,7 @@ libraries). If you do not need the support for some of these you can just freely @code{.bash} nmake @endcode - After this set the Qt enviroment variables using the following command on Windows 7: + After this set the Qt environment variables using the following command on Windows 7: @code{.bash} setx -m QTDIR D:/OpenCV/dep/qt/qt-everywhere-opensource-src-4.7.3 @endcode @@ -266,8 +266,8 @@ libraries). If you do not need the support for some of these you can just freely fully functional on your computer. - *BUILD_PACKAGE* -\> Prior to version 2.3 with this you could build a project that will build an OpenCV installer. With this you can easily install your OpenCV flavor on other - systems. For the latest source files of OpenCV it generates a new project that simply - creates zip archive with OpenCV sources. + systems. For the latest source files of OpenCV, it generates a new project that simply + creates a zip archive with OpenCV sources. - *BUILD_SHARED_LIBS* -\> With this you can control to build DLL files (when turned on) or static library files (\*.lib) otherwise. - *BUILD_TESTS* -\> Each module of OpenCV has a test project assigned to it. Building these @@ -291,7 +291,7 @@ libraries). If you do not need the support for some of these you can just freely ![](images/OpenCVBuildResultWindows.jpg) - For the documentation you need to explicitly issue the build commands on the *doc* project for + For the documentation, you need to explicitly issue the build commands on the *doc* project for the PDF files and on the *doc_html* for the HTML ones. Each of these will call *Sphinx* to do all the hard work. You can find the generated documentation inside the `Build/Doc/_html` for the HTML pages and within the `Build/Doc` the PDF manuals. @@ -299,7 +299,7 @@ libraries). If you do not need the support for some of these you can just freely ![](images/WindowsBuildDoc.png) To collect the header and the binary files, that you will use during your own projects, into a - separate directory (simillary to how the pre-built binaries ship) you need to explicitely build + separate directory (simillary to how the pre-built binaries ship) you need to explicitly build the *Install* project. ![](images/WindowsBuildInstall.png) @@ -321,10 +321,10 @@ libraries). If you do not need the support for some of these you can just freely caused mostly by old video card drivers. For testing the GPU (if built) run the *performance_gpu.exe* sample application. -Set the OpenCV enviroment variable and add it to the systems path {#tutorial_windows_install_path} +Set the OpenCV environment variable and add it to the systems path {#tutorial_windows_install_path} ================================================================= -First we set an enviroment variable to make easier our work. This will hold the build directory of +First we set an environment variable to make easier our work. This will hold the build directory of our OpenCV library that we use in our projects. Start up a command window and enter: @code setx -m OPENCV_DIR D:\OpenCV\Build\x86\vc10 (suggested for Visual Studio 2010 - 32 bit Windows) diff --git a/doc/tutorials/introduction/windows_visual_studio_Opencv/windows_visual_studio_Opencv.markdown b/doc/tutorials/introduction/windows_visual_studio_Opencv/windows_visual_studio_Opencv.markdown index 3be7f5bb87..2c9d1903db 100644 --- a/doc/tutorials/introduction/windows_visual_studio_Opencv/windows_visual_studio_Opencv.markdown +++ b/doc/tutorials/introduction/windows_visual_studio_Opencv/windows_visual_studio_Opencv.markdown @@ -11,7 +11,7 @@ header files plus binaries and you have set the environment variables as describ The OpenCV libraries, distributed by us, on the Microsoft Windows operating system are in a Dynamic Linked Libraries (*DLL*). These have the advantage that all the content of the -library are loaded only at runtime, on demand, and that countless programs may use the same library +library is loaded only at runtime, on demand, and that countless programs may use the same library file. This means that if you have ten applications using the OpenCV library, no need to have around a version for each one of them. Of course you need to have the *dll* of the OpenCV on all systems where you want to run your application. @@ -42,9 +42,9 @@ To build an application with OpenCV you need to do two things: extension libraries. The good part is that at runtime only the *DLL* is required. To pass on all this information to the Visual Studio IDE you can either do it globally (so all your -future projects will get these information) or locally (so only for you current project). The +future projects will get this information) or locally (so only for you current project). The advantage of the global one is that you only need to do it once; however, it may be undesirable to -clump all your projects all the time with all these information. In case of the global one how you +clump all your projects all the time with all this information. In case of the global one how you do it depends on the Microsoft Visual Studio you use. There is a **2008 and previous versions** and a **2010 way** of doing it. Inside the global section of this tutorial I'll show what the main differences are. @@ -53,7 +53,7 @@ The base item of a project in Visual Studio is a solution. A solution may contai Projects are the building blocks of an application. Every project will realize something and you will have a main project in which you can put together this project puzzle. In case of the many simple applications (like many of the tutorials will be) you do not need to break down the -application into modules. In these cases your main project will be the only existing one. Now go +application into modules. In these cases, your main project will be the only existing one. Now go create a new solution inside Visual studio by going through the File --\> New --\> Project menu selection. Choose *Win32 Console Application* as type. Enter its name and select the path where to create it. Then in the upcoming dialog make sure you create an empty project. @@ -91,7 +91,7 @@ projects with custom rules that I do not use it. Go the C++ groups General entry *"Additional Include Directories"* add the path to your OpenCV include. If you don't have *"C/C++"* group, you should add any .c/.cpp file to the project. @code{.bash} -\f$(OPENCV_DIR)\..\..\include +$(OPENCV_DIR)\..\..\include @endcode ![](images/PropertySheetOpenCVInclude.jpg) @@ -152,7 +152,7 @@ them. ![](images/PropertySheetOpenCVLibrariesRelease.jpg) -You can find your property sheets inside your projects directory. At this point it is a wise +You can find your property sheets inside your projects directory. At this point, it is a wise decision to back them up into some special directory, to always have them at hand in the future, whenever you create an OpenCV project. Note that for Visual Studio 2010 the file extension is *props*, while for 2008 this is *vsprops*. @@ -167,7 +167,7 @@ entry inside the Property Manager to easily add the OpenCV build rules. The *global* method ------------------- -In case you find to troublesome to add the property pages to each and every one of your projects you +In case you find it too troublesome to add the property pages to each and every one of your projects you can also add this rules to a *"global property page"*. However, this applies only to the additional include and library directories. The name of the libraries to use you still need to specify manually by using for instance: a Property page. @@ -202,10 +202,11 @@ current working directory is the projects directory, while otherwise it is the f application file currently is (so usually your build directory). Moreover, in case of starting from the *IDE* the console window will not close once finished. It will wait for a keystroke of yours. -This is important to remember when you code inside the code open and save commands. You're resources +This is important to remember when you code inside the code open and save commands. Your resources will be saved ( and queried for at opening!!!) relatively to your working directory. This is unless -you give a full, explicit path as parameter for the I/O functions. In the code above we open [this -OpenCV logo](https://github.com/opencv/opencv/tree/master/samples/data/opencv-logo.png). Before starting up the application make sure you place +you give a full, explicit path as a parameter for the I/O functions. In the code above we open [this +OpenCV logo](https://github.com/opencv/opencv/tree/master/samples/data/opencv-logo.png). Before starting up the application, +make sure you place the image file in your current working directory. Modify the image file name inside the code to try it out on other images too. Run it and voil á: @@ -214,8 +215,8 @@ it out on other images too. Run it and voil á: Command line arguments with Visual Studio ----------------------------------------- -Throughout some of our future tutorials you'll see that the programs main input method will be by -giving a runtime argument. To do this you can just start up a commmand windows (cmd + Enter in the +Throughout some of our future tutorials, you'll see that the programs main input method will be by +giving a runtime argument. To do this you can just start up a command windows (cmd + Enter in the start menu), navigate to your executable file and start it with an argument. So for example in case of my upper project this would look like: @code{.bash} @@ -232,5 +233,5 @@ cumbersome task. Luckily, in the Visual Studio there is a menu to automate all t ![](images/VisualStudioCommandLineArguments.jpg) Specify here the name of the inputs and while you start your application from the Visual Studio -enviroment you have automatic argument passing. In the next introductionary tutorial you'll see an +environment you have automatic argument passing. In the next introductory tutorial you'll see an in-depth explanation of the upper source code: @ref tutorial_display_image. From 3650ec02be2eef242c6932611930b65521b6a2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorena=20Garc=C3=ADa?= Date: Tue, 3 Jan 2017 18:34:04 +0100 Subject: [PATCH 292/409] HitMiss tutorial --- .../imgproc/hitOrMiss/hitOrMiss.markdown | 60 ++++++++++++++++++ .../hitOrMiss/images/hitmiss_example2.png | Bin 0 -> 22299 bytes .../hitOrMiss/images/hitmiss_example3.png | Bin 0 -> 22802 bytes .../hitOrMiss/images/hitmiss_input.png | Bin 0 -> 25033 bytes .../hitOrMiss/images/hitmiss_kernels.png | Bin 0 -> 11040 bytes .../hitOrMiss/images/hitmiss_output.png | Bin 0 -> 18677 bytes .../imgproc/table_of_content_imgproc.markdown | 8 +++ modules/imgproc/include/opencv2/imgproc.hpp | 4 +- samples/cpp/tutorial_code/ImgProc/HitMiss.cpp | 32 ++++++++++ 9 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 doc/tutorials/imgproc/hitOrMiss/hitOrMiss.markdown create mode 100644 doc/tutorials/imgproc/hitOrMiss/images/hitmiss_example2.png create mode 100644 doc/tutorials/imgproc/hitOrMiss/images/hitmiss_example3.png create mode 100644 doc/tutorials/imgproc/hitOrMiss/images/hitmiss_input.png create mode 100644 doc/tutorials/imgproc/hitOrMiss/images/hitmiss_kernels.png create mode 100644 doc/tutorials/imgproc/hitOrMiss/images/hitmiss_output.png create mode 100644 samples/cpp/tutorial_code/ImgProc/HitMiss.cpp diff --git a/doc/tutorials/imgproc/hitOrMiss/hitOrMiss.markdown b/doc/tutorials/imgproc/hitOrMiss/hitOrMiss.markdown new file mode 100644 index 0000000000..085e7c2e00 --- /dev/null +++ b/doc/tutorials/imgproc/hitOrMiss/hitOrMiss.markdown @@ -0,0 +1,60 @@ +Hit-or-Miss {#tutorial_hitOrMiss} +================================= + +Goal +---- + +In this tutorial you will learn how to find a given configuration or pattern in a binary image by using the Hit-or-Miss transform (also known as Hit-and-Miss transform). +This transform is also the basis of more advanced morphological operations such as thinning or pruning. + +We will use the OpenCV function @ref cv::morphologyEx. + + + +Hit-or-Miss theory +------------------- + +Morphological operators process images based on their shape. These operators apply one or more *structuring elements* to an input image to obtain the output image. +The two basic morphological operations are the *erosion* and the *dilation*. The combination of these two operations generate advanced morphological transformations such as *opening*, *closing*, or *top-hat* transform. +To know more about these and other basic morphological operations refer to previous tutorials @ref tutorial_erosion_dilatation "here" and @ref tutorial_opening_closing_hats "here". + +The Hit-or-Miss transformation is useful to find patterns in binary images. In particular, it finds those pixels whose neighbourhood matches the shape of a first structuring element \f$B_1\f$ +while not matching the shape of a second structuring element \f$B_2\f$ at the same time. Mathematically, the operation applied to an image \f$A\f$ can be expressed as follows: +\f[ + A\circledast B = (A\ominus B_1) \cap (A^c\ominus B_2) +\f] + +Therefore, the hit-or-miss operation comprises three steps: + 1. Erode image \f$A\f$ with structuring element \f$B_1\f$. + 2. Erode the complement of image \f$A\f$ (\f$A^c\f$) with structuring element \f$B_2\f$. + 3. AND results from step 1 and step 2. + +The structuring elements \f$B_1\f$ and \f$B_2\f$ can be combined into a single element \f$B\f$. Let's see an example: +![Structuring elements (kernels). Left: kernel to 'hit'. Middle: kernel to 'miss'. Right: final combined kernel](images/hitmiss_kernels.png) + +In this case, we are looking for a pattern in which the central pixel belongs to the background while the north, south, east, and west pixels belong to the foreground. The rest of pixels in the neighbourhood can be of any kind, we don't care about them. Now, let's apply this kernel to an input image: + +![Input binary image](images/hitmiss_input.png) +![Output binary image](images/hitmiss_output.png) + +You can see that the pattern is found in just one location within the image. + + +Code +---- + +The code corresponding to the previous example is shown below. You can also download it from +[here](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/ImgProc/HitMiss.cpp) +@include samples/cpp/tutorial_code/ImgProc/HitMiss.cpp + +As you can see, it is as simple as using the function @ref cv::morphologyEx with the operation type @ref cv::MORPH_HITMISS and the chosen kernel. + +Other examples +-------------- + +Here you can find the output results of applying different kernels to the same input image used before: + +![Kernel and output result for finding top-right corners](images/hitmiss_example2.png) +![Kernel and output result for finding left end points](images/hitmiss_example3.png) + +Now try your own patterns! diff --git a/doc/tutorials/imgproc/hitOrMiss/images/hitmiss_example2.png b/doc/tutorials/imgproc/hitOrMiss/images/hitmiss_example2.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e0efba13d1efaa72ee6460cc92f6e32f0a899f GIT binary patch literal 22299 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/imgproc/hitOrMiss/images/hitmiss_output.png b/doc/tutorials/imgproc/hitOrMiss/images/hitmiss_output.png new file mode 100644 index 0000000000000000000000000000000000000000..b7b81638ed419774ca6e5dcd082767cc28a6e83e GIT binary patch literal 18677 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 OpenCV 2.4 + + *Author:* Lorena García + + Learn how to find patterns in binary images using the Hit-or-Miss operation + - @subpage tutorial_moprh_lines_detection *Compatibility:* \> OpenCV 2.0 diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 007a238d67..95e5a1592b 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -245,8 +245,8 @@ enum MorphTypes{ //!< \f[\texttt{dst} = \mathrm{tophat} ( \texttt{src} , \texttt{element} )= \texttt{src} - \mathrm{open} ( \texttt{src} , \texttt{element} )\f] MORPH_BLACKHAT = 6, //!< "black hat" //!< \f[\texttt{dst} = \mathrm{blackhat} ( \texttt{src} , \texttt{element} )= \mathrm{close} ( \texttt{src} , \texttt{element} )- \texttt{src}\f] - MORPH_HITMISS = 7 //!< "hit and miss" - //!< .- Only supported for CV_8UC1 binary images. Tutorial can be found in [this page](https://web.archive.org/web/20160316070407/http://opencv-code.com/tutorials/hit-or-miss-transform-in-opencv/) + MORPH_HITMISS = 7 //!< "hit or miss" + //!< .- Only supported for CV_8UC1 binary images. A tutorial can be found in the documentation }; //! shape of the structuring element diff --git a/samples/cpp/tutorial_code/ImgProc/HitMiss.cpp b/samples/cpp/tutorial_code/ImgProc/HitMiss.cpp new file mode 100644 index 0000000000..0463aabe39 --- /dev/null +++ b/samples/cpp/tutorial_code/ImgProc/HitMiss.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +using namespace cv; + +int main(){ + Mat input_image = (Mat_(8, 8) << + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 255, 255, 0, 0, 0, 255, + 0, 255, 255, 255, 0, 0, 0, 0, + 0, 255, 255, 255, 0, 255, 0, 0, + 0, 0, 255, 0, 0, 0, 0, 0, + 0, 0, 255, 0, 0, 255, 255, 0, + 0, 255, 0, 255, 0, 0, 255, 0, + 0, 255, 255, 255, 0, 0, 0, 0); + + Mat kernel = (Mat_(3, 3) << + 0, 1, 0, + 1, -1, 1, + 0, 1, 0); + + Mat output_image; + morphologyEx(input_image, output_image, MORPH_HITMISS, kernel); + + namedWindow("Original", CV_WINDOW_NORMAL); + imshow("Original", input_image); + namedWindow("Hit or Miss", CV_WINDOW_NORMAL); + imshow("Hit or Miss", output_image); + waitKey(0); + return 0; +} \ No newline at end of file From fa36b9d3455d62e98554a996dea3ebf7201109c2 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 3 Jan 2017 22:18:27 +0300 Subject: [PATCH 293/409] imgproc: fix using of uninitialized edge[] members in FillConvexPoly --- modules/imgproc/src/drawing.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/imgproc/src/drawing.cpp b/modules/imgproc/src/drawing.cpp index 113f499d98..9099d7378a 100644 --- a/modules/imgproc/src/drawing.cpp +++ b/modules/imgproc/src/drawing.cpp @@ -1171,6 +1171,9 @@ FillConvexPoly( Mat& img, const Point2l* v, int npts, const void* color, int lin edge[0].di = 1; edge[1].di = npts - 1; + edge[0].x = edge[1].x = -XY_ONE; + edge[0].dx = edge[1].dx = 0; + ptr += img.step*y; do @@ -1213,6 +1216,9 @@ FillConvexPoly( Mat& img, const Point2l* v, int npts, const void* color, int lin } } + if (edges < 0) + break; + if (y >= 0) { int left = 0, right = 1; From 1c3b9d75911819f951654db8bafcd696196d649f Mon Sep 17 00:00:00 2001 From: Duncan Barber Date: Wed, 4 Jan 2017 21:24:25 +0000 Subject: [PATCH 294/409] Tiny documentation fix in stereoCalibrate Modified the (R2,T2) computation documentation in the stereoCalibrate function documentation to display on two lines for clarity --- modules/calib3d/include/opencv2/calib3d.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/calib3d/include/opencv2/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d.hpp index 362e81f8bf..5a0e020d31 100644 --- a/modules/calib3d/include/opencv2/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d.hpp @@ -979,8 +979,8 @@ This means that, given ( \f$R_1\f$,\f$T_1\f$ ), it should be possible to compute need to know the position and orientation of the second camera relative to the first camera. This is what the described function does. It computes ( \f$R\f$,\f$T\f$ ) so that: -\f[R_2=R*R_1 -T_2=R*T_1 + T,\f] +\f[R_2=R*R_1\f] +\f[T_2=R*T_1 + T,\f] Optionally, it computes the essential matrix E: From d33d37ffd9af0367aa3ee637a3f4252ac1271930 Mon Sep 17 00:00:00 2001 From: Tetragramm Date: Wed, 4 Jan 2017 18:38:46 -0600 Subject: [PATCH 295/409] Add check for all zero moments. If one of the shapes is empty, the match would return zero distance between the shapes even when the other one had content. It now returns DBL_MAX if no moments had value. --- modules/imgproc/src/matchcontours.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/imgproc/src/matchcontours.cpp b/modules/imgproc/src/matchcontours.cpp index 1a371677bf..f848bd59a8 100644 --- a/modules/imgproc/src/matchcontours.cpp +++ b/modules/imgproc/src/matchcontours.cpp @@ -50,6 +50,7 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou double eps = 1.e-5; double mmm; double result = 0; + bool anyResults = false; HuMoments( moments(contour1), ma ); HuMoments( moments(contour2), mb ); @@ -80,6 +81,7 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou ama = 1. / (sma * log10( ama )); amb = 1. / (smb * log10( amb )); result += fabs( -ama + amb ); + anyResults = true; } } break; @@ -108,6 +110,7 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou ama = sma * log10( ama ); amb = smb * log10( amb ); result += fabs( -ama + amb ); + anyResults = true; } } break; @@ -138,6 +141,7 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou mmm = fabs( (ama - amb) / ama ); if( result < mmm ) result = mmm; + anyResults = true; } } break; @@ -145,6 +149,9 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou CV_Error( CV_StsBadArg, "Unknown comparison method" ); } + if (!anyResults) + result = DBL_MAX; + return result; } From a891caa755595b00f68b69462af07d3d3cbad2d2 Mon Sep 17 00:00:00 2001 From: catree Date: Thu, 5 Jan 2017 16:05:53 +0100 Subject: [PATCH 296/409] Use directly the doxygen snippet command for the Changing the contrast and brightness of an image tutorial. Extend the tutorial with a pratical example and with a gamma correction method to adjust the brightness of an image. --- .../basic_linear_transform.markdown | 160 ++++++++++-------- .../Basic_Linear_Transform_Tutorial_gamma.png | Bin 0 -> 92059 bytes ...ar_Transform_Tutorial_gamma_correction.jpg | Bin 0 -> 276052 bytes ...c_Linear_Transform_Tutorial_hist_alpha.png | Bin 0 -> 3192 bytes ...ic_Linear_Transform_Tutorial_hist_beta.png | Bin 0 -> 3531 bytes ...r_Transform_Tutorial_histogram_compare.png | Bin 0 -> 1448 bytes ...m_Tutorial_linear_transform_correction.jpg | Bin 0 -> 227228 bytes .../ImgProc/BasicLinearTransforms.cpp | 75 ++++---- .../changing_contrast_brightness_image.cpp | 91 ++++++++++ 9 files changed, 220 insertions(+), 106 deletions(-) create mode 100644 doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma.png create mode 100644 doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg create mode 100644 doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_alpha.png create mode 100644 doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_beta.png create mode 100644 doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_histogram_compare.png create mode 100644 doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg create mode 100644 samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp diff --git a/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown b/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown index bb0ffd8978..05dd4db13f 100644 --- a/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown +++ b/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown @@ -10,6 +10,7 @@ In this tutorial you will learn how to: - Initialize a matrix with zeros - Learn what @ref cv::saturate_cast does and why it is useful - Get some cool info about pixel transformations +- Improve the brightness of an image on a practical example Theory ------ @@ -53,87 +54,29 @@ Code ---- - The following code performs the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ : -@code{.cpp} -#include -#include - -using namespace cv; - -double alpha; /*< Simple contrast control */ -int beta; /*< Simple brightness control */ - -int main( int argc, char** argv ) -{ - /// Read image given by user - Mat image = imread( argv[1] ); - Mat new_image = Mat::zeros( image.size(), image.type() ); - - /// Initialize values - std::cout<<" Basic Linear Transforms "<>alpha; - std::cout<<"* Enter the beta value [0-100]: "; std::cin>>beta; - - /// Do the operation new_image(i,j) = alpha*image(i,j) + beta - for( int y = 0; y < image.rows; y++ ) { - for( int x = 0; x < image.cols; x++ ) { - for( int c = 0; c < 3; c++ ) { - new_image.at(y,x)[c] = - saturate_cast( alpha*( image.at(y,x)[c] ) + beta ); - } - } - } - - /// Create Windows - namedWindow("Original Image", 1); - namedWindow("New Image", 1); - - /// Show stuff - imshow("Original Image", image); - imshow("New Image", new_image); - - /// Wait until user press some key - waitKey(); - return 0; -} -@endcode +@include BasicLinearTransforms.cpp Explanation ----------- -# We begin by creating parameters to save \f$\alpha\f$ and \f$\beta\f$ to be entered by the user: - @code{.cpp} - double alpha; - int beta; - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-parameters + -# We load an image using @ref cv::imread and save it in a Mat object: - @code{.cpp} - Mat image = imread( argv[1] ); - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-load -# Now, since we will make some transformations to this image, we need a new Mat object to store it. Also, we want this to have the following features: - Initial pixel values equal to zero - Same size and type as the original image - @code{.cpp} - Mat new_image = Mat::zeros( image.size(), image.type() ); - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-output We observe that @ref cv::Mat::zeros returns a Matlab-style zero initializer based on *image.size()* and *image.type()* -# Now, to perform the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ we will access to each pixel in image. Since we are operating with BGR images, we will have three values per pixel (B, G and R), so we will also access them separately. Here is the piece of code: - @code{.cpp} - for( int y = 0; y < image.rows; y++ ) { - for( int x = 0; x < image.cols; x++ ) { - for( int c = 0; c < 3; c++ ) { - new_image.at(y,x)[c] = - saturate_cast( alpha*( image.at(y,x)[c] ) + beta ); - } - } - } - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-operation Notice the following: - To access each pixel in the images we are using this syntax: *image.at\(y,x)[c]* where *y* is the row, *x* is the column and *c* is R, G or B (0, 1 or 2). @@ -142,15 +85,7 @@ Explanation values are valid. -# Finally, we create windows and show the images, the usual way. - @code{.cpp} - namedWindow("Original Image", 1); - namedWindow("New Image", 1); - - imshow("Original Image", image); - imshow("New Image", new_image); - - waitKey(0); - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-display @note Instead of using the **for** loops to access each pixel, we could have simply used this command: @@ -176,3 +111,82 @@ Result - We get this: ![](images/Basic_Linear_Transform_Tutorial_Result_big.jpg) + +Practical example +---- + +In this paragraph, we will put into practice what we have learned to correct an underexposed image by adjusting the brightness +and the contrast of the image. We will also see another technique to correct the brightness of an image called +gamma correction. + +### Brightness and contrast adjustments + +Increasing (/ decreasing) the \f$\beta\f$ value will add (/ subtract) a constant value to every pixel. Pixel values outside of the [0 ; 255] +range will be saturated (i.e. a pixel value higher (/ lesser) than 255 (/ 0) will be clamp to 255 (/ 0)). + +![In light gray, histogram of the original image, in dark gray when brightness = 80 in Gimp](images/Basic_Linear_Transform_Tutorial_hist_beta.png) + +The histogram represents for each color level the number of pixels with that color level. A dark image will have many pixels with +low color value and thus the histogram will present a peak in his left part. When adding a constant bias, the histogram is shifted to the +right as we have added a constant bias to all the pixels. + +The \f$\alpha\f$ parameter will modify how the levels spread. If \f$ \alpha < 1 \f$, the color levels will be compressed and the result +will be an image with less contrast. + +![In light gray, histogram of the original image, in dark gray when contrast < 0 in Gimp](images/Basic_Linear_Transform_Tutorial_hist_alpha.png) + +Note that these histograms have been obtained using the Brightness-Contrast tool in the Gimp software. The brightness tool should be +identical to the \f$\beta\f$ bias parameters but the contrast tool seems to differ to the \f$\alpha\f$ gain where the output range +seems to be centered with Gimp (as you can notice in the previous histogram). + +It can occur that playing with the \f$\beta\f$ bias will improve the brightness but in the same time the image will appear with a +slight veil as the contrast will be reduced. The \f$\alpha\f$ gain can be used to diminue this effect but due to the saturation, +we will lose some details in the original bright regions. + +### Gamma correction + +[Gamma correction](https://en.wikipedia.org/wiki/Gamma_correction) can be used to correct the brightness of an image by using a non +linear transformation between the input values and the mapped output values: + +\f[O = \left( \frac{I}{255} \right)^{\gamma} \times 255\f] + +As this relation is non linear, the effect will not be the same for all the pixels and will depend to their original value. + +![Plot for different values of gamma](images/Basic_Linear_Transform_Tutorial_gamma.png) + +When \f$ \gamma < 1 \f$, the original dark regions will be brighter and the histogram will be shifted to the right whereas it will +be the opposite with \f$ \gamma > 1 \f$. + +### Correct an underexposed image + +The following image has been corrected with: \f$ \alpha = 1.3 \f$ and \f$ \beta = 40 \f$. + +![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg) + +The overall brightness has been improved but you can notice that the clouds are now greatly saturated due to the numerical saturation +of the implementation used. A custom method that preserves the original color range can of course be implemented instead. + +The following image has been corrected with: \f$ \gamma = 0.4 \f$. + +![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg) + +The gamma correction should tend to add less saturation effect but should introduce some other type of color artifacts instead. + +![Left: histogram after alpha, beta correction ; Center: histogram of the original image ; Right: histogram after the gamma correction](images/Basic_Linear_Transform_Tutorial_histogram_compare.png) + +The previous figure compares the histograms for the three images (the y-ranges are not the same between the three histograms). +You can notice that most of the pixel values are in the lower part of the histogram for the original image. After \f$ \alpha \f$, +\f$ \beta \f$ correction, we can observe a big peak at 255 due to the saturation as well as a shift in the right. +After gamma correction, the histogram is shifted to the right but the pixels in the dark regions are more shifted +(see the gamma curves [figure](Basic_Linear_Transform_Tutorial_gamma.png)) than those in the bright regions. + +In this tutorial, you have seen two simple methods to adjust the contrast and the brightness of an image. **They are basic techniques +and are not intended to be used as a replacement of a raster graphics editor!** + +### Code + +Code for the tutorial is [here](changing_contrast_brightness_image.cpp). Code for the gamma correction: + +@snippet changing_contrast_brightness_image.cpp changing-contrast-brightness-gamma-correction + +A look-up table is used to improve the performance of the computation as only 256 values needs to be calculated once. diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma.png b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma.png new file mode 100644 index 0000000000000000000000000000000000000000..50cd5860accf411687c149d2635ec48a0b3de8ab GIT binary patch literal 92059 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 z∓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 KlBH&T_5T3A=`S(> literal 0 HcmV?d00001 diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7ade82d85e07f979e60a6c68b0e9d28bbebd2efd GIT binary patch literal 276052 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≥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

=>AX%)Wi@?XN}c_q*8cI~lQ2>g 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%0DK^i$VUh}C+$VJPFX|1&tH{~ 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

71E(^+B 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%0AXE7W*(Qr=-~Bx-PSt$6m{EASQt_hML<9All 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

8_F+yzc2xG`Gu@^Gp67*UCBn0F(vO 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

    RJ1jhnRp0NM%Qr9dBfu~HuFav9+s%0| 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

    T6e>HKArLA*41F;HdjJYbe&r!sHvmVQ~-Z-mY# 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

    U|SLun-Qeovk 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 G&;Qx6!G>M{ literal 0 HcmV?d00001 diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_alpha.png b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..21185a16d3ef5160b3655e09dcd87612c909a090 GIT binary patch literal 3192 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_histogram_compare.png b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_histogram_compare.png new file mode 100644 index 0000000000000000000000000000000000000000..c2494dc22ef3a7aa064bcaf1d66b06b4dfe310f2 GIT binary patch literal 1448 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År@(67@zJy7 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


    BX<7*3l@68N7j?uOv$&4k(wN+~B$0bJupERT*zG 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/core/table_of_content_core.markdown b/doc/tutorials/core/table_of_content_core.markdown index 70d9c81a1b..2b9afb8b19 100644 --- a/doc/tutorials/core/table_of_content_core.markdown +++ b/doc/tutorials/core/table_of_content_core.markdown @@ -106,3 +106,10 @@ understanding how to manipulate the images on a pixel level. *Author:* Elena Gvozdeva You will see how to use the IPP Async with OpenCV. + + +- @subpage tutorial_how_to_use_OpenCV_parallel_for_ + + *Compatibility:* \>= OpenCV 2.4.3 + + You will see how to use the OpenCV parallel_for_ to easily parallelize your code. diff --git a/samples/cpp/tutorial_code/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.cpp b/samples/cpp/tutorial_code/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.cpp new file mode 100644 index 0000000000..c661b919b3 --- /dev/null +++ b/samples/cpp/tutorial_code/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +using namespace std; +using namespace cv; + +namespace +{ +//! [mandelbrot-escape-time-algorithm] +int mandelbrot(const complex &z0, const int max) +{ + complex z = z0; + for (int t = 0; t < max; t++) + { + if (z.real()*z.real() + z.imag()*z.imag() > 4.0f) return t; + z = z*z + z0; + } + + return max; +} +//! [mandelbrot-escape-time-algorithm] + +//! [mandelbrot-grayscale-value] +int mandelbrotFormula(const complex &z0, const int maxIter=500) { + int value = mandelbrot(z0, maxIter); + if(maxIter - value == 0) + { + return 0; + } + + return cvRound(sqrt(value / (float) maxIter) * 255); +} +//! [mandelbrot-grayscale-value] + +//! [mandelbrot-parallel] +class ParallelMandelbrot : public ParallelLoopBody +{ +public: + ParallelMandelbrot (Mat &img, const float x1, const float y1, const float scaleX, const float scaleY) + : m_img(img), m_x1(x1), m_y1(y1), m_scaleX(scaleX), m_scaleY(scaleY) + { + } + + virtual void operator ()(const Range& range) const + { + for (int r = range.start; r < range.end; r++) + { + int i = r / m_img.cols; + int j = r % m_img.cols; + + float x0 = j / m_scaleX + m_x1; + float y0 = i / m_scaleY + m_y1; + + complex z0(x0, y0); + uchar value = (uchar) mandelbrotFormula(z0); + m_img.ptr(i)[j] = value; + } + } + + ParallelMandelbrot& operator=(const ParallelMandelbrot &) { + return *this; + }; + +private: + Mat &m_img; + float m_x1; + float m_y1; + float m_scaleX; + float m_scaleY; +}; +//! [mandelbrot-parallel] + +//! [mandelbrot-sequential] +void sequentialMandelbrot(Mat &img, const float x1, const float y1, const float scaleX, const float scaleY) +{ + for (int i = 0; i < img.rows; i++) + { + for (int j = 0; j < img.cols; j++) + { + float x0 = j / scaleX + x1; + float y0 = i / scaleY + y1; + + complex z0(x0, y0); + uchar value = (uchar) mandelbrotFormula(z0); + img.ptr(i)[j] = value; + } + } +} +//! [mandelbrot-sequential] +} + +int main() +{ + //! [mandelbrot-transformation] + Mat mandelbrotImg(4800, 5400, CV_8U); + float x1 = -2.1f, x2 = 0.6f; + float y1 = -1.2f, y2 = 1.2f; + float scaleX = mandelbrotImg.cols / (x2 - x1); + float scaleY = mandelbrotImg.rows / (y2 - y1); + //! [mandelbrot-transformation] + + double t1 = (double) getTickCount(); + //! [mandelbrot-parallel-call] + ParallelMandelbrot parallelMandelbrot(mandelbrotImg, x1, y1, scaleX, scaleY); + parallel_for_(Range(0, mandelbrotImg.rows*mandelbrotImg.cols), parallelMandelbrot); + //! [mandelbrot-parallel-call] + t1 = ((double) getTickCount() - t1) / getTickFrequency(); + cout << "Parallel Mandelbrot: " << t1 << " s" << endl; + + Mat mandelbrotImgSequential(4800, 5400, CV_8U); + double t2 = (double) getTickCount(); + sequentialMandelbrot(mandelbrotImgSequential, x1, y1, scaleX, scaleY); + t2 = ((double) getTickCount() - t2) / getTickFrequency(); + cout << "Sequential Mandelbrot: " << t2 << " s" << endl; + cout << "Speed-up: " << t2/t1 << " X" << endl; + + imwrite("Mandelbrot_parallel.png", mandelbrotImg); + imwrite("Mandelbrot_sequential.png", mandelbrotImgSequential); + + return EXIT_SUCCESS; +} From 7cc0b0f93ea3fa8131b478bc9483199ae933ebe7 Mon Sep 17 00:00:00 2001 From: Tetragramm Date: Tue, 10 Jan 2017 21:38:31 -0600 Subject: [PATCH 302/409] Add case including both moments empty. --- modules/imgproc/src/matchcontours.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/modules/imgproc/src/matchcontours.cpp b/modules/imgproc/src/matchcontours.cpp index f848bd59a8..2a0c5df330 100644 --- a/modules/imgproc/src/matchcontours.cpp +++ b/modules/imgproc/src/matchcontours.cpp @@ -50,7 +50,7 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou double eps = 1.e-5; double mmm; double result = 0; - bool anyResults = false; + bool anyA = false, anyB = false; HuMoments( moments(contour1), ma ); HuMoments( moments(contour2), mb ); @@ -63,6 +63,11 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou double ama = fabs( ma[i] ); double amb = fabs( mb[i] ); + if (ama > 0) + anyA = true; + if (amb > 0) + anyB = true; + if( ma[i] > 0 ) sma = 1; else if( ma[i] < 0 ) @@ -81,7 +86,6 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou ama = 1. / (sma * log10( ama )); amb = 1. / (smb * log10( amb )); result += fabs( -ama + amb ); - anyResults = true; } } break; @@ -92,6 +96,11 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou double ama = fabs( ma[i] ); double amb = fabs( mb[i] ); + if (ama > 0) + anyA = true; + if (amb > 0) + anyB = true; + if( ma[i] > 0 ) sma = 1; else if( ma[i] < 0 ) @@ -110,7 +119,6 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou ama = sma * log10( ama ); amb = smb * log10( amb ); result += fabs( -ama + amb ); - anyResults = true; } } break; @@ -121,6 +129,11 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou double ama = fabs( ma[i] ); double amb = fabs( mb[i] ); + if (ama > 0) + anyA = true; + if (amb > 0) + anyB = true; + if( ma[i] > 0 ) sma = 1; else if( ma[i] < 0 ) @@ -141,7 +154,6 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou mmm = fabs( (ama - amb) / ama ); if( result < mmm ) result = mmm; - anyResults = true; } } break; @@ -149,7 +161,10 @@ double cv::matchShapes(InputArray contour1, InputArray contour2, int method, dou CV_Error( CV_StsBadArg, "Unknown comparison method" ); } - if (!anyResults) + //If anyA and anyB are both true, the result is correct. + //If anyA and anyB are both false, the distance is 0, perfect match. + //If only one is true, then it's a false 0 and return large error. + if (anyA != anyB) result = DBL_MAX; return result; From 7e99911c82f33269cb223b33b646858f79eb2f04 Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Wed, 11 Jan 2017 17:17:02 +0300 Subject: [PATCH 303/409] Fix MKL x86 detection on Windows --- cmake/OpenCVFindMKL.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/OpenCVFindMKL.cmake b/cmake/OpenCVFindMKL.cmake index f43ce9c286..337456843b 100644 --- a/cmake/OpenCVFindMKL.cmake +++ b/cmake/OpenCVFindMKL.cmake @@ -79,12 +79,13 @@ if(CMAKE_CXX_SIZEOF_DATA_PTR EQUAL 8) include(CheckTypeSize) CHECK_TYPE_SIZE(int _sizeof_int) if (_sizeof_int EQUAL 4) - set(MKL_LP64 "lp64") + set(MKL_ARCH_SUFFIX "lp64") else() - set(MKL_LP64 "ilp64") + set(MKL_ARCH_SUFFIX "ilp64") endif() else() set(MKL_ARCH "ia32") + set(MKL_ARCH_SUFFIX "c") endif() if(${MKL_VERSION_STR} VERSION_GREATER "11.3.0" OR ${MKL_VERSION_STR} VERSION_EQUAL "11.3.0") @@ -94,7 +95,7 @@ if(${MKL_VERSION_STR} VERSION_GREATER "11.3.0" OR ${MKL_VERSION_STR} VERSION_EQU set(mkl_lib_list mkl_core - mkl_intel_${MKL_LP64}) + mkl_intel_${MKL_ARCH_SUFFIX}) if(MKL_WITH_TBB) list(APPEND mkl_lib_list mkl_tbb_thread tbb) @@ -112,7 +113,6 @@ else() mkl_fail() endif() - set(MKL_LIBRARIES "") foreach(lib ${mkl_lib_list}) find_library(${lib} ${lib} ${mkl_lib_find_paths}) @@ -133,4 +133,4 @@ else() #it's ugly but helps to avoid cyclic lib problem set(MKL_LIBRARIES ${MKL_LIBRARIES} ${MKL_LIBRARIES} ${MKL_LIBRARIES} "-lpthread" "-lm" "-ldl") set(MKL_LIBRARIES ${MKL_LIBRARIES} CACHE STRING "MKL libarries") -endif() \ No newline at end of file +endif() From 8b22099da26b4f05fe5198e547ef2975c27284ef Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Thu, 12 Jan 2017 08:47:28 +0900 Subject: [PATCH 304/409] use universal intrinsic and SSE4 popcount instruction in normHamming - add v_popcount in universal intrinsic - add test for v_popcount - add wrapper of popcount for both MSVC and GCC --- modules/core/include/opencv2/core/cvdef.h | 8 ++ .../include/opencv2/core/hal/intrin_cpp.hpp | 45 ++++++- .../include/opencv2/core/hal/intrin_neon.hpp | 16 +++ .../include/opencv2/core/hal/intrin_sse.hpp | 22 ++++ modules/core/src/stat.cpp | 111 +++++++++++------- modules/core/test/test_intrin.cpp | 18 +++ 6 files changed, 177 insertions(+), 43 deletions(-) diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index 699b1667b4..810ec56ed3 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -188,8 +188,16 @@ enum CpuFeatures { # if defined __POPCNT__ || (defined _MSC_VER && _MSC_VER >= 1500) # ifdef _MSC_VER # include +# if defined(_M_X64) +# define CV_POPCNT_U64 _mm_popcnt_u64 +# endif +# define CV_POPCNT_U32 _mm_popcnt_u32 # else # include +# if defined(__x86_64__) +# define CV_POPCNT_U64 __builtin_popcountll +# endif +# define CV_POPCNT_U32 __builtin_popcount # endif # define CV_POPCNT 1 # endif diff --git a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp index 93ca397817..e15c97d528 100644 --- a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp @@ -149,7 +149,7 @@ Element-wise binary and unary operations. Most of these operations return only one value. -- Reduce: @ref v_reduce_min, @ref v_reduce_max, @ref v_reduce_sum +- Reduce: @ref v_reduce_min, @ref v_reduce_max, @ref v_reduce_sum, @ref v_popcount - Mask: @ref v_signmask, @ref v_check_all, @ref v_check_any, @ref v_select ### Other math @@ -574,6 +574,49 @@ Scheme: For 32-bit integer and 32-bit floating point types. */ OPENCV_HAL_IMPL_REDUCE_MINMAX_FUNC(v_reduce_max, std::max) +static const unsigned char popCountTable[] = +{ + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, +}; +/** @brief Count the 1 bits in the vector and return 4 values + +Scheme: +@code +{A1 A2 A3 ...} => popcount(A1) +@endcode +Any types but result will be in v_uint32x4*/ +template inline v_uint32x4 v_popcount(const v_reg<_Tp, n>& a) +{ + v_uint8x16 b; + b = v_reinterpret_as_u8(a); + for( int i = 0; i < v_uint8x16::nlanes; i++ ) + { + b.s[i] = popCountTable[b.s[i]]; + } + v_uint32x4 c; + for( int i = 0; i < v_uint32x4::nlanes; i++ ) + { + c.s[i] = b.s[i*4] + b.s[i*4+1] + b.s[i*4+2] + b.s[i*4+3]; + } + return c; +} + + //! @cond IGNORED template inline void v_minmax( const v_reg<_Tp, n>& a, const v_reg<_Tp, n>& b, diff --git a/modules/core/include/opencv2/core/hal/intrin_neon.hpp b/modules/core/include/opencv2/core/hal/intrin_neon.hpp index b000733a5f..2bcff2bc15 100644 --- a/modules/core/include/opencv2/core/hal/intrin_neon.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_neon.hpp @@ -813,6 +813,22 @@ OPENCV_HAL_IMPL_NEON_REDUCE_OP_4(v_float32x4, float32x2, float, sum, add, f32) OPENCV_HAL_IMPL_NEON_REDUCE_OP_4(v_float32x4, float32x2, float, max, max, f32) OPENCV_HAL_IMPL_NEON_REDUCE_OP_4(v_float32x4, float32x2, float, min, min, f32) +#define OPENCV_HAL_IMPL_NEON_POPCOUNT(_Tpvec, cast) \ +inline v_uint32x4 v_popcount(const _Tpvec& a) \ +{ \ + uint8x16_t t = vcntq_u8(cast(a.val)); \ + uint16x8_t t0 = vpaddlq_u8(t); /* 16 -> 8 */ \ + uint32x4_t t1 = vpaddlq_u16(t0); /* 8 -> 4 */ \ + return v_uint32x4(t1); \ +} + +OPENCV_HAL_IMPL_NEON_POPCOUNT(v_uint8x16, OPENCV_HAL_NOP) +OPENCV_HAL_IMPL_NEON_POPCOUNT(v_uint16x8, vreinterpretq_u8_u16) +OPENCV_HAL_IMPL_NEON_POPCOUNT(v_uint32x4, vreinterpretq_u8_u32) +OPENCV_HAL_IMPL_NEON_POPCOUNT(v_int8x16, vreinterpretq_u8_s8) +OPENCV_HAL_IMPL_NEON_POPCOUNT(v_int16x8, vreinterpretq_u8_s16) +OPENCV_HAL_IMPL_NEON_POPCOUNT(v_int32x4, vreinterpretq_u8_s32) + inline int v_signmask(const v_uint8x16& a) { int8x8_t m0 = vcreate_s8(CV_BIG_UINT(0x0706050403020100)); diff --git a/modules/core/include/opencv2/core/hal/intrin_sse.hpp b/modules/core/include/opencv2/core/hal/intrin_sse.hpp index fc81dac35d..9ff10c9b47 100644 --- a/modules/core/include/opencv2/core/hal/intrin_sse.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_sse.hpp @@ -1121,6 +1121,28 @@ OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_float32x4, float, sum, OPENCV_HAL_ADD) OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_float32x4, float, max, std::max) OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_float32x4, float, min, std::min) +#define OPENCV_HAL_IMPL_SSE_POPCOUNT(_Tpvec) \ +inline v_uint32x4 v_popcount(const _Tpvec& a) \ +{ \ + __m128i m1 = _mm_set1_epi32(0x55555555); \ + __m128i m2 = _mm_set1_epi32(0x33333333); \ + __m128i m4 = _mm_set1_epi32(0x0f0f0f0f); \ + __m128i p = a.val; \ + p = _mm_add_epi32(_mm_and_si128(_mm_srli_epi32(p, 1), m1), _mm_and_si128(p, m1)); \ + p = _mm_add_epi32(_mm_and_si128(_mm_srli_epi32(p, 2), m2), _mm_and_si128(p, m2)); \ + p = _mm_add_epi32(_mm_and_si128(_mm_srli_epi32(p, 4), m4), _mm_and_si128(p, m4)); \ + p = _mm_adds_epi8(p, _mm_srli_si128(p, 1)); \ + p = _mm_adds_epi8(p, _mm_srli_si128(p, 2)); \ + return v_uint32x4(_mm_and_si128(p, _mm_set1_epi32(0x000000ff))); \ +} + +OPENCV_HAL_IMPL_SSE_POPCOUNT(v_uint8x16) +OPENCV_HAL_IMPL_SSE_POPCOUNT(v_uint16x8) +OPENCV_HAL_IMPL_SSE_POPCOUNT(v_uint32x4) +OPENCV_HAL_IMPL_SSE_POPCOUNT(v_int8x16) +OPENCV_HAL_IMPL_SSE_POPCOUNT(v_int16x8) +OPENCV_HAL_IMPL_SSE_POPCOUNT(v_int32x4) + #define OPENCV_HAL_IMPL_SSE_CHECK_SIGNS(_Tpvec, suffix, pack_op, and_op, signmask, allmask) \ inline int v_signmask(const _Tpvec& a) \ { \ diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index e1fddb220d..3336152a44 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -44,6 +44,7 @@ #include "precomp.hpp" #include #include +#include "opencv2/core/hal/intrin.hpp" #include "opencl_kernels_core.hpp" @@ -4238,22 +4239,8 @@ int normHamming(const uchar* a, int n) { int i = 0; int result = 0; -#if CV_NEON - { - uint32x4_t bits = vmovq_n_u32(0); - for (; i <= n - 16; i += 16) { - uint8x16_t A_vec = vld1q_u8 (a + i); - uint8x16_t bitsSet = vcntq_u8 (A_vec); - uint16x8_t bitSet8 = vpaddlq_u8 (bitsSet); - uint32x4_t bitSet4 = vpaddlq_u16 (bitSet8); - bits = vaddq_u32(bits, bitSet4); - } - uint64x2_t bitSet2 = vpaddlq_u32 (bits); - result = vgetq_lane_s32 (vreinterpretq_s32_u64(bitSet2),0); - result += vgetq_lane_s32 (vreinterpretq_s32_u64(bitSet2),2); - } -#elif CV_AVX2 - if (USE_AVX2) +#if CV_AVX2 + if(USE_AVX2) { __m256i _r0 = _mm256_setzero_si256(); __m256i _0 = _mm256_setzero_si256(); @@ -4274,12 +4261,40 @@ int normHamming(const uchar* a, int n) _r0 = _mm256_add_epi32(_r0, _mm256_shuffle_epi32(_r0, 2)); result = _mm256_extract_epi32_(_mm256_add_epi32(_r0, _mm256_permute2x128_si256(_r0, _r0, 1)), 0); } +#elif CV_POPCNT + if(checkHardwareSupport(CV_CPU_POPCNT)) + { +# if defined CV_POPCNT_U64 + for(; i <= n - 8; i += 8) + { + result += (int)CV_POPCNT_U64(*(uint64*)(a + i)); + } +# endif + for(; i <= n - 4; i += 4) + { + result += CV_POPCNT_U32(*(uint*)(a + i)); + } + } +#elif CV_SIMD128 + if(hasSIMD128()) + { + v_uint32x4 t = v_setzero_u32(); + for(; i <= n - v_uint8x16::nlanes; i += v_uint8x16::nlanes) + { + t += v_popcount(v_load(a + i)); + } + result = v_reduce_sum(t); + } #endif - for( ; i <= n - 4; i += 4 ) - result += popCountTable[a[i]] + popCountTable[a[i+1]] + - popCountTable[a[i+2]] + popCountTable[a[i+3]]; - for( ; i < n; i++ ) + for(; i <= n - 4; i += 4) + { + result += popCountTable[a[i]] + popCountTable[a[i+1]] + + popCountTable[a[i+2]] + popCountTable[a[i+3]]; + } + for(; i < n; i++) + { result += popCountTable[a[i]]; + } return result; } @@ -4287,24 +4302,8 @@ int normHamming(const uchar* a, const uchar* b, int n) { int i = 0; int result = 0; -#if CV_NEON - { - uint32x4_t bits = vmovq_n_u32(0); - for (; i <= n - 16; i += 16) { - uint8x16_t A_vec = vld1q_u8 (a + i); - uint8x16_t B_vec = vld1q_u8 (b + i); - uint8x16_t AxorB = veorq_u8 (A_vec, B_vec); - uint8x16_t bitsSet = vcntq_u8 (AxorB); - uint16x8_t bitSet8 = vpaddlq_u8 (bitsSet); - uint32x4_t bitSet4 = vpaddlq_u16 (bitSet8); - bits = vaddq_u32(bits, bitSet4); - } - uint64x2_t bitSet2 = vpaddlq_u32 (bits); - result = vgetq_lane_s32 (vreinterpretq_s32_u64(bitSet2),0); - result += vgetq_lane_s32 (vreinterpretq_s32_u64(bitSet2),2); - } -#elif CV_AVX2 - if (USE_AVX2) +#if CV_AVX2 + if(USE_AVX2) { __m256i _r0 = _mm256_setzero_si256(); __m256i _0 = _mm256_setzero_si256(); @@ -4328,12 +4327,40 @@ int normHamming(const uchar* a, const uchar* b, int n) _r0 = _mm256_add_epi32(_r0, _mm256_shuffle_epi32(_r0, 2)); result = _mm256_extract_epi32_(_mm256_add_epi32(_r0, _mm256_permute2x128_si256(_r0, _r0, 1)), 0); } +#elif CV_POPCNT + if(checkHardwareSupport(CV_CPU_POPCNT)) + { +# if defined CV_POPCNT_U64 + for(; i <= n - 8; i += 8) + { + result += (int)CV_POPCNT_U64(*(uint64*)(a + i) ^ *(uint64*)(b + i)); + } +# endif + for(; i <= n - 4; i += 4) + { + result += CV_POPCNT_U32(*(uint*)(a + i) ^ *(uint*)(b + i)); + } + } +#elif CV_SIMD128 + if(hasSIMD128()) + { + v_uint32x4 t = v_setzero_u32(); + for(; i <= n - v_uint8x16::nlanes; i += v_uint8x16::nlanes) + { + t += v_popcount(v_load(a + i) ^ v_load(b + i)); + } + result = v_reduce_sum(t); + } #endif - for( ; i <= n - 4; i += 4 ) - result += popCountTable[a[i] ^ b[i]] + popCountTable[a[i+1] ^ b[i+1]] + - popCountTable[a[i+2] ^ b[i+2]] + popCountTable[a[i+3] ^ b[i+3]]; - for( ; i < n; i++ ) + for(; i <= n - 4; i += 4) + { + result += popCountTable[a[i] ^ b[i]] + popCountTable[a[i+1] ^ b[i+1]] + + popCountTable[a[i+2] ^ b[i+2]] + popCountTable[a[i+3] ^ b[i+3]]; + } + for(; i < n; i++) + { result += popCountTable[a[i] ^ b[i]]; + } return result; } diff --git a/modules/core/test/test_intrin.cpp b/modules/core/test/test_intrin.cpp index 66b2083a37..0ec24ef1fb 100644 --- a/modules/core/test/test_intrin.cpp +++ b/modules/core/test/test_intrin.cpp @@ -404,6 +404,18 @@ template struct TheTest return *this; } + TheTest & test_popcount() + { + static unsigned popcountTable[] = {0, 1, 2, 4, 5, 7, 9, 12, 13, 15, 17, 20, 22, 25, 28, 32, 33}; + Data dataA; + R a = dataA; + + unsigned resB = (unsigned)v_reduce_sum(v_popcount(a)); + EXPECT_EQ(popcountTable[R::nlanes], resB); + + return *this; + } + TheTest & test_absdiff() { typedef typename V_RegTrait128::u_reg Ru; @@ -798,6 +810,7 @@ TEST(hal_intrin, uint8x16) { .test_min_max() .test_absdiff() .test_mask() + .test_popcount() .test_pack<1>().test_pack<2>().test_pack<3>().test_pack<8>() .test_pack_u<1>().test_pack_u<2>().test_pack_u<3>().test_pack_u<8>() .test_unpack() @@ -819,6 +832,7 @@ TEST(hal_intrin, int8x16) { .test_absdiff() .test_abs() .test_mask() + .test_popcount() .test_pack<1>().test_pack<2>().test_pack<3>().test_pack<8>() .test_unpack() .test_extract<0>().test_extract<1>().test_extract<8>().test_extract<15>() @@ -844,6 +858,7 @@ TEST(hal_intrin, uint16x8) { .test_absdiff() .test_reduce() .test_mask() + .test_popcount() .test_pack<1>().test_pack<2>().test_pack<7>().test_pack<16>() .test_pack_u<1>().test_pack_u<2>().test_pack_u<7>().test_pack_u<16>() .test_unpack() @@ -870,6 +885,7 @@ TEST(hal_intrin, int16x8) { .test_abs() .test_reduce() .test_mask() + .test_popcount() .test_pack<1>().test_pack<2>().test_pack<7>().test_pack<16>() .test_unpack() .test_extract<0>().test_extract<1>().test_extract<4>().test_extract<7>() @@ -894,6 +910,7 @@ TEST(hal_intrin, uint32x4) { .test_absdiff() .test_reduce() .test_mask() + .test_popcount() .test_pack<1>().test_pack<2>().test_pack<15>().test_pack<32>() .test_unpack() .test_extract<0>().test_extract<1>().test_extract<2>().test_extract<3>() @@ -910,6 +927,7 @@ TEST(hal_intrin, int32x4) { .test_mul() .test_abs() .test_cmp() + .test_popcount() .test_shift<1>().test_shift<8>() .test_logic() .test_min_max() From f94e64a0ae12463de03281bb4434044872337612 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 12 Jan 2017 14:38:47 +0300 Subject: [PATCH 305/409] cuda: fix CUDA tests initialization --- modules/ts/include/opencv2/ts.hpp | 7 ++++++- modules/ts/include/opencv2/ts/cuda_test.hpp | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/ts/include/opencv2/ts.hpp b/modules/ts/include/opencv2/ts.hpp index c573905c6f..a1981926ff 100644 --- a/modules/ts/include/opencv2/ts.hpp +++ b/modules/ts/include/opencv2/ts.hpp @@ -604,12 +604,17 @@ void dumpOpenCLDevice(); void parseCustomOptions(int argc, char **argv); -#define CV_TEST_MAIN(resourcesubdir, ...) \ +#define CV_TEST_INIT0_NOOP (void)0 + +#define CV_TEST_MAIN(resourcesubdir, ...) CV_TEST_MAIN_EX(resourcesubdir, NOOP, __VA_ARGS__) + +#define CV_TEST_MAIN_EX(resourcesubdir, INIT0, ...) \ int main(int argc, char **argv) \ { \ using namespace cvtest; \ TS* ts = TS::ptr(); \ ts->init(resourcesubdir); \ + __CV_TEST_EXEC_ARGS(CV_TEST_INIT0_ ## INIT0) \ ::testing::InitGoogleTest(&argc, argv); \ cvtest::printVersionInfo(); \ TEST_DUMP_OCL_INFO \ diff --git a/modules/ts/include/opencv2/ts/cuda_test.hpp b/modules/ts/include/opencv2/ts/cuda_test.hpp index b459bb358f..2780927304 100644 --- a/modules/ts/include/opencv2/ts/cuda_test.hpp +++ b/modules/ts/include/opencv2/ts/cuda_test.hpp @@ -351,8 +351,10 @@ namespace cv { namespace cuda #ifdef HAVE_CUDA -#define CV_CUDA_TEST_MAIN(resourcesubdir) \ - CV_TEST_MAIN(resourcesubdir, cvtest::parseCudaDeviceOptions(argc, argv), cvtest::printCudaInfo(), cv::setUseOptimized(false)) +#define CV_TEST_INIT0_CUDA cvtest::parseCudaDeviceOptions(argc, argv), cvtest::printCudaInfo(), cv::setUseOptimized(false) + +#define CV_CUDA_TEST_MAIN(resourcesubdir, ...) \ + CV_TEST_MAIN_EX(resourcesubdir, CUDA, __VA_ARGS__) #else // HAVE_CUDA From b7630a088b25bf7e7cf88878510c1a09c8b840ee Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 12 Jan 2017 19:14:51 +0300 Subject: [PATCH 306/409] cmake: add global defines to enable useful macros --- cmake/OpenCVModule.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index 742a287ec9..ebeb25cf2b 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -224,6 +224,10 @@ macro(ocv_add_module _name) endif() if((NOT OPENCV_MODULE_${the_module}_IS_PART_OF_WORLD AND NOT ${the_module} STREQUAL opencv_world) OR NOT ${BUILD_opencv_world}) project(${the_module}) + add_definitions( + -D_USE_MATH_DEFINES # M_PI constant in MSVS + -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS # to use C libraries from C++ code (ffmpeg) + ) endif() endif() endmacro() From 8c66531c42c5c37ae2647a96f84e13abe2119e02 Mon Sep 17 00:00:00 2001 From: mshabunin Date: Thu, 12 Jan 2017 19:12:54 +0300 Subject: [PATCH 307/409] imgproc/CLAHE/ocl: Removed unnecessary __local variable --- modules/imgproc/src/opencl/clahe.cl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/modules/imgproc/src/opencl/clahe.cl b/modules/imgproc/src/opencl/clahe.cl index 187933ce0c..ba69085634 100644 --- a/modules/imgproc/src/opencl/clahe.cl +++ b/modules/imgproc/src/opencl/clahe.cl @@ -186,21 +186,13 @@ __kernel void calcLut(__global __const uchar * src, const int srcStep, #else clipped = smem[0]; #endif - - // broadcast evaluated value - - __local int totalClipped; - - if (tid == 0) - totalClipped = clipped; barrier(CLK_LOCAL_MEM_FENCE); // redistribute clipped samples evenly - - int redistBatch = totalClipped / 256; + int redistBatch = clipped / 256; tHistVal += redistBatch; - int residual = totalClipped - redistBatch * 256; + int residual = clipped - redistBatch * 256; int rStep = 256 / residual; if (rStep < 1) rStep = 1; From 256aa5332626464f6b6548946ae708062b79de5d Mon Sep 17 00:00:00 2001 From: Nicholas Nadeau Date: Mon, 16 Jan 2017 05:03:49 -0500 Subject: [PATCH 308/409] Merge pull request #7994 from nnadeau:master Fixed exceptions, print statements, and long types for gen_pattern.py to be Python 3 compatible (#7994) * fixed exceptions and print statements to be python 3 compatible; removed long type checks (py3 uses int); whitespace reformatting * Pulled latest svgfig from upstream https://github.com/jpivarski/svgfig/commit/f3179a8926508bf0f9021fd4e1f9731c95524a38 --- doc/pattern_tools/gen_pattern.py | 8 +-- doc/pattern_tools/svgfig.py | 104 +++++++++++++++++-------------- 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/doc/pattern_tools/gen_pattern.py b/doc/pattern_tools/gen_pattern.py index 85b3ea4955..34512bad27 100755 --- a/doc/pattern_tools/gen_pattern.py +++ b/doc/pattern_tools/gen_pattern.py @@ -70,9 +70,9 @@ def main(): opts, args = getopt.getopt(sys.argv[1:], "Ho:c:r:T:u:s:R:w:h:a:", ["help","output=","columns=","rows=", "type=","units=","square_size=","radius_rate=", "page_width=","page_height=", "page_size="]) - except getopt.error, msg: - print msg - print "for help use --help" + except getopt.error as msg: + print(msg) + print("for help use --help") sys.exit(2) output = "out.svg" columns = 8 @@ -89,7 +89,7 @@ def main(): # process options for o, a in opts: if o in ("-H", "--help"): - print __doc__ + print(__doc__) sys.exit(0) elif o in ("-r", "--rows"): rows = int(a) diff --git a/doc/pattern_tools/svgfig.py b/doc/pattern_tools/svgfig.py index c690c9ff08..5e1b1df45d 100755 --- a/doc/pattern_tools/svgfig.py +++ b/doc/pattern_tools/svgfig.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - # svgfig.py copyright (C) 2008 Jim Pivarski # # This program is free software; you can redistribute it and/or @@ -21,6 +19,15 @@ import re, codecs, os, platform, copy, itertools, math, cmath, random, sys, copy _epsilon = 1e-5 +if sys.version_info >= (3,0): + long = int + basestring = (str,bytes) + +# Fix Python 2.x. +try: + UNICODE_EXISTS = bool(type(unicode)) +except NameError: + unicode = lambda s: str(s) if re.search("windows", platform.system(), re.I): try: @@ -49,20 +56,21 @@ def rgb(r, g, b, maximum=1.): max(0, min(b*255./maximum, 255))) def attr_preprocess(attr): + attrCopy = attr.copy() for name in attr.keys(): name_colon = re.sub("__", ":", name) if name_colon != name: - attr[name_colon] = attr[name] - del attr[name] + attrCopy[name_colon] = attrCopy[name] + del attrCopy[name] name = name_colon name_dash = re.sub("_", "-", name) if name_dash != name: - attr[name_dash] = attr[name] - del attr[name] + attrCopy[name_dash] = attrCopy[name] + del attrCopy[name] name = name_dash - return attr + return attrCopy class SVG: @@ -128,7 +136,7 @@ class SVG: """ def __init__(self, *t_sub, **attr): if len(t_sub) == 0: - raise TypeError, "SVG element must have a t (SVG type)" + raise TypeError( "SVG element must have a t (SVG type)") # first argument is t (SVG type) self.t = t_sub[0] @@ -262,7 +270,7 @@ class SVG: Returns a breadth-first generator over the SVG. If depth_limit is a number, stop recursion at that depth.""" - raise NotImplementedError, "Got an algorithm for breadth-first searching a tree without effectively copying the tree?" + raise NotImplementedError( "Got an algorithm for breadth-first searching a tree without effectively copying the tree?") def __iter__(self): return self.depth_first() @@ -558,7 +566,7 @@ def canvas_outline(*sub, **attr): svg = canvas(*sub, **attr) match = re.match(r"[, \t]*([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]*", svg["viewBox"]) if match is None: - raise ValueError, "canvas viewBox is incorrectly formatted" + raise ValueError( "canvas viewBox is incorrectly formatted") x, y, width, height = [float(x) for x in match.groups()] svg.prepend(SVG("rect", x=x, y=y, width=width, height=height, stroke="none", fill="cornsilk")) svg.append(SVG("rect", x=x, y=y, width=width, height=height, stroke="black", fill="none")) @@ -675,7 +683,7 @@ def totrans(expr, vars=("x", "y"), globals=None, locals=None): return output else: - raise TypeError, "must be a function of 2 or 1 variables" + raise TypeError( "must be a function of 2 or 1 variables") if len(vars) == 2: g = math.__dict__ @@ -696,7 +704,7 @@ def totrans(expr, vars=("x", "y"), globals=None, locals=None): return output2 else: - raise TypeError, "vars must have 2 or 1 elements" + raise TypeError( "vars must have 2 or 1 elements") def window(xmin, xmax, ymin, ymax, x=0, y=0, width=100, height=100, @@ -735,10 +743,10 @@ def window(xmin, xmax, ymin, ymax, x=0, y=0, width=100, height=100, iy2 = ymax if xlogbase is not None and (ix1 <= 0. or ix2 <= 0.): - raise ValueError, "x range incompatible with log scaling: (%g, %g)" % (ix1, ix2) + raise ValueError ("x range incompatible with log scaling: (%g, %g)" % (ix1, ix2)) if ylogbase is not None and (iy1 <= 0. or iy2 <= 0.): - raise ValueError, "y range incompatible with log scaling: (%g, %g)" % (iy1, iy2) + raise ValueError ("y range incompatible with log scaling: (%g, %g)" % (iy1, iy2)) def maybelog(t, it1, it2, ot1, ot2, logbase): if t <= 0.: @@ -813,7 +821,7 @@ class Fig: self.trans = kwds["trans"]; del kwds["trans"] if len(kwds) != 0: - raise TypeError, "Fig() got unexpected keyword arguments %s" % kwds.keys() + raise TypeError ("Fig() got unexpected keyword arguments %s" % kwds.keys()) def SVG(self, trans=None): """Apply the transformation "trans" and return an SVG object. @@ -931,7 +939,7 @@ class Plot: self.text_attr = kwds["text_attr"]; del kwds["text_attr"] self.axis_attr = kwds["axis_attr"]; del kwds["axis_attr"] if len(kwds) != 0: - raise TypeError, "Plot() got unexpected keyword arguments %s" % kwds.keys() + raise TypeError ("Plot() got unexpected keyword arguments %s" % kwds.keys()) def SVG(self, trans=None): """Apply the transformation "trans" and return an SVG object.""" @@ -1039,7 +1047,7 @@ class Frame: self.axis_attr.update(kwds["axis_attr"]); del kwds["axis_attr"] if len(kwds) != 0: - raise TypeError, "Frame() got unexpected keyword arguments %s" % kwds.keys() + raise TypeError( "Frame() got unexpected keyword arguments %s" % kwds.keys()) def SVG(self): """Apply the window transformation and return an SVG object.""" @@ -1101,7 +1109,7 @@ class Frame: def pathtoPath(svg): """Converts SVG("path", d="...") into Path(d=[...]).""" if not isinstance(svg, SVG) or svg.t != "path": - raise TypeError, "Only SVG objects can be converted into Paths" + raise TypeError ("Only SVG objects can be converted into Paths") attr = dict(svg.attr) d = attr["d"] del attr["d"] @@ -1235,7 +1243,7 @@ class Path: errstring = "Path command \"%s\" requires a number at index %d" % (command, index) num1, index, pathdata = self.parse_number(index, pathdata) if num1 is None: - raise ValueError, errstring + raise ValueError ( errstring) while num1 is not None: output.append((command, num1)) @@ -1248,11 +1256,11 @@ class Path: num2, index, pathdata = self.parse_number(index, pathdata) if num1 is None: - raise ValueError, errstring + raise ValueError ( errstring) while num1 is not None: if num2 is None: - raise ValueError, errstring + raise ValueError ( errstring) output.append((command, num1, num2, False)) num1, index, pathdata = self.parse_number(index, pathdata) @@ -1267,11 +1275,11 @@ class Path: num4, index, pathdata = self.parse_number(index, pathdata) if num1 is None: - raise ValueError, errstring + raise ValueError ( errstring ) while num1 is not None: if num2 is None or num3 is None or num4 is None: - raise ValueError, errstring + raise ValueError (errstring) output.append((command, num1, num2, False, num3, num4, False)) num1, index, pathdata = self.parse_number(index, pathdata) @@ -1290,11 +1298,11 @@ class Path: num6, index, pathdata = self.parse_number(index, pathdata) if num1 is None: - raise ValueError, errstring + raise ValueError(errstring) while num1 is not None: if num2 is None or num3 is None or num4 is None or num5 is None or num6 is None: - raise ValueError, errstring + raise ValueError(errstring) output.append((command, num1, num2, False, num3, num4, False, num5, num6, False)) @@ -1317,11 +1325,11 @@ class Path: num7, index, pathdata = self.parse_number(index, pathdata) if num1 is None: - raise ValueError, errstring + raise ValueError(errstring) while num1 is not None: if num2 is None or num3 is None or num4 is None or num5 is None or num6 is None or num7 is None: - raise ValueError, errstring + raise ValueError(errstring) output.append((command, num1, num2, False, num3, num4, num5, num6, num7, False)) @@ -1344,7 +1352,7 @@ class Path: output = [] for datum in self.d: if not isinstance(datum, (tuple, list)): - raise TypeError, "pathdata elements must be tuples/lists" + raise TypeError("pathdata elements must be tuples/lists") command = datum[0] @@ -1722,7 +1730,7 @@ class Curve: try: # the best way to keep all the information while sampling is to make a linked list if not (self.low < self.high): - raise ValueError, "low must be less than high" + raise ValueError("low must be less than high") low, high = self.Sample(float(self.low)), self.Sample(float(self.high)) low.link(None, high) high.link(low, None) @@ -1913,10 +1921,10 @@ class Poly: vx[i], vy[i] = 0., 0. else: - raise ValueError, "mode must be \"lines\", \"bezier\", \"velocity\", \"foreback\", \"smooth\", or an abbreviation" + raise ValueError("mode must be \"lines\", \"bezier\", \"velocity\", \"foreback\", \"smooth\", or an abbreviation") d = [] - indexes = range(len(self.d)) + indexes = list(range(len(self.d))) if self.loop and len(self.d) > 0: indexes.append(0) @@ -2220,7 +2228,7 @@ class Line(Curve): defs.append(make_marker(self.arrow_start, "arrow_start")) line.attr["marker-start"] = "url(#%s)" % self.arrow_start else: - raise TypeError, "arrow_start must be False/None or an id string for the new marker" + raise TypeError("arrow_start must be False/None or an id string for the new marker") if self.arrow_end != False and self.arrow_end is not None: if isinstance(self.arrow_end, SVG): @@ -2230,7 +2238,7 @@ class Line(Curve): defs.append(make_marker(self.arrow_end, "arrow_end")) line.attr["marker-end"] = "url(#%s)" % self.arrow_end else: - raise TypeError, "arrow_end must be False/None or an id string for the new marker" + raise TypeError("arrow_end must be False/None or an id string for the new marker") return SVG("g", defs, line) @@ -2316,7 +2324,7 @@ class LineGlobal: defs.append(make_marker(self.arrow_start, "arrow_start")) line.attr["marker-start"] = "url(#%s)" % self.arrow_start else: - raise TypeError, "arrow_start must be False/None or an id string for the new marker" + raise TypeError("arrow_start must be False/None or an id string for the new marker") if self.arrow_end != False and self.arrow_end is not None: if isinstance(self.arrow_end, SVG): @@ -2326,7 +2334,7 @@ class LineGlobal: defs.append(make_marker(self.arrow_end, "arrow_end")) line.attr["marker-end"] = "url(#%s)" % self.arrow_end else: - raise TypeError, "arrow_end must be False/None or an id string for the new marker" + raise TypeError("arrow_end must be False/None or an id string for the new marker") return SVG("g", defs, line) @@ -2681,7 +2689,7 @@ class Ticks: elif isinstance(self.arrow_start, basestring): defs.append(make_marker(self.arrow_start, "arrow_start")) else: - raise TypeError, "arrow_start must be False/None or an id string for the new marker" + raise TypeError("arrow_start must be False/None or an id string for the new marker") if self.arrow_end != False and self.arrow_end is not None: if isinstance(self.arrow_end, SVG): @@ -2689,7 +2697,7 @@ class Ticks: elif isinstance(self.arrow_end, basestring): defs.append(make_marker(self.arrow_end, "arrow_end")) else: - raise TypeError, "arrow_end must be False/None or an id string for the new marker" + raise TypeError("arrow_end must be False/None or an id string for the new marker") output.append(defs) @@ -2757,7 +2765,7 @@ class Ticks: format = self.labels else: - raise TypeError, "labels must be None/False, True, a format string, or a number->string function" + raise TypeError("labels must be None/False, True, a format string, or a number->string function") # Now for the ticks ticks = self.ticks @@ -2793,7 +2801,7 @@ class Ticks: return ticks, [] else: - raise TypeError, "miniticks must be None/False, True, a number of desired miniticks, or a list of numbers" + raise TypeError("miniticks must be None/False, True, a number of desired miniticks, or a list of numbers") # Cases 3 & 4: ticks is iterable elif getattr(ticks, "__iter__", False): @@ -2830,10 +2838,10 @@ class Ticks: return ticks, [] else: - raise TypeError, "miniticks must be None/False, True, a number of desired miniticks, or a list of numbers" + raise TypeError("miniticks must be None/False, True, a number of desired miniticks, or a list of numbers") else: - raise TypeError, "ticks must be None/False, a number of desired ticks, a list of numbers, or a dictionary of explicit markers" + raise TypeError("ticks must be None/False, a number of desired ticks, a list of numbers, or a dictionary of explicit markers") def compute_ticks(self, N, format): """Return less than -N or exactly N optimal linear ticks. @@ -2841,9 +2849,9 @@ class Ticks: Normally only used internally. """ if self.low >= self.high: - raise ValueError, "low must be less than high" + raise ValueError("low must be less than high") if N == 1: - raise ValueError, "N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum" + raise ValueError("N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum") eps = _epsilon * (self.high - self.low) @@ -2948,7 +2956,7 @@ class Ticks: original_ticks.sort() if self.low > original_ticks[0] + _epsilon or self.high < original_ticks[-1] - _epsilon: - raise ValueError, "original_ticks {%g...%g} extend beyond [%g, %g]" % (original_ticks[0], original_ticks[-1], self.low, self.high) + raise ValueError("original_ticks {%g...%g} extend beyond [%g, %g]" % (original_ticks[0], original_ticks[-1], self.low, self.high)) granularities = [] for i in range(len(original_ticks)-1): @@ -2975,9 +2983,9 @@ class Ticks: Normally only used internally. """ if self.low >= self.high: - raise ValueError, "low must be less than high" + raise ValueError("low must be less than high") if N == 1: - raise ValueError, "N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum" + raise ValueError("N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum") eps = _epsilon * (self.high - self.low) @@ -3032,7 +3040,7 @@ class Ticks: Normally only used internally. """ if self.low >= self.high: - raise ValueError, "low must be less than high" + raise ValueError("low must be less than high") lowN = math.floor(math.log(self.low, base)) highN = math.ceil(math.log(self.high, base)) @@ -3166,7 +3174,7 @@ class LineAxis(Line, Ticks): def interpret(self): if self.exclude is not None and not (isinstance(self.exclude, (tuple, list)) and len(self.exclude) == 2 and isinstance(self.exclude[0], (int, long, float)) and isinstance(self.exclude[1], (int, long, float))): - raise TypeError, "exclude must either be None or (low, high)" + raise TypeError("exclude must either be None or (low, high)") ticks, miniticks = Ticks.interpret(self) if self.exclude is None: From a8e5d1d9fdae183762d4e06a7e25473d10ef1974 Mon Sep 17 00:00:00 2001 From: logic1988 <1988heqian@163.com> Date: Mon, 16 Jan 2017 18:19:24 +0800 Subject: [PATCH 309/409] Merge pull request #7978 from logic1988:master Fix error usage in HitMiss tutorial, and improved the visualization results (#7978) * Fix error usage in HitMiss tutorial, and improved the visualization results Fix error usage in HitMiss tutorial, and improved the visualization results * Update HitMiss.cpp * Update HitMiss.cpp --- samples/cpp/tutorial_code/ImgProc/HitMiss.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/samples/cpp/tutorial_code/ImgProc/HitMiss.cpp b/samples/cpp/tutorial_code/ImgProc/HitMiss.cpp index 0463aabe39..806eec489e 100644 --- a/samples/cpp/tutorial_code/ImgProc/HitMiss.cpp +++ b/samples/cpp/tutorial_code/ImgProc/HitMiss.cpp @@ -15,7 +15,7 @@ int main(){ 0, 255, 0, 255, 0, 0, 255, 0, 0, 255, 255, 255, 0, 0, 0, 0); - Mat kernel = (Mat_(3, 3) << + Mat kernel = (Mat_(3, 3) << 0, 1, 0, 1, -1, 1, 0, 1, 0); @@ -23,10 +23,15 @@ int main(){ Mat output_image; morphologyEx(input_image, output_image, MORPH_HITMISS, kernel); - namedWindow("Original", CV_WINDOW_NORMAL); + const int rate = 10; + kernel = (kernel + 1) * 127; + kernel.convertTo(kernel, CV_8U); + cv::resize(kernel, kernel, cv::Size(), rate, rate, INTER_NEAREST); + imshow("kernel", kernel); + cv::resize(input_image, input_image, cv::Size(), rate, rate, INTER_NEAREST); imshow("Original", input_image); - namedWindow("Hit or Miss", CV_WINDOW_NORMAL); + cv::resize(output_image, output_image, cv::Size(), rate, rate, INTER_NEAREST); imshow("Hit or Miss", output_image); waitKey(0); return 0; -} \ No newline at end of file +} From 89a740a62f66a2f55b7f3e87bad96e10d880f523 Mon Sep 17 00:00:00 2001 From: berak Date: Tue, 17 Jan 2017 11:57:50 +0100 Subject: [PATCH 310/409] Merge pull request #8019 from berak:patch-2 ml: fix small typo in lr.cpp (#8019) --- modules/ml/src/lr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ml/src/lr.cpp b/modules/ml/src/lr.cpp index 57fcac7ef0..c3c314228b 100644 --- a/modules/ml/src/lr.cpp +++ b/modules/ml/src/lr.cpp @@ -579,7 +579,7 @@ void LogisticRegressionImpl::write(FileStorage& fs) const CV_Error(CV_StsBadArg,"file can't open. Check file path"); } writeFormat(fs); - string desc = "Logisitic Regression Classifier"; + string desc = "Logistic Regression Classifier"; fs<<"classifier"<params.alpha; fs<<"iterations"<params.num_iters; From 2b44c0b6493726c465152e1db82cd8e65944d0db Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 23 Dec 2016 14:50:33 +0300 Subject: [PATCH 311/409] cmake: fix lapack - added compile check with multiple tries for different configurations - restored find_package(LAPACK) - avoided modification of OPENCV_LINKER_LIBS --- cmake/OpenCVFindLAPACK.cmake | 213 +++++++++++++++++++++++----------- cmake/checks/lapack_check.cpp | 14 +++ modules/core/CMakeLists.txt | 2 +- 3 files changed, 162 insertions(+), 67 deletions(-) create mode 100644 cmake/checks/lapack_check.cpp diff --git a/cmake/OpenCVFindLAPACK.cmake b/cmake/OpenCVFindLAPACK.cmake index dfacf24328..0bee1b4c90 100644 --- a/cmake/OpenCVFindLAPACK.cmake +++ b/cmake/OpenCVFindLAPACK.cmake @@ -1,78 +1,159 @@ -macro(_find_file_in_dirs VAR NAME DIRS) - find_path(${VAR} ${NAME} ${DIRS} NO_DEFAULT_PATH) - set(${VAR} ${${VAR}}/${NAME}) - unset(${VAR} CACHE) +macro(_find_header_file_in_dirs VAR NAME) + unset(${VAR}) + unset(${VAR} CACHE) + if(" ${ARGN}" STREQUAL " ") + check_include_file("${NAME}" HAVE_${VAR}) + if(HAVE_${VAR}) + set(${VAR} "${NAME}") # fallback + else() + set(${VAR} "") + endif() + else() + find_path(${VAR} "${NAME}" ${ARGN} NO_DEFAULT_PATH) + if(${VAR}) + set(${VAR} "${${VAR}}/${NAME}") + unset(${VAR} CACHE) + else() + unset(${VAR} CACHE) + set(${VAR} "") + endif() + endif() +endmacro() + +macro(ocv_lapack_check) + string(REGEX REPLACE "[^a-zA-Z0-9_]" "_" _lapack_impl "${LAPACK_IMPL}") + message(STATUS "LAPACK(${LAPACK_IMPL}): LAPACK_LIBRARIES: ${LAPACK_LIBRARIES}") + _find_header_file_in_dirs(OPENCV_CBLAS_H_PATH_${_lapack_impl} "${LAPACK_CBLAS_H}" "${LAPACK_INCLUDE_DIR}") + _find_header_file_in_dirs(OPENCV_LAPACKE_H_PATH_${_lapack_impl} "${LAPACK_LAPACKE_H}" "${LAPACK_INCLUDE_DIR}") + if(NOT OPENCV_CBLAS_H_PATH_${_lapack_impl} OR NOT OPENCV_LAPACKE_H_PATH_${_lapack_impl}) + message(WARNING "LAPACK(${LAPACK_IMPL}): CBLAS/LAPACK headers are not found in '${LAPACK_INCLUDE_DIR}'") + unset(LAPACK_LIBRARIES) + else() + # adding proxy opencv_lapack.h header + set(CBLAS_H_PROXY_PATH ${CMAKE_BINARY_DIR}/opencv_lapack.h) + set(_lapack_include_str "\#include \"${OPENCV_CBLAS_H_PATH_${_lapack_impl}}\"") + if(NOT "${OPENCV_CBLAS_H_PATH_${_lapack_impl}}" STREQUAL "${OPENCV_LAPACKE_H_PATH_${_lapack_impl}}") + set(_lapack_include_str "${_lapack_include_str}\n#include \"${OPENCV_LAPACKE_H_PATH_${_lapack_impl}}\"") + endif() + # update file contents (if required) + set(__content_str "") + if(EXISTS "${CBLAS_H_PROXY_PATH}") + file(READ "${CBLAS_H_PROXY_PATH}" __content_str) + endif() + if(NOT " ${__content_str}" STREQUAL " ${_lapack_include_str}") + file(WRITE "${CBLAS_H_PROXY_PATH}" "${_lapack_include_str}") + endif() + + try_compile(__VALID_LAPACK + "${OpenCV_BINARY_DIR}" + "${OpenCV_SOURCE_DIR}/cmake/checks/lapack_check.cpp" + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${LAPACK_INCLUDE_DIR}\;${CMAKE_BINARY_DIR}" + "-DLINK_DIRECTORIES:STRING=${LAPACK_LINK_LIBRARIES}" + "-DLINK_LIBRARIES:STRING=${LAPACK_LIBRARIES}" + OUTPUT_VARIABLE TRY_OUT + ) + if(NOT __VALID_LAPACK) + #message(FATAL_ERROR "LAPACK: check build log:\n${TRY_OUT}") + message(STATUS "LAPACK(${LAPACK_IMPL}): Can't build LAPACK check code. This LAPACK version is not supported.") + unset(LAPACK_LIBRARIES) + else() + message(STATUS "LAPACK(${LAPACK_IMPL}): Support is enabled.") + ocv_include_directories(${LAPACK_INCLUDE_DIR}) + set(HAVE_LAPACK 1) + endif() + endif() endmacro() if(WITH_LAPACK) - ocv_update(LAPACK_IMPL "Unknown") - if(NOT LAPACK_LIBRARIES) - include(cmake/OpenCVFindMKL.cmake) - if(HAVE_MKL) - set(LAPACK_INCLUDE_DIR ${MKL_INCLUDE_DIRS}) - set(LAPACK_LIBRARIES ${MKL_LIBRARIES} ) - set(LAPACK_CBLAS_H "mkl_cblas.h" ) - set(LAPACK_LAPACKE_H "mkl_lapack.h" ) - set(LAPACK_IMPL "MKL") - endif() + ocv_update(LAPACK_IMPL "Unknown") + if(NOT OPENCV_LAPACK_FIND_PACKAGE_ONLY) + if(NOT LAPACK_LIBRARIES AND NOT OPENCV_LAPACK_DISABLE_MKL) + include(cmake/OpenCVFindMKL.cmake) + if(HAVE_MKL) + set(LAPACK_INCLUDE_DIR ${MKL_INCLUDE_DIRS}) + set(LAPACK_LIBRARIES ${MKL_LIBRARIES}) + set(LAPACK_CBLAS_H "mkl_cblas.h") + set(LAPACK_LAPACKE_H "mkl_lapack.h") + set(LAPACK_IMPL "MKL") + ocv_lapack_check() + endif() endif() if(NOT LAPACK_LIBRARIES) - include(cmake/OpenCVFindOpenBLAS.cmake) - if(OpenBLAS_FOUND) - set(LAPACK_INCLUDE_DIR ${OpenBLAS_INCLUDE_DIR} ) - set(LAPACK_LIBRARIES ${OpenBLAS_LIB} ) - set(LAPACK_CBLAS_H "cblas.h" ) - set(LAPACK_LAPACKE_H "lapacke.h" ) - set(LAPACK_IMPL "OpenBLAS") - endif() + include(cmake/OpenCVFindOpenBLAS.cmake) + if(OpenBLAS_FOUND) + set(LAPACK_INCLUDE_DIR ${OpenBLAS_INCLUDE_DIR}) + set(LAPACK_LIBRARIES ${OpenBLAS_LIB}) + set(LAPACK_CBLAS_H "cblas.h") + set(LAPACK_LAPACKE_H "lapacke.h") + set(LAPACK_IMPL "OpenBLAS") + ocv_lapack_check() + endif() endif() if(NOT LAPACK_LIBRARIES AND UNIX) - include(cmake/OpenCVFindAtlas.cmake) - if(ATLAS_FOUND) - set(LAPACK_INCLUDE_DIR ${Atlas_INCLUDE_DIR}) - set(LAPACK_LIBRARIES ${Atlas_LIBRARIES} ) - set(LAPACK_CBLAS_H "cblas.h" ) - set(LAPACK_LAPACKE_H "lapacke.h" ) - set(LAPACK_IMPL "Atlas") - endif() + include(cmake/OpenCVFindAtlas.cmake) + if(ATLAS_FOUND) + set(LAPACK_INCLUDE_DIR ${Atlas_INCLUDE_DIR}) + set(LAPACK_LIBRARIES ${Atlas_LIBRARIES}) + set(LAPACK_CBLAS_H "cblas.h") + set(LAPACK_LAPACKE_H "lapacke.h") + set(LAPACK_IMPL "Atlas") + ocv_lapack_check() + endif() endif() + endif() - if(NOT LAPACK_LIBRARIES AND APPLE) - set(LAPACK_INCLUDE_DIR "Accelerate") - set(LAPACK_LIBRARIES "-framework Accelerate") - set(LAPACK_CBLAS_H "cblas.h" ) - set(LAPACK_LAPACKE_H "lapacke.h" ) - set(LAPACK_IMPL "Apple") + if(NOT LAPACK_LIBRARIES) + if(WIN32 AND NOT OPENCV_LAPACK_SHARED_LIBS) + set(BLA_STATIC 1) endif() - - set(LAPACK_INCLUDE_DIR ${LAPACK_INCLUDE_DIR} CACHE PATH "Path to BLAS include dir" FORCE) - set(LAPACK_CBLAS_H ${LAPACK_CBLAS_H} CACHE STRING "Alternative name of cblas.h" FORCE) - set(LAPACK_LAPACKE_H ${LAPACK_LAPACKE_H} CACHE STRING "Alternative name of lapacke.h" FORCE) - set(LAPACK_LIBRARIES ${LAPACK_LIBRARIES} CACHE STRING "Names of BLAS & LAPACK binaries (.so, .dll, .a, .lib)" FORCE) - set(LAPACK_IMPL ${LAPACK_IMPL} CACHE STRING "Lapack implementation id" FORCE) - - if(LAPACK_LIBRARIES) #adding proxy cblas.h header - message(STATUS "LAPACK_IMPL: ${LAPACK_IMPL}, LAPACK_LIBRARIES: ${LAPACK_LIBRARIES}") - if("${LAPACK_IMPL}" STREQUAL "Apple") - set(CBLAS_H_PATH "Accelerate/Accelerate.h") - set(LAPACKE_H_PATH "Accelerate/Accelerate.h") - else() - _find_file_in_dirs(CBLAS_H_PATH "${LAPACK_CBLAS_H}" "${LAPACK_INCLUDE_DIR}") - _find_file_in_dirs(LAPACKE_H_PATH "${LAPACK_LAPACKE_H}" "${LAPACK_INCLUDE_DIR}") - endif() - if(NOT CBLAS_H_PATH OR NOT LAPACKE_H_PATH) - message(WARNING "CBLAS/LAPACK headers are not found in '${LAPACK_INCLUDE_DIR}'") - endif() - ocv_include_directories(${LAPACK_INCLUDE_DIR}) - list(APPEND OPENCV_LINKER_LIBS ${LAPACK_LIBRARIES}) - set(HAVE_LAPACK 1) - - set(CBLAS_H_PROXY_PATH ${CMAKE_BINARY_DIR}/opencv_lapack.h) - set(_include_str "\#include \"${CBLAS_H_PATH}\"") - if("${CBLAS_H_PATH}" STREQUAL "${LAPACKE_H_PATH}") - else() - set(_include_str "${_include_str}\n\#include \"${LAPACKE_H_PATH}\"") - endif() - file(WRITE ${CBLAS_H_PROXY_PATH} ${_include_str}) + find_package(LAPACK) + if(LAPACK_FOUND) + if(NOT DEFINED LAPACKE_INCLUDE_DIR) + find_path(LAPACKE_INCLUDE_DIR "lapacke.h") + endif() + if(NOT DEFINED MKL_LAPACKE_INCLUDE_DIR) + find_path(MKL_LAPACKE_INCLUDE_DIR "mkl_lapack.h") + endif() + if(MKL_LAPACKE_INCLUDE_DIR AND NOT OPENCV_LAPACK_DISABLE_MKL) + set(LAPACK_INCLUDE_DIR ${MKL_LAPACKE_INCLUDE_DIR}) + set(LAPACK_CBLAS_H "mkl_cblas.h") + set(LAPACK_LAPACKE_H "mkl_lapack.h") + set(LAPACK_IMPL "LAPACK/MKL") + ocv_lapack_check() + endif() + if(LAPACKE_INCLUDE_DIR AND NOT HAVE_LAPACK) + set(LAPACK_INCLUDE_DIR ${LAPACKE_INCLUDE_DIR}) + set(LAPACK_CBLAS_H "cblas.h") + set(LAPACK_LAPACKE_H "lapacke.h") + set(LAPACK_IMPL "LAPACK/Generic") + ocv_lapack_check() + elseif(APPLE) + set(LAPACK_CBLAS_H "Accelerate/Accelerate.h") + set(LAPACK_LAPACKE_H "Accelerate/Accelerate.h") + set(LAPACK_IMPL "LAPACK/Apple") + ocv_lapack_check() + else() + unset(LAPACK_LIBRARIES CACHE) + endif() endif() + endif() + + if(NOT LAPACK_LIBRARIES AND APPLE AND NOT OPENCV_LAPACK_FIND_PACKAGE_ONLY) + set(LAPACK_INCLUDE_DIR "") + set(LAPACK_LIBRARIES "-framework Accelerate") + set(LAPACK_CBLAS_H "Accelerate/Accelerate.h") + set(LAPACK_LAPACKE_H "Accelerate/Accelerate.h") + set(LAPACK_IMPL "Apple") + ocv_lapack_check() + endif() + + if(NOT HAVE_LAPACK AND LAPACK_LIBRARIES) + ocv_lapack_check() + endif() + + set(LAPACK_INCLUDE_DIR ${LAPACK_INCLUDE_DIR} CACHE PATH "Path to BLAS include dir" FORCE) + set(LAPACK_CBLAS_H ${LAPACK_CBLAS_H} CACHE STRING "Alternative name of cblas.h" FORCE) + set(LAPACK_LAPACKE_H ${LAPACK_LAPACKE_H} CACHE STRING "Alternative name of lapacke.h" FORCE) + set(LAPACK_LIBRARIES ${LAPACK_LIBRARIES} CACHE STRING "Names of BLAS & LAPACK binaries (.so, .dll, .a, .lib)" FORCE) + set(LAPACK_IMPL ${LAPACK_IMPL} CACHE STRING "Lapack implementation id" FORCE) endif() diff --git a/cmake/checks/lapack_check.cpp b/cmake/checks/lapack_check.cpp new file mode 100644 index 0000000000..0457c44d68 --- /dev/null +++ b/cmake/checks/lapack_check.cpp @@ -0,0 +1,14 @@ +#include "opencv_lapack.h" + +static char* check_fn1 = (char*)sgesv_; +static char* check_fn2 = (char*)sposv_; +static char* check_fn3 = (char*)spotrf_; +static char* check_fn4 = (char*)sgesdd_; + +int main(int argc, char* argv[]) +{ + (void)argv; + if(argc > 1000) + return check_fn1[0] + check_fn2[0] + check_fn3[0] + check_fn4[0]; + return 0; +} diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index 0485a08ad3..41da825457 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -1,7 +1,7 @@ set(the_description "The Core Functionality") ocv_add_module(core "${OPENCV_HAL_LINKER_LIBS}" - PRIVATE_REQUIRED ${ZLIB_LIBRARIES} "${OPENCL_LIBRARIES}" "${VA_LIBRARIES}" + PRIVATE_REQUIRED ${ZLIB_LIBRARIES} "${OPENCL_LIBRARIES}" "${VA_LIBRARIES}" "${LAPACK_LIBRARIES}" OPTIONAL opencv_cudev WRAP java python) From 3668a01fca0858c32b4083dfd0215eb7bd8fa263 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 17 Jan 2017 15:52:45 +0300 Subject: [PATCH 312/409] eliminate warnings hal_internal.cpp(101): warning C4267: 'initializing': conversion from 'size_t' to 'int', possible loss of data --- modules/core/src/hal_internal.cpp | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/core/src/hal_internal.cpp b/modules/core/src/hal_internal.cpp index b2b6dc3626..345ca42dc6 100644 --- a/modules/core/src/hal_internal.cpp +++ b/modules/core/src/hal_internal.cpp @@ -98,7 +98,7 @@ set_value(fptype *dst, size_t dst_ld, fptype value, size_t m, size_t n) template static inline int lapack_LU(fptype* a, size_t a_step, int m, fptype* b, size_t b_step, int n, int* info) { - int lda = a_step / sizeof(fptype), sign = 0; + int lda = (int)(a_step / sizeof(fptype)), sign = 0; int* piv = new int[m]; transpose_square_inplace(a, lda, m); @@ -114,7 +114,7 @@ lapack_LU(fptype* a, size_t a_step, int m, fptype* b, size_t b_step, int n, int* } else { - int ldb = b_step / sizeof(fptype); + int ldb = (int)(b_step / sizeof(fptype)); fptype* tmpB = new fptype[m*n]; transpose(b, ldb, tmpB, m, m, n); @@ -153,7 +153,7 @@ template static inline int lapack_Cholesky(fptype* a, size_t a_step, int m, fptype* b, size_t b_step, int n, bool* info) { int lapackStatus = 0; - int lda = a_step / sizeof(fptype); + int lda = (int)(a_step / sizeof(fptype)); char L[] = {'L', '\0'}; if(b) @@ -167,7 +167,7 @@ lapack_Cholesky(fptype* a, size_t a_step, int m, fptype* b, size_t b_step, int n } else { - int ldb = b_step / sizeof(fptype); + int ldb = (int)(b_step / sizeof(fptype)); fptype* tmpB = new fptype[m*n]; transpose(b, ldb, tmpB, m, m, n); @@ -197,9 +197,9 @@ lapack_Cholesky(fptype* a, size_t a_step, int m, fptype* b, size_t b_step, int n template static inline int lapack_SVD(fptype* a, size_t a_step, fptype *w, fptype* u, size_t u_step, fptype* vt, size_t v_step, int m, int n, int flags, int* info) { - int lda = a_step / sizeof(fptype); - int ldv = v_step / sizeof(fptype); - int ldu = u_step / sizeof(fptype); + int lda = (int)(a_step / sizeof(fptype)); + int ldv = (int)(v_step / sizeof(fptype)); + int ldu = (int)(u_step / sizeof(fptype)); int lwork = -1; int* iworkBuf = new int[8*std::min(m, n)]; fptype work1 = 0; @@ -256,7 +256,7 @@ lapack_SVD(fptype* a, size_t a_step, fptype *w, fptype* u, size_t u_step, fptype template static inline int lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_step, fptype* dst, int* info) { - int lda = a_step / sizeof(fptype); + int lda = (int)(a_step / sizeof(fptype)); char mode[] = { 'N', '\0' }; if(m < n) return CV_HAL_ERROR_NOT_IMPLEMENTED; @@ -303,7 +303,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste { std::vector tmpBMemHolder(m*k); fptype* tmpB = &tmpBMemHolder.front(); - int ldb = b_step / sizeof(fptype); + int ldb = (int)(b_step / sizeof(fptype)); transpose(b, ldb, tmpB, m, m, k); if (typeid(fptype) == typeid(float)) @@ -357,10 +357,10 @@ template static inline int lapack_gemm(const fptype *src1, size_t src1_step, const fptype *src2, size_t src2_step, fptype alpha, const fptype *src3, size_t src3_step, fptype beta, fptype *dst, size_t dst_step, int a_m, int a_n, int d_n, int flags) { - int ldsrc1 = src1_step / sizeof(fptype); - int ldsrc2 = src2_step / sizeof(fptype); - int ldsrc3 = src3_step / sizeof(fptype); - int lddst = dst_step / sizeof(fptype); + int ldsrc1 = (int)(src1_step / sizeof(fptype)); + int ldsrc2 = (int)(src2_step / sizeof(fptype)); + int ldsrc3 = (int)(src3_step / sizeof(fptype)); + int lddst = (int)(dst_step / sizeof(fptype)); int c_m, c_n, d_m; CBLAS_TRANSPOSE transA, transB; @@ -434,10 +434,10 @@ template static inline int lapack_gemm_c(const fptype *src1, size_t src1_step, const fptype *src2, size_t src2_step, fptype alpha, const fptype *src3, size_t src3_step, fptype beta, fptype *dst, size_t dst_step, int a_m, int a_n, int d_n, int flags) { - int ldsrc1 = src1_step / sizeof(std::complex); - int ldsrc2 = src2_step / sizeof(std::complex); - int ldsrc3 = src3_step / sizeof(std::complex); - int lddst = dst_step / sizeof(std::complex); + int ldsrc1 = (int)(src1_step / sizeof(std::complex)); + int ldsrc2 = (int)(src2_step / sizeof(std::complex)); + int ldsrc3 = (int)(src3_step / sizeof(std::complex)); + int lddst = (int)(dst_step / sizeof(std::complex)); int c_m, c_n, d_m; CBLAS_TRANSPOSE transA, transB; std::complex cAlpha(alpha, 0.0); From 896c34fab3fa49ec5d913305040b1eab15589d3a Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Tue, 17 Jan 2017 16:40:38 +0300 Subject: [PATCH 313/409] Add support of type headings from YAML1.2 --- modules/core/src/persistence.cpp | 20 ++++++++++++++++++++ modules/core/test/test_io.cpp | 17 +++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index bf16c2f398..946875f5b1 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -1475,6 +1475,26 @@ icvYMLParseValue( CvFileStorage* fs, char* ptr, CvFileNode* node, ptr++; value_type |= CV_NODE_USER; } + if ( d == '<') //support of full type heading from YAML 1.2 + { + const char* yamlTypeHeading = "' && (size_t)(typeEndPtr - ptr) > headingLenght ) + { + if ( memcmp(ptr, yamlTypeHeading, headingLenght) == 0 ) + { + value_type |= CV_NODE_USER; + *typeEndPtr = ' '; + ptr += headingLenght - 1; + } + } + } endptr = ptr++; do d = *++endptr; diff --git a/modules/core/test/test_io.cpp b/modules/core/test/test_io.cpp index eb02a7b23d..880d5cb1d8 100644 --- a/modules/core/test/test_io.cpp +++ b/modules/core/test/test_io.cpp @@ -996,3 +996,20 @@ TEST(Core_InputOutput, filestorage_vec_vec_io) remove((fileName + formats[i]).c_str()); } } + +TEST(Core_InputOutput, filestorage_yaml_advanvced_type_heading) +{ + String content = "%YAML:1.0\n cameraMatrix: !\n" + " rows: 1\n" + " cols: 1\n" + " dt: d\n" + " data: [ 1. ]"; + + cv::FileStorage fs(content, cv::FileStorage::READ | cv::FileStorage::MEMORY); + + cv::Mat inputMatrix; + cv::Mat actualMatrix = cv::Mat::eye(1, 1, CV_64F); + fs["cameraMatrix"] >> inputMatrix; + + ASSERT_EQ(cv::norm(inputMatrix, actualMatrix, NORM_INF), 0.); +} From e357e2c1b7d8624bf9290b27310b80615b0d3be6 Mon Sep 17 00:00:00 2001 From: catree Date: Tue, 17 Jan 2017 16:09:27 +0100 Subject: [PATCH 314/409] Fix typos in py_houghlines tutorial. Extract the python code in a specific folder. --- .../py_houghlines/py_houghlines.markdown | 100 ++++++------------ .../hough_line_transform.py | 22 ++++ .../probabilistic_hough_line_transform.py | 12 +++ 3 files changed, 66 insertions(+), 68 deletions(-) create mode 100644 samples/python/tutorial_code/imgProc/hough_line_transform/hough_line_transform.py create mode 100644 samples/python/tutorial_code/imgProc/hough_line_transform/probabilistic_hough_line_transform.py diff --git a/doc/py_tutorials/py_imgproc/py_houghlines/py_houghlines.markdown b/doc/py_tutorials/py_imgproc/py_houghlines/py_houghlines.markdown index 5b569ed074..f42d6ad226 100644 --- a/doc/py_tutorials/py_imgproc/py_houghlines/py_houghlines.markdown +++ b/doc/py_tutorials/py_imgproc/py_houghlines/py_houghlines.markdown @@ -5,36 +5,36 @@ Goal ---- In this chapter, - - We will understand the concept of Hough Tranform. - - We will see how to use it detect lines in an image. - - We will see following functions: **cv2.HoughLines()**, **cv2.HoughLinesP()** + - We will understand the concept of the Hough Transform. + - We will see how to use it to detect lines in an image. + - We will see the following functions: **cv2.HoughLines()**, **cv2.HoughLinesP()** Theory ------ -Hough Transform is a popular technique to detect any shape, if you can represent that shape in +The Hough Transform is a popular technique to detect any shape, if you can represent that shape in a mathematical form. It can detect the shape even if it is broken or distorted a little bit. We will see how it works for a line. -A line can be represented as \f$y = mx+c\f$ or in parametric form, as -\f$\rho = x \cos \theta + y \sin \theta\f$ where \f$\rho\f$ is the perpendicular distance from origin to the -line, and \f$\theta\f$ is the angle formed by this perpendicular line and horizontal axis measured in -counter-clockwise ( That direction varies on how you represent the coordinate system. This -representation is used in OpenCV). Check below image: +A line can be represented as \f$y = mx+c\f$ or in a parametric form, as +\f$\rho = x \cos \theta + y \sin \theta\f$ where \f$\rho\f$ is the perpendicular distance from the origin to the +line, and \f$\theta\f$ is the angle formed by this perpendicular line and the horizontal axis measured in +counter-clockwise (That direction varies on how you represent the coordinate system. This +representation is used in OpenCV). Check the image below: ![image](images/houghlines1.svg) -So if line is passing below the origin, it will have a positive rho and angle less than 180. If it -is going above the origin, instead of taking angle greater than 180, angle is taken less than 180, +So if the line is passing below the origin, it will have a positive rho and an angle less than 180. If it +is going above the origin, instead of taking an angle greater than 180, the angle is taken less than 180, and rho is taken negative. Any vertical line will have 0 degree and horizontal lines will have 90 degree. -Now let's see how Hough Transform works for lines. Any line can be represented in these two terms, -\f$(\rho, \theta)\f$. So first it creates a 2D array or accumulator (to hold values of two parameters) +Now let's see how the Hough Transform works for lines. Any line can be represented in these two terms, +\f$(\rho, \theta)\f$. So first it creates a 2D array or accumulator (to hold the values of the two parameters) and it is set to 0 initially. Let rows denote the \f$\rho\f$ and columns denote the \f$\theta\f$. Size of -array depends on the accuracy you need. Suppose you want the accuracy of angles to be 1 degree, you +array depends on the accuracy you need. Suppose you want the accuracy of angles to be 1 degree, you will need 180 columns. For \f$\rho\f$, the maximum distance possible is the diagonal length of the image. So -taking one pixel accuracy, number of rows can be diagonal length of the image. +taking one pixel accuracy, the number of rows can be the diagonal length of the image. Consider a 100x100 image with a horizontal line at the middle. Take the first point of the line. You know its (x,y) values. Now in the line equation, put the values \f$\theta = 0,1,2,....,180\f$ and check @@ -42,57 +42,34 @@ the \f$\rho\f$ you get. For every \f$(\rho, \theta)\f$ pair, you increment value in its corresponding \f$(\rho, \theta)\f$ cells. So now in accumulator, the cell (50,90) = 1 along with some other cells. -Now take the second point on the line. Do the same as above. Increment the the values in the cells +Now take the second point on the line. Do the same as above. Increment the values in the cells corresponding to `(rho, theta)` you got. This time, the cell (50,90) = 2. What you actually do is voting the \f$(\rho, \theta)\f$ values. You continue this process for every point on the line. At each point, the cell (50,90) will be incremented or voted up, while other cells may or may not be voted up. This way, at the end, the cell (50,90) will have maximum votes. So if you search the accumulator for maximum votes, you get the value (50,90) which says, there is a line in this image -at distance 50 from origin and at angle 90 degrees. It is well shown in below animation (Image +at a distance 50 from the origin and at angle 90 degrees. It is well shown in the below animation (Image Courtesy: [Amos Storkey](http://homepages.inf.ed.ac.uk/amos/hough.html) ) ![](images/houghlinesdemo.gif) -This is how hough transform for lines works. It is simple, and may be you can implement it using +This is how hough transform works for lines. It is simple, and may be you can implement it using Numpy on your own. Below is an image which shows the accumulator. Bright spots at some locations -denotes they are the parameters of possible lines in the image. (Image courtesy: [Wikipedia](http://en.wikipedia.org/wiki/Hough_transform)) +denote they are the parameters of possible lines in the image. (Image courtesy: [Wikipedia](http://en.wikipedia.org/wiki/Hough_transform) ) ![](images/houghlines2.jpg) Hough Transform in OpenCV ========================= -Everything explained above is encapsulated in the OpenCV function, \*\*cv2.HoughLines()\*\*. It simply returns an array of :math:(rho, +Everything explained above is encapsulated in the OpenCV function, **cv2.HoughLines()**. It simply returns an array of :math:(rho, theta)\` values. \f$\rho\f$ is measured in pixels and \f$\theta\f$ is measured in radians. First parameter, -Input image should be a binary image, so apply threshold or use canny edge detection before finding +Input image should be a binary image, so apply threshold or use canny edge detection before applying hough transform. Second and third parameters are \f$\rho\f$ and \f$\theta\f$ accuracies -respectively. Fourth argument is the threshold, which means minimum vote it should get for it to be -considered as a line. Remember, number of votes depend upon number of points on the line. So it +respectively. Fourth argument is the threshold, which means the minimum vote it should get to be +considered as a line. Remember, number of votes depends upon the number of points on the line. So it represents the minimum length of line that should be detected. -@code{.py} -import cv2 -import numpy as np - -img = cv2.imread('sudoku.png') -gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) -edges = cv2.Canny(gray,50,150,apertureSize = 3) - -lines = cv2.HoughLines(edges,1,np.pi/180,200) -for line in lines: - rho,theta = line[0] - a = np.cos(theta) - b = np.sin(theta) - x0 = a*rho - y0 = b*rho - x1 = int(x0 + 1000*(-b)) - y1 = int(y0 + 1000*(a)) - x2 = int(x0 - 1000*(-b)) - y2 = int(y0 - 1000*(a)) - - cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2) - -cv2.imwrite('houghlines3.jpg',img) -@endcode +@include hough_line_transform.py Check the results below: ![image](images/houghlines3.jpg) @@ -101,36 +78,23 @@ Probabilistic Hough Transform ----------------------------- In the hough transform, you can see that even for a line with two arguments, it takes a lot of -computation. Probabilistic Hough Transform is an optimization of Hough Transform we saw. It doesn't -take all the points into consideration, instead take only a random subset of points and that is -sufficient for line detection. Just we have to decrease the threshold. See below image which compare -Hough Transform and Probabilistic Hough Transform in hough space. (Image Courtesy : [Franck -Bettinger's home page](http://phdfb1.free.fr/robot/mscthesis/node14.html) +computation. Probabilistic Hough Transform is an optimization of the Hough Transform we saw. It doesn't +take all the points into consideration. Instead, it takes only a random subset of points which is +sufficient for line detection. Just we have to decrease the threshold. See image below which compares +Hough Transform and Probabilistic Hough Transform in Hough space. (Image Courtesy : +[Franck Bettinger's home page](http://phdfb1.free.fr/robot/mscthesis/node14.html) ) ![image](images/houghlines4.png) OpenCV implementation is based on Robust Detection of Lines Using the Progressive Probabilistic -Hough Transform by Matas, J. and Galambos, C. and Kittler, J.V.. The function used is +Hough Transform by Matas, J. and Galambos, C. and Kittler, J.V. @cite Matas00. The function used is **cv2.HoughLinesP()**. It has two new arguments. - **minLineLength** - Minimum length of line. Line segments shorter than this are rejected. -- **maxLineGap** - Maximum allowed gap between line segments to treat them as single line. +- **maxLineGap** - Maximum allowed gap between line segments to treat them as a single line. Best thing is that, it directly returns the two endpoints of lines. In previous case, you got only the parameters of lines, and you had to find all the points. Here, everything is direct and simple. -@code{.py} -import cv2 -import numpy as np - -img = cv2.imread('sudoku.png') -gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) -edges = cv2.Canny(gray,50,150,apertureSize = 3) -lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength=100,maxLineGap=10) -for line in lines: - x1,y1,x2,y2 = line[0] - cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2) - -cv2.imwrite('houghlines5.jpg',img) -@endcode +@include probabilistic_hough_line_transform.py See the results below: ![image](images/houghlines5.jpg) diff --git a/samples/python/tutorial_code/imgProc/hough_line_transform/hough_line_transform.py b/samples/python/tutorial_code/imgProc/hough_line_transform/hough_line_transform.py new file mode 100644 index 0000000000..0bcf6c5e43 --- /dev/null +++ b/samples/python/tutorial_code/imgProc/hough_line_transform/hough_line_transform.py @@ -0,0 +1,22 @@ +import cv2 +import numpy as np + +img = cv2.imread('../data/sudoku.png') +gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) +edges = cv2.Canny(gray,50,150,apertureSize = 3) + +lines = cv2.HoughLines(edges,1,np.pi/180,200) +for line in lines: + rho,theta = line[0] + a = np.cos(theta) + b = np.sin(theta) + x0 = a*rho + y0 = b*rho + x1 = int(x0 + 1000*(-b)) + y1 = int(y0 + 1000*(a)) + x2 = int(x0 - 1000*(-b)) + y2 = int(y0 - 1000*(a)) + + cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2) + +cv2.imwrite('houghlines3.jpg',img) diff --git a/samples/python/tutorial_code/imgProc/hough_line_transform/probabilistic_hough_line_transform.py b/samples/python/tutorial_code/imgProc/hough_line_transform/probabilistic_hough_line_transform.py new file mode 100644 index 0000000000..2d000a1226 --- /dev/null +++ b/samples/python/tutorial_code/imgProc/hough_line_transform/probabilistic_hough_line_transform.py @@ -0,0 +1,12 @@ +import cv2 +import numpy as np + +img = cv2.imread('../data/sudoku.png') +gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) +edges = cv2.Canny(gray,50,150,apertureSize = 3) +lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength=100,maxLineGap=10) +for line in lines: + x1,y1,x2,y2 = line[0] + cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2) + +cv2.imwrite('houghlines5.jpg',img) From 00d2e279d77abd79c48413979d4b53020ffd1668 Mon Sep 17 00:00:00 2001 From: catree Date: Tue, 17 Jan 2017 17:47:27 +0100 Subject: [PATCH 315/409] Fix tutorial code link in basic_linear_transform tutorial. Add some resource links. --- .../basic_linear_transform.markdown | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown b/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown index 05dd4db13f..a035199ee9 100644 --- a/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown +++ b/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown @@ -164,13 +164,13 @@ The following image has been corrected with: \f$ \alpha = 1.3 \f$ and \f$ \beta ![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg) The overall brightness has been improved but you can notice that the clouds are now greatly saturated due to the numerical saturation -of the implementation used. A custom method that preserves the original color range can of course be implemented instead. +of the implementation used ([highlight clipping](https://en.wikipedia.org/wiki/Clipping_(photography)) in photography). The following image has been corrected with: \f$ \gamma = 0.4 \f$. ![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg) -The gamma correction should tend to add less saturation effect but should introduce some other type of color artifacts instead. +The gamma correction should tend to add less saturation effect as the mapping is non linear and there is no numerical saturation possible as in the previous method. ![Left: histogram after alpha, beta correction ; Center: histogram of the original image ; Right: histogram after the gamma correction](images/Basic_Linear_Transform_Tutorial_histogram_compare.png) @@ -185,8 +185,15 @@ and are not intended to be used as a replacement of a raster graphics editor!** ### Code -Code for the tutorial is [here](changing_contrast_brightness_image.cpp). Code for the gamma correction: +Code for the tutorial is [here](https://github.com/opencv/opencv/blob/master/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp). +Code for the gamma correction: @snippet changing_contrast_brightness_image.cpp changing-contrast-brightness-gamma-correction A look-up table is used to improve the performance of the computation as only 256 values needs to be calculated once. + +### Additional resources + +- [Gamma correction in graphics rendering](https://learnopengl.com/#!Advanced-Lighting/Gamma-Correction) +- [Gamma correction and images displayed on CRT monitors](http://www.graphics.cornell.edu/~westin/gamma/gamma.html) +- [Digital exposure techniques](http://www.cambridgeincolour.com/tutorials/digital-exposure-techniques.htm) From a2b6b595c20c15f7babe7e01b68df0e83be8c67f Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 17 Jan 2017 15:22:56 +0300 Subject: [PATCH 316/409] shape: force column-based vector --- modules/python/test/test_shape.py | 23 +++++++++++++++++++++++ modules/shape/src/haus_dis.cpp | 7 +++++++ modules/shape/src/sc_dis.cpp | 7 +++++++ modules/shape/test/test_shape.cpp | 19 +++++++++++++++++++ 4 files changed, 56 insertions(+) create mode 100644 modules/python/test/test_shape.py diff --git a/modules/python/test/test_shape.py b/modules/python/test/test_shape.py new file mode 100644 index 0000000000..ad0f0be5d5 --- /dev/null +++ b/modules/python/test/test_shape.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +import cv2 + +from tests_common import NewOpenCVTests + +class shape_test(NewOpenCVTests): + + def test_computeDistance(self): + + a = self.get_sample('samples/data/shape_sample/1.png', cv2.IMREAD_GRAYSCALE); + b = self.get_sample('samples/data/shape_sample/2.png', cv2.IMREAD_GRAYSCALE); + + _, ca, _ = cv2.findContours(a, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_TC89_KCOS) + _, cb, _ = cv2.findContours(b, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_TC89_KCOS) + + hd = cv2.createHausdorffDistanceExtractor() + sd = cv2.createShapeContextDistanceExtractor() + + d1 = hd.computeDistance(ca[0], cb[0]) + d2 = sd.computeDistance(ca[0], cb[0]) + + self.assertAlmostEqual(d1, 26.4196891785, 3, "HausdorffDistanceExtractor") + self.assertAlmostEqual(d2, 0.25804194808, 3, "ShapeContextDistanceExtractor") diff --git a/modules/shape/src/haus_dis.cpp b/modules/shape/src/haus_dis.cpp index 6f372c416d..732f288946 100644 --- a/modules/shape/src/haus_dis.cpp +++ b/modules/shape/src/haus_dis.cpp @@ -138,6 +138,13 @@ float HausdorffDistanceExtractorImpl::computeDistance(InputArray contour1, Input set2.convertTo(set2, CV_32F); CV_Assert((set1.channels()==2) && (set1.cols>0)); CV_Assert((set2.channels()==2) && (set2.cols>0)); + + // Force vectors column-based + if (set1.dims > 1) + set1 = set1.reshape(2, 1); + if (set2.dims > 1) + set2 = set2.reshape(2, 1); + return std::max( _apply(set1, set2, distanceFlag, rankProportion), _apply(set2, set1, distanceFlag, rankProportion) ); } diff --git a/modules/shape/src/sc_dis.cpp b/modules/shape/src/sc_dis.cpp index 89c6d91255..bbda11cff0 100644 --- a/modules/shape/src/sc_dis.cpp +++ b/modules/shape/src/sc_dis.cpp @@ -202,6 +202,13 @@ float ShapeContextDistanceExtractorImpl::computeDistance(InputArray contour1, In CV_Assert((set1.channels()==2) && (set1.cols>0)); CV_Assert((set2.channels()==2) && (set2.cols>0)); + + // Force vectors column-based + if (set1.dims > 1) + set1 = set1.reshape(2, 1); + if (set2.dims > 1) + set2 = set2.reshape(2, 1); + if (imageAppearanceWeight!=0) { CV_Assert((!image1.empty()) && (!image2.empty())); diff --git a/modules/shape/test/test_shape.cpp b/modules/shape/test/test_shape.cpp index 0601594f08..97a621e4b5 100644 --- a/modules/shape/test/test_shape.cpp +++ b/modules/shape/test/test_shape.cpp @@ -299,3 +299,22 @@ TEST(Hauss, regression) ShapeBaseTest test(NSN_val, NP_val, CURRENT_MAX_ACCUR_val); test.safe_run(); } + +TEST(computeDistance, regression_4976) +{ + Mat a = imread(cvtest::findDataFile("shape/samples/1.png"), 0); + Mat b = imread(cvtest::findDataFile("shape/samples/2.png"), 0); + + vector > ca,cb; + findContours(a, ca, cv::RETR_CCOMP, cv::CHAIN_APPROX_TC89_KCOS); + findContours(b, cb, cv::RETR_CCOMP, cv::CHAIN_APPROX_TC89_KCOS); + + Ptr hd = createHausdorffDistanceExtractor(); + Ptr sd = createShapeContextDistanceExtractor(); + + double d1 = hd->computeDistance(ca[0],cb[0]); + double d2 = sd->computeDistance(ca[0],cb[0]); + + EXPECT_NEAR(d1, 26.4196891785, 1e-3) << "HausdorffDistanceExtractor"; + EXPECT_NEAR(d2, 0.25804194808, 1e-3) << "ShapeContextDistanceExtractor"; +} From 66cdc100104e52e90a18395f5714db705dd897a8 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Tue, 17 Jan 2017 16:29:50 +0300 Subject: [PATCH 317/409] Minor documentation fixes: - disabled OPTIMIZE_OUTPUT_JAVA - filtered out *.m scripts - removed link to old Sphinx documentation - replaced all mentions of Sphinx with Doxygen --- doc/Doxyfile.in | 4 +- doc/DoxygenLayout.xml | 1 - .../py_setup_in_fedora.markdown | 20 +++------ .../windows_install/images/MiktexInstall.png | Bin 10347 -> 0 bytes .../windows_install/images/Sphinx_Install.png | Bin 21619 -> 0 bytes .../images/WindowsBuildDoc.png | Bin 4627 -> 0 bytes .../images/cmsdstartwindows.jpg | Bin 8795 -> 0 bytes .../windows_install/windows_install.markdown | 42 +++--------------- 8 files changed, 12 insertions(+), 55 deletions(-) delete mode 100644 doc/tutorials/introduction/windows_install/images/MiktexInstall.png delete mode 100644 doc/tutorials/introduction/windows_install/images/Sphinx_Install.png delete mode 100644 doc/tutorials/introduction/windows_install/images/WindowsBuildDoc.png delete mode 100644 doc/tutorials/introduction/windows_install/images/cmsdstartwindows.jpg diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 2e4ac7066a..b1cb9c9744 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -41,7 +41,7 @@ ALIASES += next_tutorial{1}="**Next Tutorial:** \ref \1 \n" ALIASES += youtube{1}="@htmlonly[block]
    @endhtmlonly" TCL_SUBST = OPTIMIZE_OUTPUT_FOR_C = NO -OPTIMIZE_OUTPUT_JAVA = YES +OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO EXTENSION_MAPPING = @@ -106,7 +106,7 @@ FILE_PATTERNS = RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = *.inl.hpp *.impl.hpp *_detail.hpp */cudev/**/detail/*.hpp +EXCLUDE_PATTERNS = *.inl.hpp *.impl.hpp *_detail.hpp */cudev/**/detail/*.hpp *.m EXCLUDE_SYMBOLS = cv::DataType<*> int void EXAMPLE_PATH = @CMAKE_DOXYGEN_EXAMPLE_PATH@ EXAMPLE_PATTERNS = * diff --git a/doc/DoxygenLayout.xml b/doc/DoxygenLayout.xml index 149f36f520..b2675719c9 100644 --- a/doc/DoxygenLayout.xml +++ b/doc/DoxygenLayout.xml @@ -17,7 +17,6 @@ - diff --git a/doc/py_tutorials/py_setup/py_setup_in_fedora/py_setup_in_fedora.markdown b/doc/py_tutorials/py_setup/py_setup_in_fedora/py_setup_in_fedora.markdown index da65dd4772..a4ec9bbd36 100644 --- a/doc/py_tutorials/py_setup/py_setup_in_fedora/py_setup_in_fedora.markdown +++ b/doc/py_tutorials/py_setup/py_setup_in_fedora/py_setup_in_fedora.markdown @@ -102,13 +102,10 @@ yum install eigen3-devel @endcode If you want to build **documentation** ( *Yes, you can create offline version of OpenCV's complete official documentation in your system in HTML with full search facility so that you need not access -internet always if any question, and it is quite FAST!!!* ), you need to install **Sphinx** (a -documentation generation tool) and **pdflatex** (if you want to create a PDF version of it). ( Also -while configuring installation with CMake, don't forget to pass -D BUILD_DOCS=ON. More details -below.) +internet always if any question, and it is quite FAST!!!* ), you need to install **Doxygen** (a +documentation generation tool). @code{.sh} -yum install python-sphinx -yum install texlive +yum install doxygen @endcode ### Downloading OpenCV @@ -174,6 +171,7 @@ setup you got, make sure that following fields are filled (below is the some imp configuration I got). These fields should be filled appropriately in your system also. Otherwise some problem has happened. So check if you have correctly performed above steps. @code{.sh} +... -- GUI: -- GTK+ 2.x: YES (ver 2.24.19) -- GThread : YES (ver 2.36.3) @@ -205,15 +203,7 @@ some problem has happened. So check if you have correctly performed above steps. -- numpy: /usr/lib/python2.7/site-packages/numpy/core/include (ver 1.7.1) -- packages path: lib/python2.7/site-packages --- Documentation: --- Build Documentation: YES --- Sphinx: /usr/bin/sphinx-build (ver 1.1.3) --- PdfLaTeX compiler: /usr/bin/pdflatex --- --- Tests and samples: --- Tests: NO --- Performance tests: NO --- C/C++ Examples: NO +... @endcode Many other flags and settings are there. It is left for you for further exploration. diff --git a/doc/tutorials/introduction/windows_install/images/MiktexInstall.png b/doc/tutorials/introduction/windows_install/images/MiktexInstall.png deleted file mode 100644 index 193a4039918dc47421b0642ed7ae8d3affbbe353..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10347 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�UCdBcpkN>g(cYE3fz9vu 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 JPDHLkV1i;%Hnac$ diff --git a/doc/tutorials/introduction/windows_install/images/cmsdstartwindows.jpg b/doc/tutorials/introduction/windows_install/images/cmsdstartwindows.jpg deleted file mode 100644 index e0d9530d5c23fdf37ab881560c4227b07d220307..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8795 zcmbt&2UwF!)99N(AoK)Ck={E<5d;LKhNeL1RjSgYNIQstbScsiQR%(+4$?uIbPy?0 zM5-W71Vs6R=Y02^@BZc9``q1qvNJogGdpkQ*_oY-nTsU=rixL)03Z+uFvoS^Vh)f8 z@W9~9fou4<0f9mw`1lZFLP7#4DKRN22{8!?89614j2uo*LIOj=;8fHI1cH?uSy!$;R0zL2GWBGZ;lP$GN|o&m|^Ve$?`K z|6P=B(bb`1p9K$N9;h!}s%xU6&9Sw*+^+t%McK*f+YwLi#r>RxW=eVCk@t`7ZP#Bc zUeBGBAgd9pX!x+7?-3AVGV6EOC2Ic5truIwgX$m3iHit{WG1iabSI;B$(cO746aZ@tZsV|&IH}ZSxS=#HzTZTq( zHZ6$-Dcw){Up}gEo!-mk%A_CQDn@A$B0P8$Up!&T)=BaSYTKIRYNX`dUXFC*zb?wF zmz0^p)8F>s#m7IkydN4NPfKZaeKw!{H~lFk|7`B9NxY&fA1TSFk`ip&H%|P;h-HQstH}0+Kv!`Vi6FIRnxLU9a=UsO0T)Pa3zT{mv zeJ5*!!XMD+W%zu?jbJ60C`^!S2LDZ)yZQNk-!FiyDl+5bgJ%;M4orNM-}Ee54ULbE zW+7Zuxk70_YEECecR{4|<~w8zr%{FD*@V=5KnPyYR^$t7`y_j=_*+kx%nop$;E%8N zenxY1sSIPe+;}uz1s6bTm0`U7=(B<6ercITKkr#Wo6n33#$zlCPKspMN=cmSm~}+$ zl?tA3yHl}RW6Q@T?Mcj17YjGn-?Z6BVY0CWllG!C{|@AZKkp*^-1e1Jp2cFk9e*w zB!_Wm)AwUV4mHcP^b+}uK7Y->y9O87=Ve<7b*-u?(&LV(oxjgDSuCWQT}Xq(-_okl zyZ|(xe{}Gqsr%8`ccf<6kjsC1=ZJQLrC-saOkTefL+&Gk{|jKT}R_H7P!>|<3% z3mw9)bdt7!Hsf83Yh13vkuC}$>iO!5J=12EuBMPyu8jve3_kEuH`fodZDF`@qlSi4 zufNGmvaKuMYS@Lt?HbwDf^%_|9WDOSP9fy|z1HGTK+{AQ81bN5R zLbJQ;$rmY2KsTS8sDw!BKP-?^aiO4`m*gBDd=5#han?1euXH9DXxrTRz~M|~BB`HK zPv)?q7VH(j%)s7YkK>M^G-2(20o~{C{C7mnCD6TW^)_Sfcb=f``rmXl9P>QuB_i4> zg+p++5icA7;qD@Qe8Ru(B3vK?;{$L?7&C-QNLEA!8%HfDY>~OV|NA1sMY&s`oKtYL z_KD*gA1?V%2~QLjzpb)7WAlwZH~y-%<`VE$wu^o~b>U~Wnoylv-O;f z2xHkBeNDOs>U0XJB98dcd7sLtxL|CaI9#N0l8X@K)#L ze0!Z>{E(`uro=mw7v7Hh3Z@KyNC@6ZP*fWiUbyB)a{&}`2hbvSoKgZlr`8u6U0%r? zxQ~E;8O(99`p*W&eHA!9jFOp3NLW@-h6S6s_uGE(rTrTEZ`(1f3(hS2&KXX(7b9J> zW0(h4{b0SUbDxgVmp*Ol)t1B#VH>-}f_Pj#!ve#}-t(xK6^#iKndW+>((a%<lqR*5YBpIE2+Tpo*GFP^&{mUfVJ=t8$OcEok|#z(jBhoOSMe#t`|@zQ#`B+N;hF z@T)W1O3(W4>^#m1dUc<|=2Q2+j#<Y!TQC<`-)yGA`JOR1tVE ztL$r_Z^)ZEcdsbAuL@>=yP0j*_W~HaAGw;sd9cQ$jwt&*ap= zdwxz=y!SyQsJ|++^@DBPnERLo6Ntbg71CSz!e#V9yx<&hEW?KN2)h$)O_XR(5Z%TY zdP^-&*whp`p!)=9+$F4;+ zwXMDc$7o7qVXj#T7f;w}Nro>IO?6v6sN#H;9Ollx7CyPgoxBCPV zJj(trM%MZSsqB zj?WC!-~FvAmgmcUgzQ#k9of^23n$CRa|S)pWvB>a?ucM&WsfJ9o=8jU-cp*SIkBj1 zOd9V0aBwFEdF|EOwE6h*d@9L~=0T}2e_G{L=mv(ZkE)Ig$t@C+?W7s;B^Z|$r-+SLTDlST{xs~Igr3Hp&-Jg;a_ZFO zzB1PP!;@n=N71SZLy|Jva{N+5$usykd8~-$8x8ckVy|`Njhg)0vM{`DlHF9}TQ!^~ zTt{9B{XLNAVxI&HS@WkvQ3(cf{+9<{L{Rl$e{udKUv1e*<2kJJDjxo@QhviG;{OMY zt#G})IB(rFXA`mQn8sUL=T%nsVdWByD^cqG7ZNk?91*^f$`FNOr@UYMfl`5QUHPL1 zB9)uAf2BH=^^ugGTaGj5SovzYO@uX62K8pjh>dD7DXYtYH2hKV886px47oPk zLhQkD0|%Qn4FHp6IcaD?(A@Ppenj2egz+=3~UgJI6ZuW8B`RoYOE`WO6`$c;JLz? zZ6D$oXt(B#u=|KO^D3extG;@sTAKh7-b2gUevzH;|NPpGeYSk-SK;*L9a|PBVJcxP zmtdAq;#uk7_#EfWG0E6~hCiMwWnni3%yZv9Kz}9t*jk%V=6nG-VK?AuZ(HG;f-{&H z3W)a`Y|7kHMbEE;?8jQ=jXCHFyvJKy&-!k$*=i?Kt)mDTzc=13*Ds>C+BH~TU7usV z04BeB->Flp6tB^}f2XRXT)#jkLjJaGT)u8?82E#&>~eI?ZMpENeM4b~jn`}6LwcLDy-WlIPmd^!8@M`vzf+}9cJ;?&L>%%m|!2d3?Gd%RX*5Y z*2p2P&Z3f=Qny!`vy#BbUaKO5DnD$3`R(yDOdUz#LvNUIX7v{ggq(BEx8=BhvLIdp zcns<4GIIO4^1WfG@qL;R8;ydp-+qkMtM!3_;oBLnUP7G*uF+u9uaw)avWQh@uC_bO zT9XC;?BjFK?E1ObOmi)mK)2e%UkBRsJOW~9e^2TrP1n zR=i;nUZRR+1(ZZ$I4qb@@t!QNF*Tv%-C#^_$@00XqD4WL9V8}QUjWa84}H@`yRZE) zup?j{5`^1GOp$^lr4Z*sGq@ONO5qed>M;KYnSk&&nU}eJZx9lKVI-A7Qvby*_@B9P zxdAr}0OOKC0$g5z_h&i;f&HaduV(C>xy*!fn;eEwe}$feT&5g<)O}fgLAiH95W~QDF3Ub~ zo%>p2@f(B|AzLU_D{vPBS~C=uWS<+7v3E@)79B>P)R%{f`iB^IylGCj}Pgj1B{R`T=f0` z#aS1rVAtr)6LVsjb!85$FkBdhR7|n3-OmuI;tZZivIEF0%CgR6c(cf}vB^NGWdv8& z<)AbV>!9SyOjV4xmE8_XVw5=er9oQ|W$vz~@l z-G8o}&mIh1m_tH(bUCS$zv#(% zzcwroq-U(`#-{{lJb0eF)K1`;*CC|o^hkzFlDa~3TUy0S6B2v7Nuqncn~h&O64bpz zPOeI$N{_Y7VIAkw>iHiAERh6+lF+B-(ApPWcNl)3T7Kj1!)!>$YdyEJE*31Hn6-`) zCrQ3(p`NrHD7XdX(@Z1Ze(0PEU4}jl8+i?C2w>~zG;g=NUB=|Uq)hlDfC)pIEKUx{ zq;C;Wi@Pwx0usqWI+CGa6?Pz#Jwld=xv-Of$X>f`wJ1A_-gzd>K7=I(CM?*Y!s3r; zk@2>Xo$C*NQ$-v!31#o#(~{1t*bpPlfH`GqX?7wFQ+6{GN*s7Q06|U`Fbv5`)Qr*Z zj1NYb22Y1w0FtC_LYz>e_P5$gJpMGuQizQv8(bBpGyUo#GhUQkPreMRNbr00{DMEC z5&adUNE%Fjm|Ke~YM+4III@C(gB&i?E{}5>8v-q-G^XKwQ7PHf6??B|o_CaGL1G?> zcp-iSjoqBDnBKBlDT8|~{1G|?0jMh~NmiMG#5AD|MQYI%mJ#u}^!NC|oCZ3S^X@@V zkpGGgB^>|NCf9XlYxais-NvLzFwnW;|)SHr<31MPDWIe2QeLmJd#0#gh4RZqd2`>Qs)AJKRFp}T37IDNyQkSgl71w7FElBB+d7-MJEGn-FKuSRH2COZ&nw81C-=O8fL1^ zmUT!-4}Af|qif@?6N}_^RqV0!nLJ^33Y3dk$Z|kDk_%ko8&OH`_QIE&Q&)Lnx$oln zgFGx_!x!71m2SLzC2g!pM?d+e;lOEy{1qORHuZvER{*W>Eo&!N7jkRccj%{A$oeHZ z+Tj2Pt%KJ7ry-2M0{=6psPJb&OeW%-k|8k}@{r*p>OriCaBvWu7?eXGKEs0S#2>EK zqw6L0R7PyPKxq?Fa^JfE1W$z_{hz5$x43d;_*hz*>$wdhkLkpU-x_c&StM-Q@i;+6 z&ocs;l+|Uz5V1gRUG@(AHY~`1FHDk>%2rcb#VS-Gdem?F6HFmb-R;(7yrKeUhOdj- zd~1VZ=!0(hL>NQ$W+r#by`j4%=xaaK9bHPVdCKNTm+Sn!Nx34+>}x)9sN-ZFJxK@vLExzrZ0zs(pyMeB|V_ zmCiu^X3yq{)llfZcw=yDN#-+1yQ&X6#qU=$t}KZwW8(i6Bybh(-(eyEhI=)}31;Gg zgv_6>=F6%9>^@r|`<7iT%Ch#HF*MYma-n0PfbvG#PUzrl@@uR2B&37H-k@@C+3gP2 zscK8AT#BgniZ4t@eXm!-BhSd+?vfs=CG)9nmtND07>t{&o%yPuIfUPhQc(o5wN9K0vjSu0n7!w%@7Q_(L<8pe9bFUYt8kI&v>vMf=mehCNHTJSW@rGJRAvFmmi!9_POm=;K}e5vYvs~|Fcv8 z#sx=$ze@#w#YUJQ&ge|009F#a9tkIxsDQRG}kum%7D zlkUNI*`!z8_j)GL4Ih}Iq6hdq;Ao!N1>JJZN8xx1N706+@go)$8?4EenTW9a!>5OI z57uU-mbPxYV3NZzQLzm9zmo!7fdGsvlU!CwewRr=0Q`4S(6M)!6pX=^nJV`MW&SD) z;B42IVp{ip3ef6ikd`s$fP1{b+&u|7+voGQqAg+$#{|f2Z&XV3_>k3@fYD|6l~k7O zcb;tLPlo=SWofQ^CrcD2W?o8N;*k)L|J5|JDh$8QSf|5K7>*`5rBAVCE|9ZRqXW3E zz8PEKwl83*JC31gG13iAgCdP`_-q3+ZCFakvSN~D`DRG3t5zgRQtQ*+={MpI+h0vqh?p^Ut-%7oAP@mJK8I{4tN%!5$*i_{ey@S zF|{F0kUy`inEtOOUu9`}D4FgqD7b!~-<1_&E)cn`U$tfRz4h>Y%gEhB>BmFgzKZ+_ zy8R749y*Q@5}pyfc>zQXo!6ShTwOA=BCSQ(AJYE@kr|5aetkS-sXxPY=vGUIbkmT53#h454ckNMY@A||S*el# z5^OJOo=A>3$?3`9`N*GooRRe1(BTk?okR6rZ7$THqQsjy|8t`%WPy!Nd;Cdv^08S+ zy}nua*N--BkJ5gwJ^udk>qkpEx5wX;Wq)SKl@+`h?)su}lJJYoY=(UA#~JMBFw>(9 z`=Fdzrl%oY-9x6*c9t#;AFA)cd5_}T{GkmP!Nd(!cXmn2R3i1@d^zn*?$N{zg%-FP zc3X3lA`shg{F^=t-U^+CWO41RSyEr~G<7geoa@#nxq<$=?Q)vEFAR1Ih+lw+-Z@enu~=n!O`-}YC4O$-SZFy<#R7a-w!8iz z@GOzUmfzNccgjw369T6!E~F2s8Tf26)aW+r;eWiRcqb=Zt={nJBhmP=Tr^XF`;s2oD!hTk- zz+T6*=g_jr{nMJVbSL1c`U3A>oML2vZyN_-_wH2+oiz*&SCglqH|GfVjY^czqvKs8 zjbVWK@6Apo#n-*~5*1D5mrRCTkj|=Ay!{-{S*)nhr1ap6^Xr=r))~LDY&_}UZ-HyP zjWZ#^*E~q}z5otZ6v8Rll;c?bHATW{z=;{AJ8sV%O?N#jq=gbJzaS!z8Q&bv({K8m z9{bDM9%}?3b??1vTyEfJwy=Ab1na3 z*|vTbFkDMjq2U&krWUA4Z>bjZtveL4BPdM0Yp0=3Sl01Qfkdmi3~l#Hjy7qZ;$)h& zKtbZ#-Uz-8o&2qjy@(5d$5s&E}}x02wIKXJXHl?$zf z-|a{!t&m!&p;dd5cHq|A1sOISt`u`EPv6jz!UY&Xa<37ul|I(Sq(jryHOXHxv4Ee6 zi`P>z_qq(dpjJ9}P65z5WoN1QOf;^1r;wZNKa@(fPf>ikCr-E1pF=Lz5)sQT2emb=r@K!Zn&Zw&u zZKyM9%it##KSVx`i?)5|o?{E%^t_Dv0NZU-A&;7*h@1X~_|0PHCKo_NP*Yh0Zrqf(?MZkqU^$;nW+5< zAb7Fb*yyEJQC;y(7({63fl*$ZkrI!$7ldhbY9CkDcCQLB1Qa;%<+|&%-z;MC`^eg_f#P(_;#C^S zB%u0am1(v^BBE3d7gcNWV`B1*NVh#?Ob!BB!;251$@za+DJyZeR?hzPllE8NaeX)M z7@~8=TWO)DQS-1>u@VN Date: Wed, 18 Jan 2017 19:30:45 +0200 Subject: [PATCH 318/409] Fixed bug #7482. Updated dense flow routine to reference bound textures. --- modules/cudaoptflow/src/cuda/pyrlk.cu | 19 +++++++++++-------- modules/cudaoptflow/src/pyrlk.cpp | 6 ++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/modules/cudaoptflow/src/cuda/pyrlk.cu b/modules/cudaoptflow/src/cuda/pyrlk.cu index 2f2865057f..e3cca57f3a 100644 --- a/modules/cudaoptflow/src/cuda/pyrlk.cu +++ b/modules/cudaoptflow/src/cuda/pyrlk.cu @@ -51,6 +51,8 @@ #include "opencv2/core/cuda/filters.hpp" #include "opencv2/core/cuda/border_interpolate.hpp" +#include + using namespace cv::cuda; using namespace cv::cuda::device; @@ -923,15 +925,15 @@ namespace pyrlk float x = xBase - c_halfWin_x + j + 0.5f; float y = yBase - c_halfWin_y + i + 0.5f; - I_patch[i * patchWidth + j] = tex2D(tex_Ib, x, y); + I_patch[i * patchWidth + j] = tex2D(tex_If, x, y); // Sharr Deriv - dIdx_patch[i * patchWidth + j] = 3 * tex2D(tex_Ib, x+1, y-1) + 10 * tex2D(tex_Ib, x+1, y) + 3 * tex2D(tex_Ib, x+1, y+1) - - (3 * tex2D(tex_Ib, x-1, y-1) + 10 * tex2D(tex_Ib, x-1, y) + 3 * tex2D(tex_Ib, x-1, y+1)); + dIdx_patch[i * patchWidth + j] = 3 * tex2D(tex_If, x+1, y-1) + 10 * tex2D(tex_If, x+1, y) + 3 * tex2D(tex_If, x+1, y+1) - + (3 * tex2D(tex_If, x-1, y-1) + 10 * tex2D(tex_If, x-1, y) + 3 * tex2D(tex_If, x-1, y+1)); - dIdy_patch[i * patchWidth + j] = 3 * tex2D(tex_Ib, x-1, y+1) + 10 * tex2D(tex_Ib, x, y+1) + 3 * tex2D(tex_Ib, x+1, y+1) - - (3 * tex2D(tex_Ib, x-1, y-1) + 10 * tex2D(tex_Ib, x, y-1) + 3 * tex2D(tex_Ib, x+1, y-1)); + dIdy_patch[i * patchWidth + j] = 3 * tex2D(tex_If, x-1, y+1) + 10 * tex2D(tex_If, x, y+1) + 3 * tex2D(tex_If, x+1, y+1) - + (3 * tex2D(tex_If, x-1, y-1) + 10 * tex2D(tex_If, x, y-1) + 3 * tex2D(tex_If, x+1, y-1)); } } @@ -943,6 +945,7 @@ namespace pyrlk if (x >= cols || y >= rows) return; + int A11i = 0; int A12i = 0; int A22i = 0; @@ -970,7 +973,6 @@ namespace pyrlk { if (calcErr) err(y, x) = numeric_limits::max(); - return; } @@ -1014,6 +1016,7 @@ namespace pyrlk } } + float2 delta; delta.x = A12 * b2 - A22 * b1; delta.y = A12 * b1 - A11 * b2; @@ -1083,11 +1086,11 @@ namespace pyrlk funcs[patch.y - 1][patch.x - 1](I, J, I.rows, I.cols, prevPts, nextPts, status, err, ptcount, level, block, stream); } - static void dense(PtrStepSzb I, PtrStepSz J, PtrStepSzf u, PtrStepSzf v, PtrStepSzf prevU, PtrStepSzf prevV, PtrStepSzf err, int2 winSize, cudaStream_t stream) + static void dense(PtrStepSz I, PtrStepSz J, PtrStepSzf u, PtrStepSzf v, PtrStepSzf prevU, PtrStepSzf prevV, PtrStepSzf err, int2 winSize, cudaStream_t stream) { dim3 block(16, 16); dim3 grid(divUp(I.cols, block.x), divUp(I.rows, block.y)); - Tex_I<1, uchar>::bindTexture_(I); + Tex_I<1, T>::bindTexture_(I); Tex_J<1, T>::bindTexture_(J); int2 halfWin = make_int2((winSize.x - 1) / 2, (winSize.y - 1) / 2); diff --git a/modules/cudaoptflow/src/pyrlk.cpp b/modules/cudaoptflow/src/pyrlk.cpp index c7f706087b..d1704473c7 100644 --- a/modules/cudaoptflow/src/pyrlk.cpp +++ b/modules/cudaoptflow/src/pyrlk.cpp @@ -61,7 +61,7 @@ namespace pyrlk static void sparse(PtrStepSz::vec_type> I, PtrStepSz::vec_type> J, const float2* prevPts, float2* nextPts, uchar* status, float* err, int ptcount, int level, dim3 block, dim3 patch, cudaStream_t stream); - static void dense(PtrStepSzb I, PtrStepSzf J, PtrStepSzf u, PtrStepSzf v, PtrStepSzf prevU, PtrStepSzf prevV, + static void dense(PtrStepSzf I, PtrStepSzf J, PtrStepSzf u, PtrStepSzf v, PtrStepSzf prevU, PtrStepSzf prevV, PtrStepSzf err, int2 winSize, cudaStream_t stream); }; @@ -236,7 +236,9 @@ namespace prevPyr_.resize(maxLevel_ + 1); nextPyr_.resize(maxLevel_ + 1); - prevPyr_[0] = prevImg; + //prevPyr_[0] = prevImg; + + prevImg.convertTo(prevPyr_[0], CV_32F, stream); nextImg.convertTo(nextPyr_[0], CV_32F, stream); for (int level = 1; level <= maxLevel_; ++level) From f9828cd0c9ae019a57dafc3acb89d80a52025e52 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 19 Jan 2017 13:33:39 +0300 Subject: [PATCH 319/409] cmake: fix MKL detection in case of unsupported versions CMake should not fail. --- cmake/OpenCVFindLAPACK.cmake | 3 ++- cmake/OpenCVFindMKL.cmake | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cmake/OpenCVFindLAPACK.cmake b/cmake/OpenCVFindLAPACK.cmake index 0bee1b4c90..09cd66cdae 100644 --- a/cmake/OpenCVFindLAPACK.cmake +++ b/cmake/OpenCVFindLAPACK.cmake @@ -133,6 +133,7 @@ if(WITH_LAPACK) set(LAPACK_IMPL "LAPACK/Apple") ocv_lapack_check() else() + unset(LAPACK_LIBRARIES) unset(LAPACK_LIBRARIES CACHE) endif() endif() @@ -147,7 +148,7 @@ if(WITH_LAPACK) ocv_lapack_check() endif() - if(NOT HAVE_LAPACK AND LAPACK_LIBRARIES) + if(NOT HAVE_LAPACK AND LAPACK_LIBRARIES AND LAPACK_CBLAS_H AND LAPACK_LAPACKE_H) ocv_lapack_check() endif() diff --git a/cmake/OpenCVFindMKL.cmake b/cmake/OpenCVFindMKL.cmake index 337456843b..0f5462acd2 100644 --- a/cmake/OpenCVFindMKL.cmake +++ b/cmake/OpenCVFindMKL.cmake @@ -20,10 +20,8 @@ macro (mkl_find_lib VAR NAME DIRS) endmacro() macro(mkl_fail) - set(HAVE_MKL OFF CACHE BOOL "True if MKL found") + set(HAVE_MKL OFF) set(MKL_ROOT_DIR ${MKL_ROOT_DIR} CACHE PATH "Path to MKL directory") - unset(MKL_INCLUDE_DIRS CACHE) - unset(MKL_LIBRARIES CACHE) return() endmacro() @@ -64,11 +62,16 @@ if(NOT MKL_ROOT_DIR OR NOT EXISTS ${MKL_ROOT_DIR}/include/mkl.h) find_path(MKL_ROOT_DIR include/mkl.h PATHS ${mkl_root_paths}) endif() -if(NOT MKL_ROOT_DIR) +set(MKL_INCLUDE_DIRS ${MKL_ROOT_DIR}/include CACHE PATH "Path to MKL include directory") + +if(NOT MKL_ROOT_DIR + OR NOT EXISTS "${MKL_ROOT_DIR}" + OR NOT EXISTS "${MKL_INCLUDE_DIRS}" + OR NOT EXISTS "${MKL_INCLUDE_DIRS}/mkl_version.h" +) mkl_fail() endif() -set(MKL_INCLUDE_DIRS ${MKL_ROOT_DIR}/include) get_mkl_version(${MKL_INCLUDE_DIRS}/mkl_version.h) #determine arch @@ -124,13 +127,11 @@ foreach(lib ${mkl_lib_list}) endforeach() message(STATUS "Found MKL ${MKL_VERSION_STR} at: ${MKL_ROOT_DIR}") -set(HAVE_MKL ON CACHE BOOL "True if MKL found") +set(HAVE_MKL ON) set(MKL_ROOT_DIR ${MKL_ROOT_DIR} CACHE PATH "Path to MKL directory") set(MKL_INCLUDE_DIRS ${MKL_INCLUDE_DIRS} CACHE PATH "Path to MKL include directory") -if(NOT UNIX) - set(MKL_LIBRARIES ${MKL_LIBRARIES} CACHE FILEPATH "MKL libarries") -else() +set(MKL_LIBRARIES ${MKL_LIBRARIES} CACHE STRING "MKL libarries") +if(UNIX AND NOT MKL_LIBRARIES_DONT_HACK) #it's ugly but helps to avoid cyclic lib problem set(MKL_LIBRARIES ${MKL_LIBRARIES} ${MKL_LIBRARIES} ${MKL_LIBRARIES} "-lpthread" "-lm" "-ldl") - set(MKL_LIBRARIES ${MKL_LIBRARIES} CACHE STRING "MKL libarries") endif() From e3ec3566d95413374d6afd716fb007b48f51748c Mon Sep 17 00:00:00 2001 From: chrizandr Date: Fri, 13 Jan 2017 01:49:49 +0530 Subject: [PATCH 320/409] RTrees: Add wrapper for RTrees_load method to enable loading trained RTrees --- modules/ml/include/opencv2/ml.hpp | 11 +++++++++++ modules/ml/src/rtrees.cpp | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/modules/ml/include/opencv2/ml.hpp b/modules/ml/include/opencv2/ml.hpp index 99f5883d6e..3614a91298 100644 --- a/modules/ml/include/opencv2/ml.hpp +++ b/modules/ml/include/opencv2/ml.hpp @@ -1169,6 +1169,17 @@ public: Algorithm::load to load the pre-trained model. */ CV_WRAP static Ptr create(); + + /** @brief Loads and creates a serialized RTree from a file + * + * Use RTree::save to serialize and store an RTree to disk. + * Load the RTree from this file again, by calling this function with the path to the file. + * Optionally specify the node for the file containing the classifier + * + * @param filepath path to serialized RTree + * @param nodeName name of node containing the classifier + */ + CV_WRAP static Ptr load(const String& filepath , const String& nodeName = String()); }; /****************************************************************************************\ diff --git a/modules/ml/src/rtrees.cpp b/modules/ml/src/rtrees.cpp index cab33ab33b..65fe6827a7 100644 --- a/modules/ml/src/rtrees.cpp +++ b/modules/ml/src/rtrees.cpp @@ -41,7 +41,6 @@ //M*/ #include "precomp.hpp" - namespace cv { namespace ml { @@ -422,6 +421,12 @@ Ptr RTrees::create() return makePtr(); } +//Function needed for Python and Java wrappers +Ptr RTrees::load(const String& filepath, const String& nodeName) +{ + return Algorithm::load(filepath, nodeName); +} + }} // End of file. From 062d2179eb8e755b58e07cd761fdfe3faf64d521 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Fri, 20 Jan 2017 19:22:44 +0900 Subject: [PATCH 321/409] use universal intrinsic in corner detection series --- modules/imgproc/src/corner.cpp | 181 ++++++++++----------------------- 1 file changed, 52 insertions(+), 129 deletions(-) diff --git a/modules/imgproc/src/corner.cpp b/modules/imgproc/src/corner.cpp index 7f3bad5012..dc84897be4 100644 --- a/modules/imgproc/src/corner.cpp +++ b/modules/imgproc/src/corner.cpp @@ -43,6 +43,7 @@ #include "precomp.hpp" #include "opencl_kernels_imgproc.hpp" +#include "opencv2/core/hal/intrin.hpp" namespace cv { @@ -51,8 +52,8 @@ static void calcMinEigenVal( const Mat& _cov, Mat& _dst ) { int i, j; Size size = _cov.size(); -#if CV_SSE - volatile bool simd = checkHardwareSupport(CV_CPU_SSE); +#if CV_SIMD128 + bool simd = hasSIMD128(); #endif if( _cov.isContinuous() && _dst.isContinuous() ) @@ -66,44 +67,21 @@ static void calcMinEigenVal( const Mat& _cov, Mat& _dst ) const float* cov = _cov.ptr(i); float* dst = _dst.ptr(i); j = 0; - #if CV_SSE + #if CV_SIMD128 if( simd ) { - __m128 half = _mm_set1_ps(0.5f); - for( ; j <= size.width - 4; j += 4 ) + v_float32x4 half = v_setall_f32(0.5f); + for( ; j <= size.width - v_float32x4::nlanes; j += v_float32x4::nlanes ) { - __m128 t0 = _mm_loadu_ps(cov + j*3); // a0 b0 c0 x - __m128 t1 = _mm_loadu_ps(cov + j*3 + 3); // a1 b1 c1 x - __m128 t2 = _mm_loadu_ps(cov + j*3 + 6); // a2 b2 c2 x - __m128 t3 = _mm_loadu_ps(cov + j*3 + 9); // a3 b3 c3 x - __m128 a, b, c, t; - t = _mm_unpacklo_ps(t0, t1); // a0 a1 b0 b1 - c = _mm_unpackhi_ps(t0, t1); // c0 c1 x x - b = _mm_unpacklo_ps(t2, t3); // a2 a3 b2 b3 - c = _mm_movelh_ps(c, _mm_unpackhi_ps(t2, t3)); // c0 c1 c2 c3 - a = _mm_movelh_ps(t, b); - b = _mm_movehl_ps(b, t); - a = _mm_mul_ps(a, half); - c = _mm_mul_ps(c, half); - t = _mm_sub_ps(a, c); - t = _mm_add_ps(_mm_mul_ps(t, t), _mm_mul_ps(b,b)); - a = _mm_sub_ps(_mm_add_ps(a, c), _mm_sqrt_ps(t)); - _mm_storeu_ps(dst + j, a); + v_float32x4 v_a, v_b, v_c, v_t; + v_load_deinterleave(cov + j*3, v_a, v_b, v_c); + v_a *= half; + v_c *= half; + v_t = v_a - v_c; + v_t = v_muladd(v_b, v_b, (v_t * v_t)); + v_store(dst + j, (v_a + v_c) - v_sqrt(v_t)); } } - #elif CV_NEON - float32x4_t v_half = vdupq_n_f32(0.5f); - for( ; j <= size.width - 4; j += 4 ) - { - float32x4x3_t v_src = vld3q_f32(cov + j * 3); - float32x4_t v_a = vmulq_f32(v_src.val[0], v_half); - float32x4_t v_b = v_src.val[1]; - float32x4_t v_c = vmulq_f32(v_src.val[2], v_half); - - float32x4_t v_t = vsubq_f32(v_a, v_c); - v_t = vmlaq_f32(vmulq_f32(v_t, v_t), v_b, v_b); - vst1q_f32(dst + j, vsubq_f32(vaddq_f32(v_a, v_c), cv_vsqrtq_f32(v_t))); - } #endif for( ; j < size.width; j++ ) { @@ -120,8 +98,8 @@ static void calcHarris( const Mat& _cov, Mat& _dst, double k ) { int i, j; Size size = _cov.size(); -#if CV_SSE - volatile bool simd = checkHardwareSupport(CV_CPU_SSE); +#if CV_SIMD128 + bool simd = hasSIMD128(); #endif if( _cov.isContinuous() && _dst.isContinuous() ) @@ -136,40 +114,21 @@ static void calcHarris( const Mat& _cov, Mat& _dst, double k ) float* dst = _dst.ptr(i); j = 0; - #if CV_SSE + #if CV_SIMD128 if( simd ) { - __m128 k4 = _mm_set1_ps((float)k); - for( ; j <= size.width - 4; j += 4 ) - { - __m128 t0 = _mm_loadu_ps(cov + j*3); // a0 b0 c0 x - __m128 t1 = _mm_loadu_ps(cov + j*3 + 3); // a1 b1 c1 x - __m128 t2 = _mm_loadu_ps(cov + j*3 + 6); // a2 b2 c2 x - __m128 t3 = _mm_loadu_ps(cov + j*3 + 9); // a3 b3 c3 x - __m128 a, b, c, t; - t = _mm_unpacklo_ps(t0, t1); // a0 a1 b0 b1 - c = _mm_unpackhi_ps(t0, t1); // c0 c1 x x - b = _mm_unpacklo_ps(t2, t3); // a2 a3 b2 b3 - c = _mm_movelh_ps(c, _mm_unpackhi_ps(t2, t3)); // c0 c1 c2 c3 - a = _mm_movelh_ps(t, b); - b = _mm_movehl_ps(b, t); - t = _mm_add_ps(a, c); - a = _mm_sub_ps(_mm_mul_ps(a, c), _mm_mul_ps(b, b)); - t = _mm_mul_ps(_mm_mul_ps(k4, t), t); - a = _mm_sub_ps(a, t); - _mm_storeu_ps(dst + j, a); - } - } - #elif CV_NEON - float32x4_t v_k = vdupq_n_f32((float)k); + v_float32x4 v_k = v_setall_f32((float)k); - for( ; j <= size.width - 4; j += 4 ) - { - float32x4x3_t v_src = vld3q_f32(cov + j * 3); - float32x4_t v_a = v_src.val[0], v_b = v_src.val[1], v_c = v_src.val[2]; - float32x4_t v_ac_bb = vmlsq_f32(vmulq_f32(v_a, v_c), v_b, v_b); - float32x4_t v_ac = vaddq_f32(v_a, v_c); - vst1q_f32(dst + j, vmlsq_f32(v_ac_bb, v_k, vmulq_f32(v_ac, v_ac))); + for( ; j <= size.width - v_float32x4::nlanes; j += v_float32x4::nlanes ) + { + v_float32x4 v_a, v_b, v_c; + v_load_deinterleave(cov + j * 3, v_a, v_b, v_c); + + v_float32x4 v_ac_bb = v_a * v_c - v_b * v_b; + v_float32x4 v_ac = v_a + v_c; + v_float32x4 v_dst = v_ac_bb - v_k * v_ac * v_ac; + v_store(dst + j, v_dst); + } } #endif @@ -272,8 +231,8 @@ cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size, if (tegra::useTegra() && tegra::cornerEigenValsVecs(src, eigenv, block_size, aperture_size, op_type, k, borderType)) return; #endif -#if CV_SSE2 - bool haveSSE2 = checkHardwareSupport(CV_CPU_SSE2); +#if CV_SIMD128 + bool haveSimd = hasSIMD128(); #endif int depth = src.depth(); @@ -309,44 +268,20 @@ cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size, const float* dydata = Dy.ptr(i); j = 0; - #if CV_NEON - for( ; j <= size.width - 4; j += 4 ) + #if CV_SIMD128 + if (haveSimd) { - float32x4_t v_dx = vld1q_f32(dxdata + j); - float32x4_t v_dy = vld1q_f32(dydata + j); - - float32x4x3_t v_dst; - v_dst.val[0] = vmulq_f32(v_dx, v_dx); - v_dst.val[1] = vmulq_f32(v_dx, v_dy); - v_dst.val[2] = vmulq_f32(v_dy, v_dy); - - vst3q_f32(cov_data + j * 3, v_dst); - } - #elif CV_SSE2 - if (haveSSE2) - { - for( ; j <= size.width - 8; j += 8 ) + for( ; j <= size.width - v_float32x4::nlanes; j += v_float32x4::nlanes ) { - __m128 v_dx_0 = _mm_loadu_ps(dxdata + j); - __m128 v_dx_1 = _mm_loadu_ps(dxdata + j + 4); - __m128 v_dy_0 = _mm_loadu_ps(dydata + j); - __m128 v_dy_1 = _mm_loadu_ps(dydata + j + 4); + v_float32x4 v_dx = v_load(dxdata + j); + v_float32x4 v_dy = v_load(dydata + j); - __m128 v_dx2_0 = _mm_mul_ps(v_dx_0, v_dx_0); - __m128 v_dxy_0 = _mm_mul_ps(v_dx_0, v_dy_0); - __m128 v_dy2_0 = _mm_mul_ps(v_dy_0, v_dy_0); - __m128 v_dx2_1 = _mm_mul_ps(v_dx_1, v_dx_1); - __m128 v_dxy_1 = _mm_mul_ps(v_dx_1, v_dy_1); - __m128 v_dy2_1 = _mm_mul_ps(v_dy_1, v_dy_1); + v_float32x4 v_dst0, v_dst1, v_dst2; + v_dst0 = v_dx * v_dx; + v_dst1 = v_dx * v_dy; + v_dst2 = v_dy * v_dy; - _mm_interleave_ps(v_dx2_0, v_dx2_1, v_dxy_0, v_dxy_1, v_dy2_0, v_dy2_1); - - _mm_storeu_ps(cov_data + j * 3, v_dx2_0); - _mm_storeu_ps(cov_data + j * 3 + 4, v_dx2_1); - _mm_storeu_ps(cov_data + j * 3 + 8, v_dxy_0); - _mm_storeu_ps(cov_data + j * 3 + 12, v_dxy_1); - _mm_storeu_ps(cov_data + j * 3 + 16, v_dy2_0); - _mm_storeu_ps(cov_data + j * 3 + 20, v_dy2_1); + v_store_interleave(cov_data + j * 3, v_dst0, v_dst1, v_dst2); } } #endif @@ -751,13 +686,10 @@ void cv::preCornerDetect( InputArray _src, OutputArray _dst, int ksize, int bord if( src.depth() == CV_8U ) factor *= 255; factor = 1./(factor * factor * factor); -#if CV_NEON || CV_SSE2 +#if CV_SIMD128 float factor_f = (float)factor; -#endif - -#if CV_SSE2 - volatile bool haveSSE2 = cv::checkHardwareSupport(CV_CPU_SSE2); - __m128 v_factor = _mm_set1_ps(factor_f), v_m2 = _mm_set1_ps(-2.0f); + bool haveSimd = hasSIMD128(); + v_float32x4 v_factor = v_setall_f32(factor_f), v_m2 = v_setall_f32(-2.0f); #endif Size size = src.size(); @@ -773,30 +705,21 @@ void cv::preCornerDetect( InputArray _src, OutputArray _dst, int ksize, int bord j = 0; -#if CV_SSE2 - if (haveSSE2) +#if CV_SIMD128 + if (haveSimd) { - for( ; j <= size.width - 4; j += 4 ) + for( ; j <= size.width - v_float32x4::nlanes; j += v_float32x4::nlanes ) { - __m128 v_dx = _mm_loadu_ps((const float *)(dxdata + j)); - __m128 v_dy = _mm_loadu_ps((const float *)(dydata + j)); + v_float32x4 v_dx = v_load(dxdata + j); + v_float32x4 v_dy = v_load(dydata + j); - __m128 v_s1 = _mm_mul_ps(_mm_mul_ps(v_dx, v_dx), _mm_loadu_ps((const float *)(d2ydata + j))); - __m128 v_s2 = _mm_mul_ps(_mm_mul_ps(v_dy, v_dy), _mm_loadu_ps((const float *)(d2xdata + j))); - __m128 v_s3 = _mm_mul_ps(_mm_mul_ps(v_dx, v_dy), _mm_loadu_ps((const float *)(dxydata + j))); - v_s1 = _mm_mul_ps(v_factor, _mm_add_ps(v_s1, _mm_add_ps(v_s2, _mm_mul_ps(v_s3, v_m2)))); - _mm_storeu_ps(dstdata + j, v_s1); + v_float32x4 v_s1 = (v_dx * v_dx) * v_load(d2ydata + j); + v_float32x4 v_s2 = v_muladd((v_dy * v_dy), v_load(d2xdata + j), v_s1); + v_float32x4 v_s3 = v_muladd((v_dy * v_dx) * v_load(dxydata + j), v_m2, v_s2); + + v_store(dstdata + j, v_s3 * v_factor); } } -#elif CV_NEON - for( ; j <= size.width - 4; j += 4 ) - { - float32x4_t v_dx = vld1q_f32(dxdata + j), v_dy = vld1q_f32(dydata + j); - float32x4_t v_s = vmulq_f32(v_dx, vmulq_f32(v_dx, vld1q_f32(d2ydata + j))); - v_s = vmlaq_f32(v_s, vld1q_f32(d2xdata + j), vmulq_f32(v_dy, v_dy)); - v_s = vmlaq_f32(v_s, vld1q_f32(dxydata + j), vmulq_n_f32(vmulq_f32(v_dy, v_dx), -2)); - vst1q_f32(dstdata + j, vmulq_n_f32(v_s, factor_f)); - } #endif for( ; j < size.width; j++ ) From e71ff78f7f1367cb754f884946c0c9e807cb21cf Mon Sep 17 00:00:00 2001 From: Dal Rupnik Date: Fri, 20 Jan 2017 17:16:01 +0100 Subject: [PATCH 322/409] Merge pull request #8009 from Legoless:master CMake: Building Dynamic Framework on iOS (#8009) * Updated python script with dynamic parameter Updated python script to build static library by default Updated python script to include bitcode flag Added bitcode flag to c flags Fixed directories and targets with static Bitcode parameter fixed Fixed script for static library Fixed parameters in build function Updated cmake common toolchain Added changes to OpenCV Utils Updates to cmake Added cache internal Updates to common toolchain Fixed path in framework destination and added UIKit dependency Dynamic plist for framework Lib version removed hardcoded value Removed trailing whitespace in toolchain * Removed trailing whitespace * Fixed typo in comment * Renamed bitcode variable to bitcodedisabled * Fixed target device family --- cmake/OpenCVGenInfoPlist.cmake | 9 +- cmake/OpenCVUtils.cmake | 29 ++- modules/videoio/CMakeLists.txt | 4 + modules/world/CMakeLists.txt | 2 +- platforms/ios/Info.Dynamic.plist.in | 28 +++ platforms/ios/build_framework.py | 165 +++++++++++++----- .../ios/cmake/Modules/Platform/iOS.cmake | 9 +- .../Toolchains/common-ios-toolchain.cmake | 53 ++++-- 8 files changed, 230 insertions(+), 69 deletions(-) create mode 100644 platforms/ios/Info.Dynamic.plist.in diff --git a/cmake/OpenCVGenInfoPlist.cmake b/cmake/OpenCVGenInfoPlist.cmake index 2b78ae1e53..6dbdc5b9e7 100644 --- a/cmake/OpenCVGenInfoPlist.cmake +++ b/cmake/OpenCVGenInfoPlist.cmake @@ -2,8 +2,13 @@ set(OPENCV_APPLE_BUNDLE_NAME "OpenCV") set(OPENCV_APPLE_BUNDLE_ID "org.opencv") if(IOS) - configure_file("${OpenCV_SOURCE_DIR}/platforms/ios/Info.plist.in" - "${CMAKE_BINARY_DIR}/ios/Info.plist") + if (APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + configure_file("${OpenCV_SOURCE_DIR}/platforms/ios/Info.Dynamic.plist.in" + "${CMAKE_BINARY_DIR}/ios/Info.plist") + else() + configure_file("${OpenCV_SOURCE_DIR}/platforms/ios/Info.plist.in" + "${CMAKE_BINARY_DIR}/ios/Info.plist") + endif() elseif(APPLE) configure_file("${OpenCV_SOURCE_DIR}/platforms/osx/Info.plist.in" "${CMAKE_BINARY_DIR}/osx/Info.plist") diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index cdf257d5fe..7a0e448f77 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -668,7 +668,11 @@ endfunction() # add install command function(ocv_install_target) - install(TARGETS ${ARGN}) + if(APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + install(TARGETS ${ARGN} FRAMEWORK DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH}) + else() + install(TARGETS ${ARGN}) + endif() set(isPackage 0) unset(__package) @@ -931,6 +935,29 @@ function(ocv_add_library target) unset(sources) endif() + if(APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + message(STATUS "Setting Apple target properties for ${target}") + + set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG 1) + + set_target_properties(${target} PROPERTIES + FRAMEWORK TRUE + MACOSX_FRAMEWORK_IDENTIFIER org.opencv + MACOSX_FRAMEWORK_INFO_PLIST ${CMAKE_BINARY_DIR}/ios/Info.plist + # "current version" in semantic format in Mach-O binary file + VERSION ${OPENCV_LIBVERSION} + # "compatibility version" in semantic format in Mach-O binary file + SOVERSION ${OPENCV_LIBVERSION} + INSTALL_RPATH "" + INSTALL_NAME_DIR "@rpath" + BUILD_WITH_INSTALL_RPATH 1 + LIBRARY_OUTPUT_NAME "opencv2" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" + #PUBLIC_HEADER "${OPENCV_CONFIG_FILE_INCLUDE_DIR}/cvconfig.h" + #XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer" + ) + endif() + _ocv_append_target_includes(${target}) endfunction() diff --git a/modules/videoio/CMakeLists.txt b/modules/videoio/CMakeLists.txt index fb64894899..8a5b9d7a36 100644 --- a/modules/videoio/CMakeLists.txt +++ b/modules/videoio/CMakeLists.txt @@ -208,7 +208,11 @@ if(IOS) ${CMAKE_CURRENT_LIST_DIR}/src/cap_ios_abstract_camera.mm ${CMAKE_CURRENT_LIST_DIR}/src/cap_ios_photo_camera.mm ${CMAKE_CURRENT_LIST_DIR}/src/cap_ios_video_camera.mm) + list(APPEND VIDEOIO_LIBRARIES "-framework Accelerate" "-framework AVFoundation" "-framework CoreGraphics" "-framework CoreImage" "-framework CoreMedia" "-framework CoreVideo" "-framework QuartzCore" "-framework AssetsLibrary") + if(APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + list(APPEND VIDEOIO_LIBRARIES "-framework UIKit") + endif() endif() if(WIN32) diff --git a/modules/world/CMakeLists.txt b/modules/world/CMakeLists.txt index db8928d69c..1a77245df2 100644 --- a/modules/world/CMakeLists.txt +++ b/modules/world/CMakeLists.txt @@ -2,7 +2,7 @@ set(the_description "All the selected OpenCV modules in a single binary") set(OPENCV_MODULE_IS_PART_OF_WORLD FALSE) set(BUILD_opencv_world_INIT OFF) -if(APPLE_FRAMEWORK OR NOT BUILD_SHARED_LIBS) +if(NOT BUILD_SHARED_LIBS) set(OPENCV_MODULE_TYPE STATIC) set(OPENCV_WORLD_FLAGS_PROPERTY STATIC_LIBRARY_FLAGS) else() diff --git a/platforms/ios/Info.Dynamic.plist.in b/platforms/ios/Info.Dynamic.plist.in new file mode 100644 index 0000000000..4ff68cca04 --- /dev/null +++ b/platforms/ios/Info.Dynamic.plist.in @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + opencv2 + CFBundleName + ${OPENCV_APPLE_BUNDLE_NAME} + CFBundleIdentifier + ${OPENCV_APPLE_BUNDLE_ID} + CFBundleVersion + ${OPENCV_LIBVERSION} + CFBundleShortVersionString + ${OPENCV_LIBVERSION} + CFBundleSignature + ???? + CFBundlePackageType + FMWK + CFBundleSupportedPlatforms + + iPhoneOS + + MinimumOSVersion + 8.0 + + \ No newline at end of file diff --git a/platforms/ios/build_framework.py b/platforms/ios/build_framework.py index f8886b6f93..2770a5a102 100644 --- a/platforms/ios/build_framework.py +++ b/platforms/ios/build_framework.py @@ -23,6 +23,8 @@ Script will create , if it's missing, and a few its subdirectories: The script should handle minor OpenCV updates efficiently - it does not recompile the library from scratch each time. However, opencv2.framework directory is erased and recreated on each run. + +Adding --dynamic parameter will build opencv2.framework as App Store dynamic framework. Only iOS 8+ versions are supported. """ from __future__ import print_function @@ -43,7 +45,7 @@ def getXCodeMajor(): return 0 class Builder: - def __init__(self, opencv, contrib, exclude, targets): + def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, targets): self.opencv = os.path.abspath(opencv) self.contrib = None if contrib: @@ -52,11 +54,18 @@ class Builder: self.contrib = os.path.abspath(modpath) else: print("Note: contrib repository is bad - modules subfolder not found", file=sys.stderr) + self.dynamic = dynamic + self.bitcodedisabled = bitcodedisabled self.exclude = exclude self.targets = targets def getBD(self, parent, t): - res = os.path.join(parent, '%s-%s' % t) + + if len(t[0]) == 1: + res = os.path.join(parent, 'build-%s-%s' % (t[0][0].lower(), t[1].lower())) + else: + res = os.path.join(parent, 'build-%s' % t[1].lower()) + if not os.path.isdir(res): os.makedirs(res) return os.path.abspath(res) @@ -70,17 +79,32 @@ class Builder: xcode_ver = getXCodeMajor() - for t in self.targets: + if self.dynamic: + alltargets = self.targets + else: + # if we are building a static library, we must build each architecture separately + alltargets = [] + + for t in self.targets: + for at in t[0]: + current = ( [at], t[1] ) + + alltargets.append(current) + + for t in alltargets: mainBD = self.getBD(mainWD, t) dirs.append(mainBD) + cmake_flags = [] if self.contrib: cmake_flags.append("-DOPENCV_EXTRA_MODULES_PATH=%s" % self.contrib) - if xcode_ver >= 7 and t[1] == 'iPhoneOS': + if xcode_ver >= 7 and t[1] == 'iPhoneOS' and self.bitcodedisabled == False: cmake_flags.append("-DCMAKE_C_FLAGS=-fembed-bitcode") cmake_flags.append("-DCMAKE_CXX_FLAGS=-fembed-bitcode") self.buildOne(t[0], t[1], mainBD, cmake_flags) - self.mergeLibs(mainBD) + + if self.dynamic == False: + self.mergeLibs(mainBD) self.makeFramework(outdir, dirs) def build(self, outdir): @@ -97,13 +121,26 @@ class Builder: return None def getCMakeArgs(self, arch, target): - args = [ - "cmake", - "-GXcode", - "-DAPPLE_FRAMEWORK=ON", - "-DCMAKE_INSTALL_PREFIX=install", - "-DCMAKE_BUILD_TYPE=Release", - ] + + if self.dynamic: + args = [ + "cmake", + "-GXcode", + "-DAPPLE_FRAMEWORK=ON", + "-DCMAKE_INSTALL_PREFIX=install", + "-DCMAKE_BUILD_TYPE=Release", + "-DBUILD_SHARED_LIBS=ON", + "-DCMAKE_MACOSX_BUNDLE=ON", + "-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO", + ] + else: + args = [ + "cmake", + "-GXcode", + "-DAPPLE_FRAMEWORK=ON", + "-DCMAKE_INSTALL_PREFIX=install", + "-DCMAKE_BUILD_TYPE=Release", + ] if len(self.exclude) > 0: args += ["-DBUILD_opencv_world=OFF"] @@ -111,16 +148,38 @@ class Builder: return args - def getBuildCommand(self, arch, target): - buildcmd = [ - "xcodebuild", - "IPHONEOS_DEPLOYMENT_TARGET=6.0", - "ARCHS=%s" % arch, - "-sdk", target.lower(), - "-configuration", "Release", - "-parallelizeTargets", - "-jobs", "4" - ] + def getBuildCommand(self, archs, target): + + if self.dynamic: + buildcmd = [ + "xcodebuild", + "IPHONEOS_DEPLOYMENT_TARGET=8.0", + "ONLY_ACTIVE_ARCH=NO", + ] + + for arch in archs: + buildcmd.append("-arch") + buildcmd.append(arch.lower()) + + buildcmd += [ + "-sdk", target.lower(), + "-configuration", "Release", + "-parallelizeTargets", + "-jobs", "4", + "-target","ALL_BUILD", + ] + else: + arch = ";".join(archs) + buildcmd = [ + "xcodebuild", + "IPHONEOS_DEPLOYMENT_TARGET=6.0", + "ARCHS=%s" % arch, + "-sdk", target.lower(), + "-configuration", "Release", + "-parallelizeTargets", + "-jobs", "4" + ] + return buildcmd def getInfoPlist(self, builddirs): @@ -131,11 +190,12 @@ class Builder: toolchain = self.getToolchain(arch, target) cmakecmd = self.getCMakeArgs(arch, target) + \ (["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else []) - if arch.startswith("armv") or arch.startswith("arm64"): + if target.lower().startswith("iphoneos"): cmakecmd.append("-DENABLE_NEON=ON") cmakecmd.append(self.opencv) cmakecmd.extend(cmakeargs) execute(cmakecmd, cwd = builddir) + # Clean and build clean_dir = os.path.join(builddir, "install") if os.path.isdir(clean_dir): @@ -153,7 +213,6 @@ class Builder: def makeFramework(self, outdir, builddirs): name = "opencv2" - libname = "libopencv_merged.a" # set the current dir to the dst root framework_dir = os.path.join(outdir, "%s.framework" % name) @@ -161,7 +220,12 @@ class Builder: shutil.rmtree(framework_dir) os.makedirs(framework_dir) - dstdir = os.path.join(framework_dir, "Versions", "A") + if self.dynamic: + dstdir = framework_dir + libname = "opencv2.framework/opencv2" + else: + dstdir = os.path.join(framework_dir, "Versions", "A") + libname = "libopencv_merged.a" # copy headers from one of build folders shutil.copytree(os.path.join(builddirs[0], "install", "include", "opencv2"), os.path.join(dstdir, "Headers")) @@ -174,22 +238,27 @@ class Builder: print("Creating universal library from:\n\t%s" % "\n\t".join(libs), file=sys.stderr) execute(lipocmd) - # copy Info.plist - resdir = os.path.join(dstdir, "Resources") - os.makedirs(resdir) - shutil.copyfile(self.getInfoPlist(builddirs), os.path.join(resdir, "Info.plist")) + # dynamic framework has different structure, just copy the Plist directly + if self.dynamic: + resdir = dstdir + shutil.copyfile(self.getInfoPlist(builddirs), os.path.join(resdir, "Info.plist")) + else: + # copy Info.plist + resdir = os.path.join(dstdir, "Resources") + os.makedirs(resdir) + shutil.copyfile(self.getInfoPlist(builddirs), os.path.join(resdir, "Info.plist")) - # make symbolic links - links = [ - (["A"], ["Versions", "Current"]), - (["Versions", "Current", "Headers"], ["Headers"]), - (["Versions", "Current", "Resources"], ["Resources"]), - (["Versions", "Current", name], [name]) - ] - for l in links: - s = os.path.join(*l[0]) - d = os.path.join(framework_dir, *l[1]) - os.symlink(s, d) + # make symbolic links + links = [ + (["A"], ["Versions", "Current"]), + (["Versions", "Current", "Headers"], ["Headers"]), + (["Versions", "Current", "Resources"], ["Resources"]), + (["Versions", "Current", name], [name]) + ] + for l in links: + s = os.path.join(*l[0]) + d = os.path.join(framework_dir, *l[1]) + os.symlink(s, d) class iOSBuilder(Builder): @@ -198,6 +267,8 @@ class iOSBuilder(Builder): return toolchain def getCMakeArgs(self, arch, target): + arch = ";".join(arch) + args = Builder.getCMakeArgs(self, arch, target) args = args + [ '-DIOS_ARCH=%s' % arch @@ -212,18 +283,16 @@ if __name__ == "__main__": parser.add_argument('--opencv', metavar='DIR', default=folder, help='folder with opencv repository (default is "../.." relative to script location)') parser.add_argument('--contrib', metavar='DIR', default=None, help='folder with opencv_contrib repository (default is "None" - build only main framework)') parser.add_argument('--without', metavar='MODULE', default=[], action='append', help='OpenCV modules to exclude from the framework') + parser.add_argument('--dynamic', default=False, action='store_true', help='build dynamic framework (default is "False" - builds static framework)') + parser.add_argument('--disable-bitcode', default=False, dest='bitcodedisabled', action='store_true', help='disable bitcode (enabled by default)') args = parser.parse_args() - b = iOSBuilder(args.opencv, args.contrib, args.without, + b = iOSBuilder(args.opencv, args.contrib, args.dynamic, args.bitcodedisabled, args.without, [ - ("armv7", "iPhoneOS"), - ("arm64", "iPhoneOS"), + (["armv7", "arm64"], "iPhoneOS"), ] if os.environ.get('BUILD_PRECOMMIT', None) else [ - ("armv7", "iPhoneOS"), - ("armv7s", "iPhoneOS"), - ("arm64", "iPhoneOS"), - ("i386", "iPhoneSimulator"), - ("x86_64", "iPhoneSimulator"), + (["armv7", "armv7s", "arm64"], "iPhoneOS"), + (["i386", "x86_64"], "iPhoneSimulator"), ]) b.build(args.out) diff --git a/platforms/ios/cmake/Modules/Platform/iOS.cmake b/platforms/ios/cmake/Modules/Platform/iOS.cmake index 63cf1d89e7..6915adfbc7 100644 --- a/platforms/ios/cmake/Modules/Platform/iOS.cmake +++ b/platforms/ios/cmake/Modules/Platform/iOS.cmake @@ -38,6 +38,14 @@ set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") +# Additional flags for dynamic framework +if (APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + set (CMAKE_MODULE_LINKER_FLAGS "-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") + set (CMAKE_SHARED_LINKER_FLAGS "-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") + set (CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG 1) + set (CMAKE_INSTALL_NAME_DIR "@rpath") +endif() + # Hidden visibilty is required for cxx on iOS set (no_warn "-Wno-unused-function -Wno-overloaded-virtual") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${no_warn}") @@ -144,7 +152,6 @@ set (CMAKE_C_CREATE_MACOSX_FRAMEWORK set (CMAKE_CXX_CREATE_MACOSX_FRAMEWORK " -o -install_name ") - # Add the install directory of the running cmake to the search directories # CMAKE_ROOT is CMAKE_INSTALL_PREFIX/share/cmake, so we need to go two levels up get_filename_component (_CMAKE_INSTALL_DIR "${CMAKE_ROOT}" PATH) diff --git a/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake b/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake index 24dab91ff6..44ad57ce74 100644 --- a/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake +++ b/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake @@ -86,24 +86,52 @@ endif() set(CMAKE_MACOSX_BUNDLE YES) set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") -set(CMAKE_OSX_ARCHITECTURES "${IOS_ARCH}" CACHE INTERNAL "Build architecture for iOS" FORCE) +if(APPLE_FRAMEWORK AND NOT BUILD_SHARED_LIBS) + set(CMAKE_OSX_ARCHITECTURES "${IOS_ARCH}" CACHE INTERNAL "Build architecture for iOS" FORCE) +endif() if(NOT __IN_TRY_COMPILE) set(_xcodebuild_wrapper "${CMAKE_BINARY_DIR}/xcodebuild_wrapper") if(NOT CMAKE_MAKE_PROGRAM STREQUAL _xcodebuild_wrapper) - set(_xcodebuild_wrapper_tmp "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/xcodebuild_wrapper") - file(WRITE "${_xcodebuild_wrapper_tmp}" "#!/bin/sh + if(APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + set(_xcodebuild_wrapper_tmp "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/xcodebuild_wrapper") + file(WRITE "${_xcodebuild_wrapper_tmp}" "#!/bin/sh +${CMAKE_MAKE_PROGRAM} IPHONEOS_DEPLOYMENT_TARGET=8.0 -sdk ${CMAKE_OSX_SYSROOT} \$*") + # Make executable + file(COPY "${_xcodebuild_wrapper_tmp}" DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + set(CMAKE_MAKE_PROGRAM "${_xcodebuild_wrapper}" CACHE INTERNAL "" FORCE) + else() + set(_xcodebuild_wrapper_tmp "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/xcodebuild_wrapper") + file(WRITE "${_xcodebuild_wrapper_tmp}" "#!/bin/sh ${CMAKE_MAKE_PROGRAM} IPHONEOS_DEPLOYMENT_TARGET=6.0 ARCHS=${IOS_ARCH} -sdk ${CMAKE_OSX_SYSROOT} \$*") - # Make executable - file(COPY "${_xcodebuild_wrapper_tmp}" DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) - set(CMAKE_MAKE_PROGRAM "${_xcodebuild_wrapper}" CACHE INTERNAL "" FORCE) + # Make executable + file(COPY "${_xcodebuild_wrapper_tmp}" DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + set(CMAKE_MAKE_PROGRAM "${_xcodebuild_wrapper}" CACHE INTERNAL "" FORCE) + endif() endif() endif() # Standard settings set(CMAKE_SYSTEM_NAME iOS) -set(CMAKE_SYSTEM_VERSION 6.0) -set(CMAKE_SYSTEM_PROCESSOR "${IOS_ARCH}") + +# Apple Framework settings +if(APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + set(CMAKE_SYSTEM_VERSION 8.0) + set(CMAKE_C_SIZEOF_DATA_PTR 4) + set(CMAKE_CXX_SIZEOF_DATA_PTR 4) +else() + set(CMAKE_SYSTEM_VERSION 6.0) + set(CMAKE_SYSTEM_PROCESSOR "${IOS_ARCH}") + + if(AARCH64 OR X86_64) + set(CMAKE_C_SIZEOF_DATA_PTR 8) + set(CMAKE_CXX_SIZEOF_DATA_PTR 8) + else() + set(CMAKE_C_SIZEOF_DATA_PTR 4) + set(CMAKE_CXX_SIZEOF_DATA_PTR 4) + endif() +endif() + # Include extra modules for the iOS platform files set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/cmake/Modules") @@ -112,13 +140,6 @@ include(CMakeForceCompiler) #CMAKE_FORCE_C_COMPILER (clang GNU) #CMAKE_FORCE_CXX_COMPILER (clang++ GNU) -if(AARCH64 OR X86_64) - set(CMAKE_C_SIZEOF_DATA_PTR 8) - set(CMAKE_CXX_SIZEOF_DATA_PTR 8) -else() - set(CMAKE_C_SIZEOF_DATA_PTR 4) - set(CMAKE_CXX_SIZEOF_DATA_PTR 4) -endif() set(CMAKE_C_HAS_ISYSROOT 1) set(CMAKE_CXX_HAS_ISYSROOT 1) set(CMAKE_C_COMPILER_ABI ELF) @@ -134,4 +155,4 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -toolchain_save_config(IOS_ARCH) +toolchain_save_config(IOS_ARCH) \ No newline at end of file From c659f94dca9f8504afc2b30c750e2ab841bed267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20W=C3=BCrtz?= Date: Fri, 20 Jan 2017 18:34:41 +0100 Subject: [PATCH 323/409] Expose more UMat constructors to python --- modules/python/src2/cv2.cpp | 88 +++++++++++++++++++++++++++---------- modules/python/test/test.py | 11 +++++ 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index e69e933375..66e3733cec 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -417,34 +417,74 @@ typedef struct { UMat* um; } cv2_UMatWrapperObject; -// UMatWrapper init - takes one optional argument, that converts to Mat, that converts to UMat and stored inside. -// If no argument given - empty UMat created. +static bool PyObject_IsUMat(PyObject *o); + +// UMatWrapper init - try to map arguments from python to UMat constructors static int UMatWrapper_init(cv2_UMatWrapperObject *self, PyObject *args, PyObject *kwds) { - self->um = new UMat(); - - PyObject *np_mat = NULL; - - static char *kwlist[] = {new char[3], NULL}; - strcpy(kwlist[0], "mat"); - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &np_mat)) - return -1; - - if (np_mat) { - Mat m; - if (!pyopencv_to(np_mat, m, ArgInfo("UMatWrapper.np_mat", 0))) - return -1; - - m.copyTo(*self->um); + self->um = NULL; + { + // constructor () + const char *kwlist[] = {NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "", (char**) kwlist)) { + self->um = new UMat(); + return 0; + } + PyErr_Clear(); } - - return 0; + { + // constructor (rows, cols, type) + const char *kwlist[] = {"rows", "cols", "type", NULL}; + int rows, cols, type; + if (PyArg_ParseTupleAndKeywords(args, kwds, "iii", (char**) kwlist, &rows, &cols, &type)) { + self->um = new UMat(rows, cols, type); + return 0; + } + PyErr_Clear(); + } + { + // constructor (m, rowRange, colRange) + const char *kwlist[] = {"m", "rowRange", "colRange", NULL}; + PyObject *obj = NULL; + int y0 = -1, y1 = -1, x0 = -1, x1 = -1; + if (PyArg_ParseTupleAndKeywords(args, kwds, "O(ii)|(ii)", (char**) kwlist, &obj, &y0, &y1, &x0, &x1) && PyObject_IsUMat(obj)) { + UMat *um_other = ((cv2_UMatWrapperObject *) obj)->um; + Range rowRange(y0, y1); + Range colRange = (x0 >= 0 && x1 >= 0) ? Range(x0, x1) : Range::all(); + self->um = new UMat(*um_other, rowRange, colRange); + return 0; + } + PyErr_Clear(); + } + { + // constructor (m) + const char *kwlist[] = {"m", NULL}; + PyObject *obj = NULL; + if (PyArg_ParseTupleAndKeywords(args, kwds, "O", (char**) kwlist, &obj)) { + // constructor (UMat m) + if (PyObject_IsUMat(obj)) { + UMat *um_other = ((cv2_UMatWrapperObject *) obj)->um; + self->um = new UMat(*um_other); + return 0; + } + // python specific constructor from array like object + Mat m; + if (pyopencv_to(obj, m, ArgInfo("UMatWrapper.np_mat", 0))) { + self->um = new UMat(); + m.copyTo(*self->um); + return 0; + } + } + PyErr_Clear(); + } + PyErr_SetString(PyExc_TypeError, "no matching UMat constructor found/supported"); + return -1; } static void UMatWrapper_dealloc(cv2_UMatWrapperObject* self) { - delete self->um; + if (self->um) + delete self->um; #if PY_MAJOR_VERSION >= 3 Py_TYPE(self)->tp_free((PyObject*)self); #else @@ -529,8 +569,12 @@ static PyTypeObject cv2_UMatWrapperType = { #endif }; +static bool PyObject_IsUMat(PyObject *o) { + return (o != NULL) && PyObject_TypeCheck(o, &cv2_UMatWrapperType); +} + static bool pyopencv_to(PyObject* o, UMat& um, const ArgInfo info) { - if (o != NULL && PyObject_TypeCheck(o, &cv2_UMatWrapperType) ) { + if (PyObject_IsUMat(o)) { um = *((cv2_UMatWrapperObject *) o)->um; return true; } diff --git a/modules/python/test/test.py b/modules/python/test/test.py index 5a66769a49..f4585471e8 100755 --- a/modules/python/test/test.py +++ b/modules/python/test/test.py @@ -123,6 +123,17 @@ class Hackathon244Tests(NewOpenCVTests): boost.getMaxDepth() # from ml::DTrees boost.isClassifier() # from ml::StatModel + def test_umat_construct(self): + data = np.random.random([512, 512]) + # UMat constructors + data_um = cv2.UMat(data) # from ndarray + data_sub_um = cv2.UMat(data_um, [0, 256], [0, 256]) # from UMat + data_dst_um = cv2.UMat(256, 256, cv2.CV_64F) # from size/type + + # simple test + cv2.multiply(data_sub_um, 2., dst=data_dst_um) + assert np.allclose(2. * data[:256, :256], data_dst_um.get()) + def test_umat_matching(self): img1 = self.get_sample("samples/data/right01.jpg") img2 = self.get_sample("samples/data/right02.jpg") From 1d01e435ca9d029c517091d0444eb5d3bd8acdf4 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Mon, 23 Jan 2017 19:03:22 +0900 Subject: [PATCH 324/409] use universal intrinsic in fast --- modules/features2d/src/fast.cpp | 122 +++++++++++++++++--------------- 1 file changed, 64 insertions(+), 58 deletions(-) diff --git a/modules/features2d/src/fast.cpp b/modules/features2d/src/fast.cpp index e731dedca7..98d62a3b2c 100644 --- a/modules/features2d/src/fast.cpp +++ b/modules/features2d/src/fast.cpp @@ -44,6 +44,7 @@ The references are: #include "precomp.hpp" #include "fast_score.hpp" #include "opencl_kernels_features2d.hpp" +#include "opencv2/core/hal/intrin.hpp" #include "opencv2/core/openvx/ovx_defs.hpp" #if defined _MSC_VER @@ -58,9 +59,10 @@ void FAST_t(InputArray _img, std::vector& keypoints, int threshold, bo { Mat img = _img.getMat(); const int K = patternSize/2, N = patternSize + K + 1; -#if CV_SSE2 +#if CV_SIMD128 const int quarterPatternSize = patternSize/4; - (void)quarterPatternSize; + v_uint8x16 delta = v_setall_u8(0x80), t = v_setall_u8((char)threshold), K16 = v_setall_u8((char)K); + bool hasSimd = hasSIMD128(); #endif int i, j, k, pixel[25]; makeOffsets(pixel, (int)img.step, patternSize); @@ -69,12 +71,6 @@ void FAST_t(InputArray _img, std::vector& keypoints, int threshold, bo threshold = std::min(std::max(threshold, 0), 255); -#if CV_SSE2 - __m128i delta = _mm_set1_epi8(-128), t = _mm_set1_epi8((char)threshold), K16 = _mm_set1_epi8((char)K); - (void)K16; - (void)delta; - (void)t; -#endif uchar threshold_tab[512]; for( i = -255; i <= 255; i++ ) threshold_tab[i+255] = (uchar)(i < -threshold ? 1 : i > threshold ? 2 : 0); @@ -99,66 +95,76 @@ void FAST_t(InputArray _img, std::vector& keypoints, int threshold, bo if( i < img.rows - 3 ) { j = 3; - #if CV_SSE2 - if( patternSize == 16 ) +#if CV_SIMD128 + if( hasSimd ) { - for(; j < img.cols - 16 - 3; j += 16, ptr += 16) + if( patternSize == 16 ) { - __m128i m0, m1; - __m128i v0 = _mm_loadu_si128((const __m128i*)ptr); - __m128i v1 = _mm_xor_si128(_mm_subs_epu8(v0, t), delta); - v0 = _mm_xor_si128(_mm_adds_epu8(v0, t), delta); - - __m128i x0 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[0])), delta); - __m128i x1 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[quarterPatternSize])), delta); - __m128i x2 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[2*quarterPatternSize])), delta); - __m128i x3 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[3*quarterPatternSize])), delta); - m0 = _mm_and_si128(_mm_cmpgt_epi8(x0, v0), _mm_cmpgt_epi8(x1, v0)); - m1 = _mm_and_si128(_mm_cmpgt_epi8(v1, x0), _mm_cmpgt_epi8(v1, x1)); - m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x1, v0), _mm_cmpgt_epi8(x2, v0))); - m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x1), _mm_cmpgt_epi8(v1, x2))); - m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x2, v0), _mm_cmpgt_epi8(x3, v0))); - m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x2), _mm_cmpgt_epi8(v1, x3))); - m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x3, v0), _mm_cmpgt_epi8(x0, v0))); - m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x3), _mm_cmpgt_epi8(v1, x0))); - m0 = _mm_or_si128(m0, m1); - int mask = _mm_movemask_epi8(m0); - if( mask == 0 ) - continue; - if( (mask & 255) == 0 ) + for(; j < img.cols - 16 - 3; j += 16, ptr += 16) { - j -= 8; - ptr -= 8; - continue; - } + v_uint8x16 v = v_load(ptr); + v_int8x16 v0 = v_reinterpret_as_s8((v + t) ^ delta); + v_int8x16 v1 = v_reinterpret_as_s8((v - t) ^ delta); - __m128i c0 = _mm_setzero_si128(), c1 = c0, max0 = c0, max1 = c0; - for( k = 0; k < N; k++ ) - { - __m128i x = _mm_xor_si128(_mm_loadu_si128((const __m128i*)(ptr + pixel[k])), delta); - m0 = _mm_cmpgt_epi8(x, v0); - m1 = _mm_cmpgt_epi8(v1, x); + v_int8x16 x0 = v_reinterpret_as_s8(v_sub_wrap(v_load(ptr + pixel[0]), delta)); + v_int8x16 x1 = v_reinterpret_as_s8(v_sub_wrap(v_load(ptr + pixel[quarterPatternSize]), delta)); + v_int8x16 x2 = v_reinterpret_as_s8(v_sub_wrap(v_load(ptr + pixel[2*quarterPatternSize]), delta)); + v_int8x16 x3 = v_reinterpret_as_s8(v_sub_wrap(v_load(ptr + pixel[3*quarterPatternSize]), delta)); - c0 = _mm_and_si128(_mm_sub_epi8(c0, m0), m0); - c1 = _mm_and_si128(_mm_sub_epi8(c1, m1), m1); + v_int8x16 m0, m1; + m0 = (v0 < x0) & (v0 < x1); + m1 = (x0 < v1) & (x1 < v1); + m0 = m0 | ((v0 < x1) & (v0 < x2)); + m1 = m1 | ((x1 < v1) & (x2 < v1)); + m0 = m0 | ((v0 < x2) & (v0 < x3)); + m1 = m1 | ((x2 < v1) & (x3 < v1)); + m0 = m0 | ((v0 < x3) & (v0 < x0)); + m1 = m1 | ((x3 < v1) & (x0 < v1)); + m0 = m0 | m1; - max0 = _mm_max_epu8(max0, c0); - max1 = _mm_max_epu8(max1, c1); - } - - max0 = _mm_max_epu8(max0, max1); - int m = _mm_movemask_epi8(_mm_cmpgt_epi8(max0, K16)); - - for( k = 0; m > 0 && k < 16; k++, m >>= 1 ) - if(m & 1) + int mask = v_signmask(m0); + if( mask == 0 ) + continue; + if( (mask & 255) == 0 ) { - cornerpos[ncorners++] = j+k; - if(nonmax_suppression) - curr[j+k] = (uchar)cornerScore(ptr+k, pixel, threshold); + j -= 8; + ptr -= 8; + continue; } + + v_int8x16 c0 = v_setzero_s8(); + v_int8x16 c1 = v_setzero_s8(); + v_uint8x16 max0 = v_setzero_u8(); + v_uint8x16 max1 = v_setzero_u8(); + for( k = 0; k < N; k++ ) + { + v_int8x16 x = v_reinterpret_as_s8(v_load((ptr + pixel[k])) ^ delta); + m0 = v0 < x; + m1 = x < v1; + + c0 = v_sub_wrap(c0, m0) & m0; + c1 = v_sub_wrap(c1, m1) & m1; + + max0 = v_max(max0, v_reinterpret_as_u8(c0)); + max1 = v_max(max1, v_reinterpret_as_u8(c1)); + } + + max0 = v_max(max0, max1); + int m = v_signmask(K16 < max0); + + for( k = 0; m > 0 && k < 16; k++, m >>= 1 ) + { + if(m & 1) + { + cornerpos[ncorners++] = j+k; + if(nonmax_suppression) + curr[j+k] = (uchar)cornerScore(ptr+k, pixel, threshold); + } + } + } } } - #endif +#endif for( ; j < img.cols - 3; j++, ptr++ ) { int v = ptr[0]; From 9bb5d6cab03c18996737c40e65850f3b3938a4d3 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 17 Jan 2017 19:58:57 +0300 Subject: [PATCH 325/409] remove precomp.cpp --- modules/shape/src/precomp.cpp | 45 ----------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 modules/shape/src/precomp.cpp diff --git a/modules/shape/src/precomp.cpp b/modules/shape/src/precomp.cpp deleted file mode 100644 index 730edbb63d..0000000000 --- a/modules/shape/src/precomp.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. -// Copyright (C) 2009, Willow Garage Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - -#include "precomp.hpp" - -/* End of file. */ From 36b34465a4314d3ec42489c3157523bd62d47d01 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 16 Jan 2017 18:12:38 +0300 Subject: [PATCH 326/409] cmake: include contrib modules into opencv_world --- CMakeLists.txt | 4 ++++ cmake/OpenCVGenABI.cmake | 10 ++++++---- cmake/OpenCVModule.cmake | 8 +++++--- cmake/OpenCVPCHSupport.cmake | 2 ++ cmake/OpenCVUtils.cmake | 9 ++++++++- modules/stitching/CMakeLists.txt | 2 +- modules/ts/misc/run_utils.py | 2 +- modules/viz/CMakeLists.txt | 8 +++++--- modules/world/CMakeLists.txt | 11 +++++++++-- 9 files changed, 41 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc45f6f394..c79ad2be6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,10 @@ if(POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif() +if(POLICY CMP0023) + cmake_policy(SET CMP0023 NEW) +endif() + if(POLICY CMP0026) # silence cmake 3.0+ warnings about reading LOCATION attribute cmake_policy(SET CMP0026 OLD) diff --git a/cmake/OpenCVGenABI.cmake b/cmake/OpenCVGenABI.cmake index 86aa78b17b..e9d6a33481 100644 --- a/cmake/OpenCVGenABI.cmake +++ b/cmake/OpenCVGenABI.cmake @@ -33,10 +33,12 @@ foreach(mod ${OPENCV_MODULES_BUILD}) list(APPEND OPENCV_ABI_SKIP_HEADERS "${h}") endforeach() # libraries - set(lib_name "") - get_target_property(lib_name opencv_${mod} LOCATION) - get_filename_component(lib_name "${lib_name}" NAME) - list(APPEND OPENCV_ABI_SKIP_LIBRARIES "${lib_name}") + if(TARGET opencv_${mod}) # opencv_world + set(lib_name "") + get_target_property(lib_name opencv_${mod} LOCATION) + get_filename_component(lib_name "${lib_name}" NAME) + list(APPEND OPENCV_ABI_SKIP_LIBRARIES "${lib_name}") + endif() endif() endforeach() string(REPLACE ";" "\n " OPENCV_ABI_SKIP_HEADERS "${OPENCV_ABI_SKIP_HEADERS}") diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index ebeb25cf2b..a0e859232e 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -182,7 +182,7 @@ macro(ocv_add_module _name) # add self to the world dependencies if((NOT DEFINED OPENCV_MODULE_IS_PART_OF_WORLD AND NOT OPENCV_MODULE_${the_module}_CLASS STREQUAL "BINDINGS" - AND NOT OPENCV_PROCESSING_EXTRA_MODULES + AND (NOT OPENCV_PROCESSING_EXTRA_MODULES OR NOT OPENCV_WORLD_EXCLUDE_EXTRA_MODULES) AND (NOT BUILD_SHARED_LIBS OR NOT "x${OPENCV_MODULE_TYPE}" STREQUAL "xSTATIC")) OR OPENCV_MODULE_IS_PART_OF_WORLD ) @@ -724,8 +724,10 @@ endmacro() # ocv_create_module() # ocv_create_module() macro(ocv_create_module) - ocv_debug_message("ocv_create_module(" ${ARGN} ")") - set(OPENCV_MODULE_${the_module}_LINK_DEPS "${OPENCV_MODULE_${the_module}_LINK_DEPS};${ARGN}" CACHE INTERNAL "") + ocv_debug_message("${the_module}: ocv_create_module(" ${ARGN} ")") + if(NOT " ${ARGN}" STREQUAL " ") + set(OPENCV_MODULE_${the_module}_LINK_DEPS "${OPENCV_MODULE_${the_module}_LINK_DEPS};${ARGN}" CACHE INTERNAL "") + endif() if(${BUILD_opencv_world} AND OPENCV_MODULE_${the_module}_IS_PART_OF_WORLD) # nothing set(the_module_target opencv_world) diff --git a/cmake/OpenCVPCHSupport.cmake b/cmake/OpenCVPCHSupport.cmake index 90437cb204..29f21d8015 100644 --- a/cmake/OpenCVPCHSupport.cmake +++ b/cmake/OpenCVPCHSupport.cmake @@ -303,9 +303,11 @@ ENDMACRO(ADD_PRECOMPILED_HEADER) MACRO(GET_NATIVE_PRECOMPILED_HEADER _targetName _input) + if(ENABLE_PRECOMPILED_HEADERS) if(CMAKE_GENERATOR MATCHES "^Visual.*$") set(${_targetName}_pch ${CMAKE_CURRENT_BINARY_DIR}/${_targetName}_pch.cpp) endif() + endif() ENDMACRO(GET_NATIVE_PRECOMPILED_HEADER) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 7a0e448f77..474f7db609 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -887,7 +887,14 @@ function(ocv_target_link_libraries target) if(";${LINK_DEPS};" MATCHES ";${target};") list(REMOVE_ITEM LINK_DEPS "${target}") # prevent "link to itself" warning (world problem) endif() - target_link_libraries(${target} ${LINK_DEPS}) + if(NOT TARGET ${target}) + if(NOT DEFINED OPENCV_MODULE_${target}_LOCATION) + message(FATAL_ERROR "ocv_target_link_libraries: invalid target: '${target}'") + endif() + set(OPENCV_MODULE_${target}_LINK_DEPS ${OPENCV_MODULE_${target}_LINK_DEPS} ${LINK_DEPS} CACHE INTERNAL "" FORCE) + else() + target_link_libraries(${target} ${LINK_DEPS}) + endif() endfunction() function(_ocv_append_target_includes target) diff --git a/modules/stitching/CMakeLists.txt b/modules/stitching/CMakeLists.txt index 76c7bc8489..0e4f39a99d 100644 --- a/modules/stitching/CMakeLists.txt +++ b/modules/stitching/CMakeLists.txt @@ -5,7 +5,7 @@ if(HAVE_CUDA) endif() set(STITCHING_CONTRIB_DEPS "opencv_xfeatures2d") -if(BUILD_SHARED_LIBS AND BUILD_opencv_world) +if(BUILD_SHARED_LIBS AND BUILD_opencv_world AND OPENCV_WORLD_EXCLUDE_EXTRA_MODULES) set(STITCHING_CONTRIB_DEPS "") endif() ocv_define_module(stitching opencv_imgproc opencv_features2d opencv_calib3d opencv_objdetect diff --git a/modules/ts/misc/run_utils.py b/modules/ts/misc/run_utils.py index 8740aa7855..2e9a66f8bd 100644 --- a/modules/ts/misc/run_utils.py +++ b/modules/ts/misc/run_utils.py @@ -193,7 +193,7 @@ class CMakeCache: self.tests_dir = os.path.normpath(path) def read(self, path, fname): - rx = re.compile(r'^opencv_(\w+)_SOURCE_DIR:STATIC=(.*)$') + rx = re.compile(r'^OPENCV_MODULE_opencv_(\w+)_LOCATION:INTERNAL=(.*)$') module_paths = {} # name -> path with open(fname, "rt") as cachefile: for l in cachefile.readlines(): diff --git a/modules/viz/CMakeLists.txt b/modules/viz/CMakeLists.txt index 9fd0301635..a22f52fd87 100644 --- a/modules/viz/CMakeLists.txt +++ b/modules/viz/CMakeLists.txt @@ -2,12 +2,14 @@ if(NOT WITH_VTK OR NOT DEFINED HAVE_VTK OR NOT HAVE_VTK) ocv_module_disable(viz) endif() -include(${VTK_USE_FILE}) set(the_description "Viz") -ocv_define_module(viz opencv_core ${VTK_LIBRARIES} WRAP python) +ocv_define_module(viz opencv_core WRAP python) + +include(${VTK_USE_FILE}) +ocv_target_link_libraries(${the_module} ${VTK_LIBRARIES}) if(APPLE AND BUILD_opencv_viz) - ocv_target_link_libraries(opencv_viz "-framework Cocoa") + ocv_target_link_libraries(${the_module} "-framework Cocoa") endif() if(TARGET opencv_test_viz) diff --git a/modules/world/CMakeLists.txt b/modules/world/CMakeLists.txt index 1a77245df2..dde793fa00 100644 --- a/modules/world/CMakeLists.txt +++ b/modules/world/CMakeLists.txt @@ -11,6 +11,11 @@ endif() function(include_one_module m) include("${OPENCV_MODULE_${m}_LOCATION}/CMakeLists.txt") + foreach(var + CMAKE_CXX_FLAGS CMAKE_C_FLAGS # Propagate warnings settings + ) + set(${var} "${${var}}" PARENT_SCOPE) + endforeach() endfunction() if(NOT OPENCV_INITIAL_PASS) @@ -35,12 +40,14 @@ ocv_add_module(world opencv_core) set(headers_list "HEADERS") set(sources_list "SOURCES") set(link_deps "") -foreach(m ${OPENCV_MODULE_${the_module}_DEPS}) +foreach(m ${OPENCV_MODULE_${the_module}_DEPS} opencv_world) if(OPENCV_MODULE_${m}_IS_PART_OF_WORLD) set(headers_list "${headers_list};${OPENCV_MODULE_${m}_HEADERS}") set(sources_list "${sources_list};${OPENCV_MODULE_${m}_SOURCES}") endif() - set(link_deps "${link_deps};${OPENCV_MODULE_${m}_LINK_DEPS}") + if(NOT " ${OPENCV_MODULE_${m}_LINK_DEPS}" STREQUAL " ") + list(APPEND link_deps ${OPENCV_MODULE_${m}_LINK_DEPS}) + endif() endforeach() ocv_glob_module_sources(${headers_list} ${sources_list}) From a5d16f09e659229d3e7e907397cdcaf4bf05909a Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 18 Jan 2017 17:14:55 +0300 Subject: [PATCH 327/409] cmake world: allow to build python bindings --- cmake/OpenCVModule.cmake | 7 +++++-- modules/python/CMakeLists.txt | 26 ++++++++++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index a0e859232e..6e6fe16c32 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -233,7 +233,7 @@ macro(ocv_add_module _name) endmacro() # excludes module from current configuration -macro(ocv_module_disable module) +macro(ocv_module_disable_ module) set(__modname ${module}) if(NOT __modname MATCHES "^opencv_") set(__modname opencv_${module}) @@ -246,9 +246,12 @@ macro(ocv_module_disable module) # touch variable controlling build of the module to suppress "unused variable" CMake warning endif() unset(__modname) - return() # leave the current folder endmacro() +macro(ocv_module_disable module) + ocv_module_disable_(${module}) + return() # leave the current folder +endmacro() # collect modules from specified directories # NB: must be called only once! diff --git a/modules/python/CMakeLists.txt b/modules/python/CMakeLists.txt index 1da5e329d6..d85a516791 100644 --- a/modules/python/CMakeLists.txt +++ b/modules/python/CMakeLists.txt @@ -2,16 +2,26 @@ # CMake file for python support # ---------------------------------------------------------------------------- -if((WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug") - OR BUILD_opencv_world - ) - ocv_module_disable(python2) - ocv_module_disable(python3) +if(ANDROID OR APPLE_FRAMEWORK OR WINRT) + set(__disable_python2 ON) + set(__disable_python3 ON) +elseif(BUILD_opencv_world OR (WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")) + if(NOT DEFINED BUILD_opencv_python2) + set(__disable_python2 ON) + endif() + if(NOT DEFINED BUILD_opencv_python3) + set(__disable_python3 ON) + endif() endif() -if(ANDROID OR APPLE_FRAMEWORK OR WINRT) - ocv_module_disable(python2) - ocv_module_disable(python3) +if(__disable_python2) + ocv_module_disable_(python2) +endif() +if(__disable_python3) + ocv_module_disable_(python3) +endif() +if(__disable_python2 AND __disable_python3) + return() endif() add_subdirectory(python2) From f1adc32765b84ca7fe85910da0c822a72efc681d Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 18 Jan 2017 18:37:52 +0300 Subject: [PATCH 328/409] cmake: fix support of duplicated file names in static libraries --- cmake/OpenCVDetectCXXCompiler.cmake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake index 8ecf4d82bd..e7dac80275 100644 --- a/cmake/OpenCVDetectCXXCompiler.cmake +++ b/cmake/OpenCVDetectCXXCompiler.cmake @@ -172,3 +172,13 @@ elseif(MINGW) set(OpenCV_ARCH x86) endif() endif() + +# Fix handling of duplicated files in the same static library: +# https://public.kitware.com/Bug/view.php?id=14874 +if(CMAKE_VERSION VERSION_LESS "3.1") + foreach(var CMAKE_C_ARCHIVE_APPEND CMAKE_CXX_ARCHIVE_APPEND) + if(${var} MATCHES "^ r") + string(REPLACE " r" " q" ${var} "${${var}}") + endif() + endforeach() +endif() From 68e50661a2f0000eba8489e40086b5e2cfbb3fbd Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 18 Jan 2017 18:47:34 +0300 Subject: [PATCH 329/409] cmake: fix CUDA flags generation VTK uses parentheses in defines, so generated code is wrong: set(nvcc_flags -m64;-D__OPENCV_BUILD=1;-DvtkRenderingContext2D_AUTOINIT=1(vtkRenderingContextOpenGL);...) --- cmake/FindCUDA/run_nvcc.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FindCUDA/run_nvcc.cmake b/cmake/FindCUDA/run_nvcc.cmake index abdd3079e1..8d1ceb1663 100644 --- a/cmake/FindCUDA/run_nvcc.cmake +++ b/cmake/FindCUDA/run_nvcc.cmake @@ -72,7 +72,7 @@ set(generated_cubin_file_internal "@generated_cubin_file@") # path set(CUDA_NVCC_EXECUTABLE "@CUDA_NVCC_EXECUTABLE@") # path set(CUDA_NVCC_FLAGS @CUDA_NVCC_FLAGS@ ;; @CUDA_WRAP_OPTION_NVCC_FLAGS@) # list @CUDA_NVCC_FLAGS_CONFIG@ -set(nvcc_flags @nvcc_flags@) # list +set(nvcc_flags "@nvcc_flags@") # list set(CUDA_NVCC_INCLUDE_ARGS "@CUDA_NVCC_INCLUDE_ARGS@") # list (needs to be in quotes to handle spaces properly). set(format_flag "@format_flag@") # string From 4b7c0b9c76cf201172ab66cf77f9cdbf6fb9c586 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 19 Jan 2017 16:56:06 +0300 Subject: [PATCH 330/409] cuda: fix samples --- samples/gpu/CMakeLists.txt | 12 ++---------- samples/gpu/performance/CMakeLists.txt | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/samples/gpu/CMakeLists.txt b/samples/gpu/CMakeLists.txt index 8c97ea865a..0f734677be 100644 --- a/samples/gpu/CMakeLists.txt +++ b/samples/gpu/CMakeLists.txt @@ -14,27 +14,19 @@ if(BUILD_EXAMPLES AND OCV_DEPENDENCIES_FOUND) project("${project}_samples") ocv_include_modules_recurse(${OPENCV_CUDA_SAMPLES_REQUIRED_DEPS}) - ocv_include_directories( - "${OpenCV_SOURCE_DIR}/modules/gpu/src/nvidia" - "${OpenCV_SOURCE_DIR}/modules/gpu/src/nvidia/core" - ) if(HAVE_opencv_xfeatures2d) - ocv_include_directories("${OpenCV_SOURCE_DIR}/modules/xfeatures2d/include") + ocv_include_modules_recurse(opencv_xfeatures2d) endif() if(HAVE_opencv_cudacodec) - ocv_include_directories("${OpenCV_SOURCE_DIR}/modules/cudacodec/include") + ocv_include_modules_recurse(opencv_cudacodec) endif() if(HAVE_CUDA) ocv_include_directories(${CUDA_INCLUDE_DIRS}) endif() - if(HAVE_OPENCL) - ocv_include_directories("${OpenCV_SOURCE_DIR}/modules/ocl/include") - endif() - if(CMAKE_COMPILER_IS_GNUCXX AND NOT ENABLE_NOISY_WARNINGS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-function") endif() diff --git a/samples/gpu/performance/CMakeLists.txt b/samples/gpu/performance/CMakeLists.txt index 9c9fb5b676..2b1bf0be6b 100644 --- a/samples/gpu/performance/CMakeLists.txt +++ b/samples/gpu/performance/CMakeLists.txt @@ -4,11 +4,11 @@ file(GLOB sources "performance/*.cpp") file(GLOB headers "performance/*.h") if(HAVE_opencv_xfeatures2d) - ocv_include_directories("${opencv_xfeatures2d_SOURCE_DIR}/include") + ocv_include_modules_recurse(opencv_xfeatures2d) endif() if(HAVE_opencv_bgsegm) - ocv_include_directories("${opencv_bgsegm_SOURCE_DIR}/include") + ocv_include_modules_recurse(opencv_bgsegm) endif() add_executable(${the_target} ${sources} ${headers}) From a50efda49d8ecff53c7c17e59025e175f8edc261 Mon Sep 17 00:00:00 2001 From: Dal Rupnik Date: Mon, 23 Jan 2017 17:15:17 +0100 Subject: [PATCH 331/409] Added export functions for ObjC classes --- modules/videoio/include/opencv2/videoio/cap_ios.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/videoio/include/opencv2/videoio/cap_ios.h b/modules/videoio/include/opencv2/videoio/cap_ios.h index c90ad2e73e..93c8a0cbea 100644 --- a/modules/videoio/include/opencv2/videoio/cap_ios.h +++ b/modules/videoio/include/opencv2/videoio/cap_ios.h @@ -32,6 +32,8 @@ #import #include "opencv2/core.hpp" +#define OPENCV_OBJC_EXPORT __attribute__((visibility("default"))) + //! @addtogroup videoio_ios //! @{ @@ -39,7 +41,7 @@ @class CvAbstractCamera; -@interface CvAbstractCamera : NSObject +OPENCV_OBJC_EXPORT @interface CvAbstractCamera : NSObject { UIDeviceOrientation currentDeviceOrientation; @@ -87,7 +89,7 @@ @class CvVideoCamera; -@protocol CvVideoCameraDelegate +OPENCV_OBJC_EXPORT @protocol CvVideoCameraDelegate #ifdef __cplusplus // delegate method for processing image frames @@ -96,7 +98,7 @@ @end -@interface CvVideoCamera : CvAbstractCamera +OPENCV_OBJC_EXPORT @interface CvVideoCamera : CvAbstractCamera { AVCaptureVideoDataOutput *videoDataOutput; @@ -129,14 +131,14 @@ @class CvPhotoCamera; -@protocol CvPhotoCameraDelegate +OPENCV_OBJC_EXPORT @protocol CvPhotoCameraDelegate - (void)photoCamera:(CvPhotoCamera*)photoCamera capturedImage:(UIImage *)image; - (void)photoCameraCancel:(CvPhotoCamera*)photoCamera; @end -@interface CvPhotoCamera : CvAbstractCamera +OPENCV_OBJC_EXPORT @interface CvPhotoCamera : CvAbstractCamera { AVCaptureStillImageOutput *stillImageOutput; } From b34ebc8cfef7582167fc116ff8726c7ce7e1fb73 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Tue, 24 Jan 2017 18:25:22 +0900 Subject: [PATCH 332/409] use universal intrinsic in stereo matching - check SIMD availability before executing --- modules/calib3d/src/stereobm.cpp | 534 +++++++++++++++---------------- 1 file changed, 265 insertions(+), 269 deletions(-) diff --git a/modules/calib3d/src/stereobm.cpp b/modules/calib3d/src/stereobm.cpp index cd861310b9..eb075d8c73 100644 --- a/modules/calib3d/src/stereobm.cpp +++ b/modules/calib3d/src/stereobm.cpp @@ -49,6 +49,7 @@ #include #include #include "opencl_kernels_calib3d.hpp" +#include "opencv2/core/hal/intrin.hpp" namespace cv { @@ -203,8 +204,8 @@ prefilterXSobel( const Mat& src, Mat& dst, int ftzero ) tab[x] = (uchar)(x - OFS < -ftzero ? 0 : x - OFS > ftzero ? ftzero*2 : x - OFS + ftzero); uchar val0 = tab[0 + OFS]; -#if CV_SSE2 - volatile bool useSIMD = checkHardwareSupport(CV_CPU_SSE2); +#if CV_SIMD128 + bool useSIMD = hasSIMD128(); #endif for( y = 0; y < size.height-1; y += 2 ) @@ -219,71 +220,34 @@ prefilterXSobel( const Mat& src, Mat& dst, int ftzero ) dptr0[0] = dptr0[size.width-1] = dptr1[0] = dptr1[size.width-1] = val0; x = 1; -#if CV_NEON - int16x8_t ftz = vdupq_n_s16 ((short) ftzero); - uint8x8_t ftz2 = vdup_n_u8 (cv::saturate_cast(ftzero*2)); - - for(; x <=size.width-9; x += 8 ) - { - uint8x8_t c0 = vld1_u8 (srow0 + x - 1); - uint8x8_t c1 = vld1_u8 (srow1 + x - 1); - uint8x8_t d0 = vld1_u8 (srow0 + x + 1); - uint8x8_t d1 = vld1_u8 (srow1 + x + 1); - - int16x8_t t0 = vreinterpretq_s16_u16 (vsubl_u8 (d0, c0)); - int16x8_t t1 = vreinterpretq_s16_u16 (vsubl_u8 (d1, c1)); - - uint8x8_t c2 = vld1_u8 (srow2 + x - 1); - uint8x8_t c3 = vld1_u8 (srow3 + x - 1); - uint8x8_t d2 = vld1_u8 (srow2 + x + 1); - uint8x8_t d3 = vld1_u8 (srow3 + x + 1); - - int16x8_t t2 = vreinterpretq_s16_u16 (vsubl_u8 (d2, c2)); - int16x8_t t3 = vreinterpretq_s16_u16 (vsubl_u8 (d3, c3)); - - int16x8_t v0 = vaddq_s16 (vaddq_s16 (t2, t0), vaddq_s16 (t1, t1)); - int16x8_t v1 = vaddq_s16 (vaddq_s16 (t3, t1), vaddq_s16 (t2, t2)); - - - uint8x8_t v0_u8 = vqmovun_s16 (vaddq_s16 (v0, ftz)); - uint8x8_t v1_u8 = vqmovun_s16 (vaddq_s16 (v1, ftz)); - v0_u8 = vmin_u8 (v0_u8, ftz2); - v1_u8 = vmin_u8 (v1_u8, ftz2); - vqmovun_s16 (vaddq_s16 (v1, ftz)); - - vst1_u8 (dptr0 + x, v0_u8); - vst1_u8 (dptr1 + x, v1_u8); - } -#elif CV_SSE2 +#if CV_SIMD128 if( useSIMD ) { - __m128i z = _mm_setzero_si128(), ftz = _mm_set1_epi16((short)ftzero), - ftz2 = _mm_set1_epi8(cv::saturate_cast(ftzero*2)); - for( ; x <= size.width-9; x += 8 ) + v_int16x8 ftz = v_setall_s16((short) ftzero); + v_int16x8 ftz2 = v_setall_s16((short)(ftzero*2)); + v_int16x8 z = v_setzero_s16(); + + for(; x <= size.width-8; x += 8 ) { - __m128i c0 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow0 + x - 1)), z); - __m128i c1 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow1 + x - 1)), z); - __m128i d0 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow0 + x + 1)), z); - __m128i d1 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow1 + x + 1)), z); + v_int16x8 s00 = v_reinterpret_as_s16(v_load_expand(srow0 + x + 1)); + v_int16x8 s01 = v_reinterpret_as_s16(v_load_expand(srow0 + x - 1)); + v_int16x8 s10 = v_reinterpret_as_s16(v_load_expand(srow1 + x + 1)); + v_int16x8 s11 = v_reinterpret_as_s16(v_load_expand(srow1 + x - 1)); + v_int16x8 s20 = v_reinterpret_as_s16(v_load_expand(srow2 + x + 1)); + v_int16x8 s21 = v_reinterpret_as_s16(v_load_expand(srow2 + x - 1)); + v_int16x8 s30 = v_reinterpret_as_s16(v_load_expand(srow3 + x + 1)); + v_int16x8 s31 = v_reinterpret_as_s16(v_load_expand(srow3 + x - 1)); - d0 = _mm_sub_epi16(d0, c0); - d1 = _mm_sub_epi16(d1, c1); + v_int16x8 d0 = s00 - s01; + v_int16x8 d1 = s10 - s11; + v_int16x8 d2 = s20 - s21; + v_int16x8 d3 = s30 - s31; - __m128i c2 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow2 + x - 1)), z); - __m128i c3 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow3 + x - 1)), z); - __m128i d2 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow2 + x + 1)), z); - __m128i d3 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow3 + x + 1)), z); + v_uint16x8 v0 = v_reinterpret_as_u16(v_max(v_min(d0 + d1 + d1 + d2 + ftz, ftz2), z)); + v_uint16x8 v1 = v_reinterpret_as_u16(v_max(v_min(d1 + d2 + d2 + d3 + ftz, ftz2), z)); - d2 = _mm_sub_epi16(d2, c2); - d3 = _mm_sub_epi16(d3, c3); - - __m128i v0 = _mm_add_epi16(d0, _mm_add_epi16(d2, _mm_add_epi16(d1, d1))); - __m128i v1 = _mm_add_epi16(d1, _mm_add_epi16(d3, _mm_add_epi16(d2, d2))); - v0 = _mm_packus_epi16(_mm_add_epi16(v0, ftz), _mm_add_epi16(v1, ftz)); - v0 = _mm_min_epu8(v0, ftz2); - - _mm_storel_epi64((__m128i*)(dptr0 + x), v0); - _mm_storel_epi64((__m128i*)(dptr1 + x), _mm_unpackhi_epi64(v0, v0)); + v_pack_store(dptr0 + x, v0); + v_pack_store(dptr1 + x, v1); } } #endif @@ -299,18 +263,18 @@ prefilterXSobel( const Mat& src, Mat& dst, int ftzero ) } } -#if CV_NEON - uint8x16_t val0_16 = vdupq_n_u8 (val0); -#endif - for( ; y < size.height; y++ ) { uchar* dptr = dst.ptr(y); x = 0; - #if CV_NEON - for(; x <= size.width-16; x+=16 ) - vst1q_u8 (dptr + x, val0_16); - #endif +#if CV_SIMD128 + if( useSIMD ) + { + v_uint8x16 val0_16 = v_setall_u8(val0); + for(; x <= size.width-16; x+=16 ) + v_store(dptr + x, val0_16); + } +#endif for(; x < size.width; x++ ) dptr[x] = val0; } @@ -320,8 +284,8 @@ prefilterXSobel( const Mat& src, Mat& dst, int ftzero ) static const int DISPARITY_SHIFT_16S = 4; static const int DISPARITY_SHIFT_32S = 8; -#if CV_SSE2 -static void findStereoCorrespondenceBM_SSE2( const Mat& left, const Mat& right, +#if CV_SIMD128 +static void findStereoCorrespondenceBM_SIMD( const Mat& left, const Mat& right, Mat& disp, Mat& cost, StereoBMParams& state, uchar* buf, int _dy0, int _dy1 ) { @@ -354,7 +318,7 @@ static void findStereoCorrespondenceBM_SSE2( const Mat& left, const Mat& right, int coststep = cost.data ? (int)(cost.step/sizeof(costbuf)) : 0; const int TABSZ = 256; uchar tab[TABSZ]; - const __m128i d0_8 = _mm_setr_epi16(0,1,2,3,4,5,6,7), dd_8 = _mm_set1_epi16(8); + const v_int16x8 d0_8 = v_int16x8(0,1,2,3,4,5,6,7), dd_8 = v_setall_s16(8); sad = (ushort*)alignPtr(buf + sizeof(sad[0]), ALIGN); hsad0 = (ushort*)alignPtr(sad + ndisp + 1 + dy0*ndisp, ALIGN); @@ -377,18 +341,20 @@ static void findStereoCorrespondenceBM_SSE2( const Mat& left, const Mat& right, for( y = -dy0; y < height + dy1; y++, hsad += ndisp, cbuf += ndisp, lptr += sstep, rptr += sstep ) { int lval = lptr[0]; - __m128i lv = _mm_set1_epi8((char)lval), z = _mm_setzero_si128(); + v_uint8x16 lv = v_setall_u8((uchar)lval); for( d = 0; d < ndisp; d += 16 ) { - __m128i rv = _mm_loadu_si128((const __m128i*)(rptr + d)); - __m128i hsad_l = _mm_load_si128((__m128i*)(hsad + d)); - __m128i hsad_h = _mm_load_si128((__m128i*)(hsad + d + 8)); - __m128i diff = _mm_adds_epu8(_mm_subs_epu8(lv, rv), _mm_subs_epu8(rv, lv)); - _mm_store_si128((__m128i*)(cbuf + d), diff); - hsad_l = _mm_add_epi16(hsad_l, _mm_unpacklo_epi8(diff,z)); - hsad_h = _mm_add_epi16(hsad_h, _mm_unpackhi_epi8(diff,z)); - _mm_store_si128((__m128i*)(hsad + d), hsad_l); - _mm_store_si128((__m128i*)(hsad + d + 8), hsad_h); + v_uint8x16 rv = v_load(rptr + d); + v_uint16x8 hsad_l = v_load(hsad + d); + v_uint16x8 hsad_h = v_load(hsad + d + 8); + v_uint8x16 diff = v_absdiff(lv, rv); + v_store(cbuf + d, diff); + v_uint16x8 diff0, diff1; + v_expand(diff, diff0, diff1); + hsad_l += diff0; + hsad_h += diff1; + v_store(hsad + d, hsad_l); + v_store(hsad + d + 8, hsad_h); } htext[y] += tab[lval]; } @@ -419,21 +385,24 @@ static void findStereoCorrespondenceBM_SSE2( const Mat& left, const Mat& right, hsad += ndisp, lptr += sstep, lptr_sub += sstep, rptr += sstep ) { int lval = lptr[0]; - __m128i lv = _mm_set1_epi8((char)lval), z = _mm_setzero_si128(); + v_uint8x16 lv = v_setall_u8((uchar)lval); for( d = 0; d < ndisp; d += 16 ) { - __m128i rv = _mm_loadu_si128((const __m128i*)(rptr + d)); - __m128i hsad_l = _mm_load_si128((__m128i*)(hsad + d)); - __m128i hsad_h = _mm_load_si128((__m128i*)(hsad + d + 8)); - __m128i cbs = _mm_load_si128((const __m128i*)(cbuf_sub + d)); - __m128i diff = _mm_adds_epu8(_mm_subs_epu8(lv, rv), _mm_subs_epu8(rv, lv)); - __m128i diff_h = _mm_sub_epi16(_mm_unpackhi_epi8(diff, z), _mm_unpackhi_epi8(cbs, z)); - _mm_store_si128((__m128i*)(cbuf + d), diff); - diff = _mm_sub_epi16(_mm_unpacklo_epi8(diff, z), _mm_unpacklo_epi8(cbs, z)); - hsad_h = _mm_add_epi16(hsad_h, diff_h); - hsad_l = _mm_add_epi16(hsad_l, diff); - _mm_store_si128((__m128i*)(hsad + d), hsad_l); - _mm_store_si128((__m128i*)(hsad + d + 8), hsad_h); + v_uint8x16 rv = v_load(rptr + d); + v_uint16x8 hsad_l = v_load(hsad + d); + v_uint16x8 hsad_h = v_load(hsad + d + 8); + v_uint8x16 cbs = v_load(cbuf_sub + d); + v_uint8x16 diff = v_absdiff(lv, rv); + v_int16x8 diff_l, diff_h, cbs_l, cbs_h; + v_store(cbuf + d, diff); + v_expand(v_reinterpret_as_s8(diff), diff_l, diff_h); + v_expand(v_reinterpret_as_s8(cbs), cbs_l, cbs_h); + diff_l -= cbs_l; + diff_h -= cbs_h; + hsad_h = v_reinterpret_as_u16(v_reinterpret_as_s16(hsad_h) + diff_h); + hsad_l = v_reinterpret_as_u16(v_reinterpret_as_s16(hsad_l) + diff_l); + v_store(hsad + d, hsad_l); + v_store(hsad + d + 8, hsad_h); } htext[y] += tab[lval] - tab[lptr_sub[0]]; } @@ -450,16 +419,16 @@ static void findStereoCorrespondenceBM_SSE2( const Mat& left, const Mat& right, hsad = hsad0 + (1 - dy0)*ndisp; for( y = 1 - dy0; y < wsz2; y++, hsad += ndisp ) - for( d = 0; d < ndisp; d += 16 ) + for( d = 0; d <= ndisp-16; d += 16 ) { - __m128i s0 = _mm_load_si128((__m128i*)(sad + d)); - __m128i s1 = _mm_load_si128((__m128i*)(sad + d + 8)); - __m128i t0 = _mm_load_si128((__m128i*)(hsad + d)); - __m128i t1 = _mm_load_si128((__m128i*)(hsad + d + 8)); - s0 = _mm_add_epi16(s0, t0); - s1 = _mm_add_epi16(s1, t1); - _mm_store_si128((__m128i*)(sad + d), s0); - _mm_store_si128((__m128i*)(sad + d + 8), s1); + v_uint16x8 s0 = v_load(sad + d); + v_uint16x8 s1 = v_load(sad + d + 8); + v_uint16x8 t0 = v_load(hsad + d); + v_uint16x8 t1 = v_load(hsad + d + 8); + s0 = s0 + t0; + s1 = s1 + t1; + v_store(sad + d, s0); + v_store(sad + d + 8, s1); } int tsum = 0; for( y = -wsz2-1; y < wsz2; y++ ) @@ -471,38 +440,38 @@ static void findStereoCorrespondenceBM_SSE2( const Mat& left, const Mat& right, int minsad = INT_MAX, mind = -1; hsad = hsad0 + MIN(y + wsz2, height+dy1-1)*ndisp; hsad_sub = hsad0 + MAX(y - wsz2 - 1, -dy0)*ndisp; - __m128i minsad8 = _mm_set1_epi16(SHRT_MAX); - __m128i mind8 = _mm_set1_epi16(0), d8 = d0_8, mask; + v_int16x8 minsad8 = v_setall_s16(SHRT_MAX); + v_int16x8 mind8 = v_setall_s16(0), d8 = d0_8; for( d = 0; d < ndisp; d += 16 ) { - __m128i u0 = _mm_load_si128((__m128i*)(hsad_sub + d)); - __m128i u1 = _mm_load_si128((__m128i*)(hsad + d)); + v_int16x8 u0 = v_reinterpret_as_s16(v_load(hsad_sub + d)); + v_int16x8 u1 = v_reinterpret_as_s16(v_load(hsad + d)); - __m128i v0 = _mm_load_si128((__m128i*)(hsad_sub + d + 8)); - __m128i v1 = _mm_load_si128((__m128i*)(hsad + d + 8)); + v_int16x8 v0 = v_reinterpret_as_s16(v_load(hsad_sub + d + 8)); + v_int16x8 v1 = v_reinterpret_as_s16(v_load(hsad + d + 8)); - __m128i usad8 = _mm_load_si128((__m128i*)(sad + d)); - __m128i vsad8 = _mm_load_si128((__m128i*)(sad + d + 8)); + v_int16x8 usad8 = v_reinterpret_as_s16(v_load(sad + d)); + v_int16x8 vsad8 = v_reinterpret_as_s16(v_load(sad + d + 8)); - u1 = _mm_sub_epi16(u1, u0); - v1 = _mm_sub_epi16(v1, v0); - usad8 = _mm_add_epi16(usad8, u1); - vsad8 = _mm_add_epi16(vsad8, v1); + u1 -= u0; + v1 -= v0; + usad8 += u1; + vsad8 += v1; - mask = _mm_cmpgt_epi16(minsad8, usad8); - minsad8 = _mm_min_epi16(minsad8, usad8); - mind8 = _mm_max_epi16(mind8, _mm_and_si128(mask, d8)); + v_int16x8 mask = minsad8 > usad8; + minsad8 = v_min(minsad8, usad8); + mind8 = v_max(mind8, (mask& d8)); - _mm_store_si128((__m128i*)(sad + d), usad8); - _mm_store_si128((__m128i*)(sad + d + 8), vsad8); + v_store(sad + d, v_reinterpret_as_u16(usad8)); + v_store(sad + d + 8, v_reinterpret_as_u16(vsad8)); - mask = _mm_cmpgt_epi16(minsad8, vsad8); - minsad8 = _mm_min_epi16(minsad8, vsad8); + mask = minsad8 > vsad8; + minsad8 = v_min(minsad8, vsad8); - d8 = _mm_add_epi16(d8, dd_8); - mind8 = _mm_max_epi16(mind8, _mm_and_si128(mask, d8)); - d8 = _mm_add_epi16(d8, dd_8); + d8 = d8 + dd_8; + mind8 = v_max(mind8, (mask & d8)); + d8 = d8 + dd_8; } tsum += htext[y + wsz2] - htext[y - wsz2 - 1]; @@ -513,8 +482,8 @@ static void findStereoCorrespondenceBM_SSE2( const Mat& left, const Mat& right, } ushort CV_DECL_ALIGNED(16) minsad_buf[8], mind_buf[8]; - _mm_store_si128((__m128i*)minsad_buf, minsad8); - _mm_store_si128((__m128i*)mind_buf, mind8); + v_store(minsad_buf, v_reinterpret_as_u16(minsad8)); + v_store(mind_buf, v_reinterpret_as_u16(mind8)); for( d = 0; d < 8; d++ ) if(minsad > (int)minsad_buf[d] || (minsad == (int)minsad_buf[d] && mind > mind_buf[d])) { @@ -525,27 +494,27 @@ static void findStereoCorrespondenceBM_SSE2( const Mat& left, const Mat& right, if( uniquenessRatio > 0 ) { int thresh = minsad + (minsad * uniquenessRatio/100); - __m128i thresh4 = _mm_set1_epi32(thresh + 1); - __m128i d1 = _mm_set1_epi32(mind-1), d2 = _mm_set1_epi32(mind+1); - __m128i dd_4 = _mm_set1_epi32(4); - __m128i d4 = _mm_set_epi32(3,2,1,0); - __m128i z = _mm_setzero_si128(); + v_int32x4 thresh4 = v_setall_s32(thresh + 1); + v_int32x4 d1 = v_setall_s32(mind-1), d2 = v_setall_s32(mind+1); + v_int32x4 dd_4 = v_setall_s32(4); + v_int32x4 d4 = v_int32x4(0,1,2,3); + v_int32x4 mask4; for( d = 0; d < ndisp; d += 8 ) { - __m128i usad4 = _mm_loadu_si128((__m128i*)(sad + d)); - __m128i vsad4 = _mm_unpackhi_epi16(usad4, z); - usad4 = _mm_unpacklo_epi16(usad4, z); - mask = _mm_cmpgt_epi32( thresh4, usad4); - mask = _mm_and_si128(mask, _mm_or_si128(_mm_cmpgt_epi32(d1,d4), _mm_cmpgt_epi32(d4,d2))); - if( _mm_movemask_epi8(mask) ) + v_int16x8 sad8 = v_reinterpret_as_s16(v_load(sad + d)); + v_int32x4 sad4_l, sad4_h; + v_expand(sad8, sad4_l, sad4_h); + mask4 = thresh4 > sad4_l; + mask4 = mask4 & ((d1 > d4) | (d4 > d2)); + if( v_signmask(mask4) ) break; - d4 = _mm_add_epi16(d4, dd_4); - mask = _mm_cmpgt_epi32( thresh4, vsad4); - mask = _mm_and_si128(mask, _mm_or_si128(_mm_cmpgt_epi32(d1,d4), _mm_cmpgt_epi32(d4,d2))); - if( _mm_movemask_epi8(mask) ) + d4 += dd_4; + mask4 = thresh4 > sad4_h; + mask4 = mask4 & ((d1 > d4) | (d4 > d2)); + if( v_signmask(mask4) ) break; - d4 = _mm_add_epi16(d4, dd_4); + d4 += dd_4; } if( d < ndisp ) { @@ -570,7 +539,7 @@ static void findStereoCorrespondenceBM_SSE2( const Mat& left, const Mat& right, template static void -findStereoCorrespondenceBM_( const Mat& left, const Mat& right, +findStereoCorrespondenceBM( const Mat& left, const Mat& right, Mat& disp, Mat& cost, const StereoBMParams& state, uchar* buf, int _dy0, int _dy1, const int disp_shift ) { @@ -590,13 +559,12 @@ findStereoCorrespondenceBM_( const Mat& left, const Mat& right, int uniquenessRatio = state.uniquenessRatio; mType FILTERED = (mType)((mindisp - 1) << disp_shift); -#if CV_NEON - CV_Assert (ndisp % 8 == 0); - int32_t d0_4_temp [4]; - for (int i = 0; i < 4; i ++) - d0_4_temp[i] = i; - int32x4_t d0_4 = vld1q_s32 (d0_4_temp); - int32x4_t dd_4 = vdupq_n_s32 (4); +#if CV_SIMD128 + bool useSIMD = hasSIMD128(); + if( useSIMD ) + { + CV_Assert (ndisp % 8 == 0); + } #endif int *sad, *hsad0, *hsad, *hsad_sub, *htext; @@ -633,29 +601,46 @@ findStereoCorrespondenceBM_( const Mat& left, const Mat& right, for( y = -dy0; y < height + dy1; y++, hsad += ndisp, cbuf += ndisp, lptr += sstep, rptr += sstep ) { int lval = lptr[0]; - #if CV_NEON - int16x8_t lv = vdupq_n_s16 ((int16_t)lval); - - for( d = 0; d < ndisp; d += 8 ) + d = 0; +#if CV_SIMD128 + if( useSIMD ) { - int16x8_t rv = vreinterpretq_s16_u16 (vmovl_u8 (vld1_u8 (rptr + d))); - int32x4_t hsad_l = vld1q_s32 (hsad + d); - int32x4_t hsad_h = vld1q_s32 (hsad + d + 4); - int16x8_t diff = vabdq_s16 (lv, rv); - vst1_u8 (cbuf + d, vmovn_u16(vreinterpretq_u16_s16(diff))); - hsad_l = vaddq_s32 (hsad_l, vmovl_s16(vget_low_s16 (diff))); - hsad_h = vaddq_s32 (hsad_h, vmovl_s16(vget_high_s16 (diff))); - vst1q_s32 ((hsad + d), hsad_l); - vst1q_s32 ((hsad + d + 4), hsad_h); + v_uint8x16 lv = v_setall_u8((uchar)lval); + + for( ; d <= ndisp - 16; d += 16 ) + { + v_uint8x16 rv = v_load(rptr + d); + v_int32x4 hsad_0 = v_load(hsad + d); + v_int32x4 hsad_1 = v_load(hsad + d + 4); + v_int32x4 hsad_2 = v_load(hsad + d + 8); + v_int32x4 hsad_3 = v_load(hsad + d + 12); + v_uint8x16 diff = v_absdiff(lv, rv); + v_store(cbuf + d, diff); + + v_uint16x8 diff0, diff1; + v_uint32x4 diff00, diff01, diff10, diff11; + v_expand(diff, diff0, diff1); + v_expand(diff0, diff00, diff01); + v_expand(diff1, diff10, diff11); + + hsad_0 += v_reinterpret_as_s32(diff00); + hsad_1 += v_reinterpret_as_s32(diff01); + hsad_2 += v_reinterpret_as_s32(diff10); + hsad_3 += v_reinterpret_as_s32(diff11); + + v_store(hsad + d, hsad_0); + v_store(hsad + d + 4, hsad_1); + v_store(hsad + d + 8, hsad_2); + v_store(hsad + d + 12, hsad_3); + } } - #else - for( d = 0; d < ndisp; d++ ) +#endif + for( ; d < ndisp; d++ ) { int diff = std::abs(lval - rptr[d]); cbuf[d] = (uchar)diff; hsad[d] = (int)(hsad[d] + diff); } - #endif htext[y] += tab[lval]; } } @@ -685,31 +670,53 @@ findStereoCorrespondenceBM_( const Mat& left, const Mat& right, hsad += ndisp, lptr += sstep, lptr_sub += sstep, rptr += sstep ) { int lval = lptr[0]; - #if CV_NEON - int16x8_t lv = vdupq_n_s16 ((int16_t)lval); - for( d = 0; d < ndisp; d += 8 ) + d = 0; +#if CV_SIMD128 + if( useSIMD ) { - int16x8_t rv = vreinterpretq_s16_u16 (vmovl_u8 (vld1_u8 (rptr + d))); - int32x4_t hsad_l = vld1q_s32 (hsad + d); - int32x4_t hsad_h = vld1q_s32 (hsad + d + 4); - int16x8_t cbs = vreinterpretq_s16_u16 (vmovl_u8 (vld1_u8 (cbuf_sub + d))); - int16x8_t diff = vabdq_s16 (lv, rv); - int32x4_t diff_h = vsubl_s16 (vget_high_s16 (diff), vget_high_s16 (cbs)); - int32x4_t diff_l = vsubl_s16 (vget_low_s16 (diff), vget_low_s16 (cbs)); - vst1_u8 (cbuf + d, vmovn_u16(vreinterpretq_u16_s16(diff))); - hsad_h = vaddq_s32 (hsad_h, diff_h); - hsad_l = vaddq_s32 (hsad_l, diff_l); - vst1q_s32 ((hsad + d), hsad_l); - vst1q_s32 ((hsad + d + 4), hsad_h); + v_uint8x16 lv = v_setall_u8((uchar)lval); + for( ; d <= ndisp - 16; d += 16 ) + { + v_uint8x16 rv = v_load(rptr + d); + v_int32x4 hsad_0 = v_load(hsad + d); + v_int32x4 hsad_1 = v_load(hsad + d + 4); + v_int32x4 hsad_2 = v_load(hsad + d + 8); + v_int32x4 hsad_3 = v_load(hsad + d + 12); + v_uint8x16 cbs = v_load(cbuf_sub + d); + v_uint8x16 diff = v_absdiff(lv, rv); + v_store(cbuf + d, diff); + + v_uint16x8 diff0, diff1, cbs0, cbs1; + v_int32x4 diff00, diff01, diff10, diff11, cbs00, cbs01, cbs10, cbs11; + v_expand(diff, diff0, diff1); + v_expand(cbs, cbs0, cbs1); + v_expand(v_reinterpret_as_s16(diff0), diff00, diff01); + v_expand(v_reinterpret_as_s16(diff1), diff10, diff11); + v_expand(v_reinterpret_as_s16(cbs0), cbs00, cbs01); + v_expand(v_reinterpret_as_s16(cbs1), cbs10, cbs11); + + v_int32x4 diff_0 = diff00 - cbs00; + v_int32x4 diff_1 = diff01 - cbs01; + v_int32x4 diff_2 = diff10 - cbs10; + v_int32x4 diff_3 = diff11 - cbs11; + hsad_0 += diff_0; + hsad_1 += diff_1; + hsad_2 += diff_2; + hsad_3 += diff_3; + + v_store(hsad + d, hsad_0); + v_store(hsad + d + 4, hsad_1); + v_store(hsad + d + 8, hsad_2); + v_store(hsad + d + 12, hsad_3); + } } - #else - for( d = 0; d < ndisp; d++ ) +#endif + for( ; d < ndisp; d++ ) { int diff = std::abs(lval - rptr[d]); cbuf[d] = (uchar)diff; hsad[d] = hsad[d] + diff - cbuf_sub[d]; } - #endif htext[y] += tab[lval] - tab[lptr_sub[0]]; } @@ -726,22 +733,25 @@ findStereoCorrespondenceBM_( const Mat& left, const Mat& right, hsad = hsad0 + (1 - dy0)*ndisp; for( y = 1 - dy0; y < wsz2; y++, hsad += ndisp ) { - #if CV_NEON - for( d = 0; d <= ndisp-8; d += 8 ) + d = 0; +#if CV_SIMD128 + if( useSIMD ) { - int32x4_t s0 = vld1q_s32 (sad + d); - int32x4_t s1 = vld1q_s32 (sad + d + 4); - int32x4_t t0 = vld1q_s32 (hsad + d); - int32x4_t t1 = vld1q_s32 (hsad + d + 4); - s0 = vaddq_s32 (s0, t0); - s1 = vaddq_s32 (s1, t1); - vst1q_s32 (sad + d, s0); - vst1q_s32 (sad + d + 4, s1); + for( d = 0; d <= ndisp-8; d += 8 ) + { + v_int32x4 s0 = v_load(sad + d); + v_int32x4 s1 = v_load(sad + d + 4); + v_int32x4 t0 = v_load(hsad + d); + v_int32x4 t1 = v_load(hsad + d + 4); + s0 += t0; + s1 += t1; + v_store(sad + d, s0); + v_store(sad + d + 4, s1); + } } - #else - for( d = 0; d < ndisp; d++ ) +#endif + for( ; d < ndisp; d++ ) sad[d] = (int)(sad[d] + hsad[d]); - #endif } int tsum = 0; for( y = -wsz2-1; y < wsz2; y++ ) @@ -753,62 +763,55 @@ findStereoCorrespondenceBM_( const Mat& left, const Mat& right, int minsad = INT_MAX, mind = -1; hsad = hsad0 + MIN(y + wsz2, height+dy1-1)*ndisp; hsad_sub = hsad0 + MAX(y - wsz2 - 1, -dy0)*ndisp; - #if CV_NEON - int32x4_t minsad4 = vdupq_n_s32 (INT_MAX); - int32x4_t mind4 = vdupq_n_s32(0), d4 = d0_4; - - for( d = 0; d <= ndisp-8; d += 8 ) + d = 0; +#if CV_SIMD128 + if( useSIMD ) { - int32x4_t u0 = vld1q_s32 (hsad_sub + d); - int32x4_t u1 = vld1q_s32 (hsad + d); + v_int32x4 d0_4 = v_int32x4(0, 1, 2, 3); + v_int32x4 dd_4 = v_setall_s32(4); + v_int32x4 minsad4 = v_setall_s32(INT_MAX); + v_int32x4 mind4 = v_setall_s32(0), d4 = d0_4; - int32x4_t v0 = vld1q_s32 (hsad_sub + d + 4); - int32x4_t v1 = vld1q_s32 (hsad + d + 4); + for( ; d <= ndisp - 8; d += 8 ) + { + v_int32x4 u0 = v_load(hsad_sub + d); + v_int32x4 u1 = v_load(hsad + d); - int32x4_t usad4 = vld1q_s32(sad + d); - int32x4_t vsad4 = vld1q_s32(sad + d + 4); + v_int32x4 v0 = v_load(hsad_sub + d + 4); + v_int32x4 v1 = v_load(hsad + d + 4); - u1 = vsubq_s32 (u1, u0); - v1 = vsubq_s32 (v1, v0); - usad4 = vaddq_s32 (usad4, u1); - vsad4 = vaddq_s32 (vsad4, v1); + v_int32x4 usad4 = v_load(sad + d); + v_int32x4 vsad4 = v_load(sad + d + 4); - uint32x4_t mask = vcgtq_s32 (minsad4, usad4); - minsad4 = vminq_s32 (minsad4, usad4); - mind4 = vbslq_s32(mask, d4, mind4); + u1 -= u0; + v1 -= v0; + usad4 += u1; + vsad4 += v1; - vst1q_s32 (sad + d, usad4); - vst1q_s32 (sad + d + 4, vsad4); - d4 = vaddq_s32 (d4, dd_4); + v_store(sad + d, usad4); + v_store(sad + d + 4, vsad4); - mask = vcgtq_s32 (minsad4, vsad4); - minsad4 = vminq_s32 (minsad4, vsad4); - mind4 = vbslq_s32(mask, d4, mind4); + v_int32x4 mask = minsad4 > usad4; + minsad4 = v_min(minsad4, usad4); + mind4 = v_select(mask, d4, mind4); + d4 += dd_4; - d4 = vaddq_s32 (d4, dd_4); + mask = minsad4 > vsad4; + minsad4 = v_min(minsad4, vsad4); + mind4 = v_select(mask, d4, mind4); + d4 += dd_4; + } + int CV_DECL_ALIGNED(16) minsad_buf[4], mind_buf[4]; + v_store(minsad_buf, minsad4); + v_store(mind_buf, mind4); + if(minsad_buf[0] < minsad || (minsad == minsad_buf[0] && mind_buf[0] < mind)) { minsad = minsad_buf[0]; mind = mind_buf[0]; } + if(minsad_buf[1] < minsad || (minsad == minsad_buf[1] && mind_buf[1] < mind)) { minsad = minsad_buf[1]; mind = mind_buf[1]; } + if(minsad_buf[2] < minsad || (minsad == minsad_buf[2] && mind_buf[2] < mind)) { minsad = minsad_buf[2]; mind = mind_buf[2]; } + if(minsad_buf[3] < minsad || (minsad == minsad_buf[3] && mind_buf[3] < mind)) { minsad = minsad_buf[3]; mind = mind_buf[3]; } } - int32x2_t mind4_h = vget_high_s32 (mind4); - int32x2_t mind4_l = vget_low_s32 (mind4); - int32x2_t minsad4_h = vget_high_s32 (minsad4); - int32x2_t minsad4_l = vget_low_s32 (minsad4); - - uint32x2_t mask = vorr_u32 (vclt_s32 (minsad4_h, minsad4_l), vand_u32 (vceq_s32 (minsad4_h, minsad4_l), vclt_s32 (mind4_h, mind4_l))); - mind4_h = vbsl_s32 (mask, mind4_h, mind4_l); - minsad4_h = vbsl_s32 (mask, minsad4_h, minsad4_l); - - mind4_l = vext_s32 (mind4_h,mind4_h,1); - minsad4_l = vext_s32 (minsad4_h,minsad4_h,1); - - mask = vorr_u32 (vclt_s32 (minsad4_h, minsad4_l), vand_u32 (vceq_s32 (minsad4_h, minsad4_l), vclt_s32 (mind4_h, mind4_l))); - mind4_h = vbsl_s32 (mask, mind4_h, mind4_l); - minsad4_h = vbsl_s32 (mask, minsad4_h, minsad4_l); - - mind = (int) vget_lane_s32 (mind4_h, 0); - minsad = sad[mind]; - - #else - for( d = 0; d < ndisp; d++ ) +#endif + for( ; d < ndisp; d++ ) { int currsad = sad[d] + hsad[d] - hsad_sub[d]; sad[d] = currsad; @@ -818,7 +821,6 @@ findStereoCorrespondenceBM_( const Mat& left, const Mat& right, mind = d; } } - #endif tsum += htext[y + wsz2] - htext[y - wsz2 - 1]; if( tsum < textureThreshold ) @@ -855,19 +857,6 @@ findStereoCorrespondenceBM_( const Mat& left, const Mat& right, } } -static void -findStereoCorrespondenceBM( const Mat& left, const Mat& right, - Mat& disp, Mat& cost, const StereoBMParams& state, - uchar* buf, int _dy0, int _dy1 ) -{ - if(disp.type() == CV_16S) - findStereoCorrespondenceBM_(left, right, disp, cost, state, - buf, _dy0, _dy1, DISPARITY_SHIFT_16S ); - else - findStereoCorrespondenceBM_(left, right, disp, cost, state, - buf, _dy0, _dy1, DISPARITY_SHIFT_32S ); -} - #ifdef HAVE_OPENCL static bool ocl_prefiltering(InputArray left0, InputArray right0, OutputArray left, OutputArray right, StereoBMParams* state) { @@ -972,6 +961,7 @@ struct FindStereoCorrespInvoker : public ParallelLoopBody bool _useShorts, Rect _validDisparityRect, Mat& _slidingSumBuf, Mat& _cost ) { + CV_Assert( _disp.type() == CV_16S || _disp.type() == CV_32S ); left = &_left; right = &_right; disp = &_disp; state = _state; nstripes = _nstripes; stripeBufSize = _stripeBufSize; @@ -979,6 +969,9 @@ struct FindStereoCorrespInvoker : public ParallelLoopBody validDisparityRect = _validDisparityRect; slidingSumBuf = &_slidingSumBuf; cost = &_cost; +#if CV_SIMD128 + useSIMD = hasSIMD128(); +#endif } void operator()( const Range& range ) const @@ -1012,12 +1005,19 @@ struct FindStereoCorrespInvoker : public ParallelLoopBody Mat disp_i = disp->rowRange(row0, row1); Mat cost_i = state->disp12MaxDiff >= 0 ? cost->rowRange(row0, row1) : Mat(); -#if CV_SSE2 - if( useShorts ) - findStereoCorrespondenceBM_SSE2( left_i, right_i, disp_i, cost_i, *state, ptr, row0, rows - row1 ); +#if CV_SIMD128 + if( useSIMD && useShorts ) + { + findStereoCorrespondenceBM_SIMD( left_i, right_i, disp_i, cost_i, *state, ptr, row0, rows - row1 ); + } else #endif - findStereoCorrespondenceBM( left_i, right_i, disp_i, cost_i, *state, ptr, row0, rows - row1 ); + { + if( disp_i.type() == CV_16S ) + findStereoCorrespondenceBM( left_i, right_i, disp_i, cost_i, *state, ptr, row0, rows - row1, DISPARITY_SHIFT_16S ); + else + findStereoCorrespondenceBM( left_i, right_i, disp_i, cost_i, *state, ptr, row0, rows - row1, DISPARITY_SHIFT_32S ); + } if( state->disp12MaxDiff >= 0 ) validateDisparity( disp_i, cost_i, state->minDisparity, state->numDisparities, state->disp12MaxDiff ); @@ -1043,6 +1043,7 @@ protected: size_t stripeBufSize; bool useShorts; Rect validDisparityRect; + bool useSIMD; }; class StereoBMImpl : public StereoBM @@ -1168,12 +1169,7 @@ public: if( params.speckleRange >= 0 && params.speckleWindowSize > 0 ) bufSize2 = width*height*(sizeof(Point_) + sizeof(int) + sizeof(uchar)); -#if CV_SSE2 - bool useShorts = params.preFilterCap <= 31 && params.SADWindowSize <= 21 && checkHardwareSupport(CV_CPU_SSE2); -#else - const bool useShorts = false; -#endif - + bool useShorts = params.preFilterCap <= 31 && params.SADWindowSize <= 21; const double SAD_overhead_coeff = 10.0; double N0 = 8000000 / (useShorts ? 1 : 4); // approx tbb's min number instructions reasonable for one thread double maxStripeSize = std::min(std::max(N0 / (width * ndisp), (wsz-1) * SAD_overhead_coeff), (double)height); From c10283b2b6d01dc477d6b9f8d264c9eb2ebad124 Mon Sep 17 00:00:00 2001 From: Dal Rupnik Date: Tue, 24 Jan 2017 10:26:19 +0100 Subject: [PATCH 333/409] Replaced macro with built-in OpenCV export --- modules/videoio/include/opencv2/videoio/cap_ios.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/videoio/include/opencv2/videoio/cap_ios.h b/modules/videoio/include/opencv2/videoio/cap_ios.h index 93c8a0cbea..0691420cf7 100644 --- a/modules/videoio/include/opencv2/videoio/cap_ios.h +++ b/modules/videoio/include/opencv2/videoio/cap_ios.h @@ -32,8 +32,6 @@ #import #include "opencv2/core.hpp" -#define OPENCV_OBJC_EXPORT __attribute__((visibility("default"))) - //! @addtogroup videoio_ios //! @{ @@ -41,7 +39,7 @@ @class CvAbstractCamera; -OPENCV_OBJC_EXPORT @interface CvAbstractCamera : NSObject +CV_EXPORTS @interface CvAbstractCamera : NSObject { UIDeviceOrientation currentDeviceOrientation; @@ -89,7 +87,7 @@ OPENCV_OBJC_EXPORT @interface CvAbstractCamera : NSObject @class CvVideoCamera; -OPENCV_OBJC_EXPORT @protocol CvVideoCameraDelegate +CV_EXPORTS @protocol CvVideoCameraDelegate #ifdef __cplusplus // delegate method for processing image frames @@ -98,7 +96,7 @@ OPENCV_OBJC_EXPORT @protocol CvVideoCameraDelegate @end -OPENCV_OBJC_EXPORT @interface CvVideoCamera : CvAbstractCamera +CV_EXPORTS @interface CvVideoCamera : CvAbstractCamera { AVCaptureVideoDataOutput *videoDataOutput; @@ -131,14 +129,14 @@ OPENCV_OBJC_EXPORT @interface CvVideoCamera : CvAbstractCamera +CV_EXPORTS @protocol CvPhotoCameraDelegate - (void)photoCamera:(CvPhotoCamera*)photoCamera capturedImage:(UIImage *)image; - (void)photoCameraCancel:(CvPhotoCamera*)photoCamera; @end -OPENCV_OBJC_EXPORT @interface CvPhotoCamera : CvAbstractCamera +CV_EXPORTS @interface CvPhotoCamera : CvAbstractCamera { AVCaptureStillImageOutput *stillImageOutput; } From c6c519166e71f083e8d800df68e2eacb2a8b6c3b Mon Sep 17 00:00:00 2001 From: mshabunin Date: Tue, 24 Jan 2017 13:37:41 +0300 Subject: [PATCH 334/409] Added CV_DEPRECATED macro --- doc/Doxyfile.in | 2 +- modules/core/include/opencv2/core/cvdef.h | 10 +++++++ modules/flann/include/opencv2/flann.hpp | 28 +++++++++---------- modules/flann/include/opencv2/flann/defines.h | 13 --------- .../include/opencv2/flann/flann_base.hpp | 4 +-- modules/flann/include/opencv2/flann/matrix.h | 2 +- 6 files changed, 28 insertions(+), 31 deletions(-) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index b1cb9c9744..9414b66f09 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -251,7 +251,7 @@ PREDEFINED = __cplusplus=1 \ CV_DEFAULT(x)=" = x" \ CV_NEON=1 \ CV_SSE2=1 \ - FLANN_DEPRECATED= + CV_DEPRECATED= EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES TAGFILES = diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index 810ec56ed3..9e2b1ed7fd 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -369,6 +369,16 @@ Cv64suf; # define CV_EXPORTS #endif +#ifndef CV_DEPRECATED +# if defined(__GNUC__) +# define CV_DEPRECATED __attribute__ ((deprecated)) +# elif defined(_MSC_VER) +# define CV_DEPRECATED __declspec(deprecated) +# else +# define CV_DEPRECATED +# endif +#endif + #ifndef CV_EXTERN_C # ifdef __cplusplus # define CV_EXTERN_C extern "C" diff --git a/modules/flann/include/opencv2/flann.hpp b/modules/flann/include/opencv2/flann.hpp index 19a98f19e5..22c6ffcf19 100644 --- a/modules/flann/include/opencv2/flann.hpp +++ b/modules/flann/include/opencv2/flann.hpp @@ -59,7 +59,7 @@ can be found in @cite Muja2009 . namespace cvflann { CV_EXPORTS flann_distance_t flann_distance_type(); - FLANN_DEPRECATED CV_EXPORTS void set_distance_type(flann_distance_t distance_type, int order); + CV_DEPRECATED CV_EXPORTS void set_distance_type(flann_distance_t distance_type, int order); } @@ -230,7 +230,7 @@ public: ::cvflann::IndexParams getParameters() { return nnIndex->getParameters(); } - FLANN_DEPRECATED const ::cvflann::IndexParams* getIndexParameters() { return nnIndex->getIndexParameters(); } + CV_DEPRECATED const ::cvflann::IndexParams* getIndexParameters() { return nnIndex->getIndexParameters(); } private: ::cvflann::Index* nnIndex; @@ -344,7 +344,7 @@ public: typedef typename L2::ElementType ElementType; typedef typename L2::ResultType DistanceType; - FLANN_DEPRECATED Index_(const Mat& dataset, const ::cvflann::IndexParams& params) + CV_DEPRECATED Index_(const Mat& dataset, const ::cvflann::IndexParams& params) { printf("[WARNING] The cv::flann::Index_ class is deperecated, use cv::flann::GenericIndex instead\n"); @@ -368,13 +368,13 @@ public: if (nnIndex_L1) nnIndex_L1->buildIndex(); if (nnIndex_L2) nnIndex_L2->buildIndex(); } - FLANN_DEPRECATED ~Index_() + CV_DEPRECATED ~Index_() { if (nnIndex_L1) delete nnIndex_L1; if (nnIndex_L2) delete nnIndex_L2; } - FLANN_DEPRECATED void knnSearch(const std::vector& query, std::vector& indices, std::vector& dists, int knn, const ::cvflann::SearchParams& searchParams) + CV_DEPRECATED void knnSearch(const std::vector& query, std::vector& indices, std::vector& dists, int knn, const ::cvflann::SearchParams& searchParams) { ::cvflann::Matrix m_query((ElementType*)&query[0], 1, query.size()); ::cvflann::Matrix m_indices(&indices[0], 1, indices.size()); @@ -383,7 +383,7 @@ public: if (nnIndex_L1) nnIndex_L1->knnSearch(m_query,m_indices,m_dists,knn,searchParams); if (nnIndex_L2) nnIndex_L2->knnSearch(m_query,m_indices,m_dists,knn,searchParams); } - FLANN_DEPRECATED void knnSearch(const Mat& queries, Mat& indices, Mat& dists, int knn, const ::cvflann::SearchParams& searchParams) + CV_DEPRECATED void knnSearch(const Mat& queries, Mat& indices, Mat& dists, int knn, const ::cvflann::SearchParams& searchParams) { CV_Assert(queries.type() == CvType::type()); CV_Assert(queries.isContinuous()); @@ -401,7 +401,7 @@ public: if (nnIndex_L2) nnIndex_L2->knnSearch(m_queries,m_indices,m_dists,knn, searchParams); } - FLANN_DEPRECATED int radiusSearch(const std::vector& query, std::vector& indices, std::vector& dists, DistanceType radius, const ::cvflann::SearchParams& searchParams) + CV_DEPRECATED int radiusSearch(const std::vector& query, std::vector& indices, std::vector& dists, DistanceType radius, const ::cvflann::SearchParams& searchParams) { ::cvflann::Matrix m_query((ElementType*)&query[0], 1, query.size()); ::cvflann::Matrix m_indices(&indices[0], 1, indices.size()); @@ -411,7 +411,7 @@ public: if (nnIndex_L2) return nnIndex_L2->radiusSearch(m_query,m_indices,m_dists,radius,searchParams); } - FLANN_DEPRECATED int radiusSearch(const Mat& query, Mat& indices, Mat& dists, DistanceType radius, const ::cvflann::SearchParams& searchParams) + CV_DEPRECATED int radiusSearch(const Mat& query, Mat& indices, Mat& dists, DistanceType radius, const ::cvflann::SearchParams& searchParams) { CV_Assert(query.type() == CvType::type()); CV_Assert(query.isContinuous()); @@ -429,32 +429,32 @@ public: if (nnIndex_L2) return nnIndex_L2->radiusSearch(m_query,m_indices,m_dists,radius,searchParams); } - FLANN_DEPRECATED void save(String filename) + CV_DEPRECATED void save(String filename) { if (nnIndex_L1) nnIndex_L1->save(filename); if (nnIndex_L2) nnIndex_L2->save(filename); } - FLANN_DEPRECATED int veclen() const + CV_DEPRECATED int veclen() const { if (nnIndex_L1) return nnIndex_L1->veclen(); if (nnIndex_L2) return nnIndex_L2->veclen(); } - FLANN_DEPRECATED int size() const + CV_DEPRECATED int size() const { if (nnIndex_L1) return nnIndex_L1->size(); if (nnIndex_L2) return nnIndex_L2->size(); } - FLANN_DEPRECATED ::cvflann::IndexParams getParameters() + CV_DEPRECATED ::cvflann::IndexParams getParameters() { if (nnIndex_L1) return nnIndex_L1->getParameters(); if (nnIndex_L2) return nnIndex_L2->getParameters(); } - FLANN_DEPRECATED const ::cvflann::IndexParams* getIndexParameters() + CV_DEPRECATED const ::cvflann::IndexParams* getIndexParameters() { if (nnIndex_L1) return nnIndex_L1->getIndexParameters(); if (nnIndex_L2) return nnIndex_L2->getIndexParameters(); @@ -505,7 +505,7 @@ int hierarchicalClustering(const Mat& features, Mat& centers, const ::cvflann::K /** @deprecated */ template -FLANN_DEPRECATED int hierarchicalClustering(const Mat& features, Mat& centers, const ::cvflann::KMeansIndexParams& params) +CV_DEPRECATED int hierarchicalClustering(const Mat& features, Mat& centers, const ::cvflann::KMeansIndexParams& params) { printf("[WARNING] cv::flann::hierarchicalClustering is deprecated, use " "cv::flann::hierarchicalClustering instead\n"); diff --git a/modules/flann/include/opencv2/flann/defines.h b/modules/flann/include/opencv2/flann/defines.h index f0264f74e3..cab6ea9c0a 100644 --- a/modules/flann/include/opencv2/flann/defines.h +++ b/modules/flann/include/opencv2/flann/defines.h @@ -50,19 +50,6 @@ #endif -#ifdef FLANN_DEPRECATED -#undef FLANN_DEPRECATED -#endif -#ifdef __GNUC__ -#define FLANN_DEPRECATED __attribute__ ((deprecated)) -#elif defined(_MSC_VER) -#define FLANN_DEPRECATED __declspec(deprecated) -#else -#pragma message("WARNING: You need to implement FLANN_DEPRECATED for this compiler") -#define FLANN_DEPRECATED -#endif - - #undef FLANN_PLATFORM_32_BIT #undef FLANN_PLATFORM_64_BIT #if defined __amd64__ || defined __x86_64__ || defined _WIN64 || defined _M_X64 diff --git a/modules/flann/include/opencv2/flann/flann_base.hpp b/modules/flann/include/opencv2/flann/flann_base.hpp index 98c33cf6c0..98901afec1 100644 --- a/modules/flann/include/opencv2/flann/flann_base.hpp +++ b/modules/flann/include/opencv2/flann/flann_base.hpp @@ -241,7 +241,7 @@ public: /** * \brief Returns actual index */ - FLANN_DEPRECATED NNIndex* getIndex() + CV_DEPRECATED NNIndex* getIndex() { return nnIndex_; } @@ -250,7 +250,7 @@ public: * \brief Returns index parameters. * \deprecated use getParameters() instead. */ - FLANN_DEPRECATED const IndexParams* getIndexParameters() + CV_DEPRECATED const IndexParams* getIndexParameters() { return &index_params_; } diff --git a/modules/flann/include/opencv2/flann/matrix.h b/modules/flann/include/opencv2/flann/matrix.h index 51b6c6352c..f6092d134e 100644 --- a/modules/flann/include/opencv2/flann/matrix.h +++ b/modules/flann/include/opencv2/flann/matrix.h @@ -66,7 +66,7 @@ public: /** * Convenience function for deallocating the storage data. */ - FLANN_DEPRECATED void free() + CV_DEPRECATED void free() { fprintf(stderr, "The cvflann::Matrix::free() method is deprecated " "and it does not do any memory deallocation any more. You are" From 3e708e015c99f7374946c6cd5137509add43fb21 Mon Sep 17 00:00:00 2001 From: Pavel Rojtberg Date: Tue, 24 Jan 2017 15:27:11 +0100 Subject: [PATCH 335/409] solvePnP: use CV_32F for rvec, tvec if all PnP inputs are CV_32F --- modules/calib3d/src/solvepnp.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/calib3d/src/solvepnp.cpp b/modules/calib3d/src/solvepnp.cpp index e205580921..1e9b8ec6e4 100644 --- a/modules/calib3d/src/solvepnp.cpp +++ b/modules/calib3d/src/solvepnp.cpp @@ -77,8 +77,14 @@ bool solvePnP( InputArray _opoints, InputArray _ipoints, } else { - _rvec.create(3, 1, CV_64F); - _tvec.create(3, 1, CV_64F); + int mtype = CV_64F; + // use CV_32F if all PnP inputs are CV_32F and outputs are empty + if (_ipoints.depth() == _cameraMatrix.depth() && _ipoints.depth() == _opoints.depth() && + _rvec.empty() && _tvec.empty()) + mtype = _opoints.depth(); + + _rvec.create(3, 1, mtype); + _tvec.create(3, 1, mtype); } rvec = _rvec.getMat(); tvec = _tvec.getMat(); From 576d43d1c2797ef80e494a27f8518c7855972f95 Mon Sep 17 00:00:00 2001 From: Dal Rupnik Date: Tue, 24 Jan 2017 19:14:50 +0100 Subject: [PATCH 336/409] Fixed build_framework.py script for osx --- platforms/osx/build_framework.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platforms/osx/build_framework.py b/platforms/osx/build_framework.py index 2d39be5f9b..64e73c5490 100644 --- a/platforms/osx/build_framework.py +++ b/platforms/osx/build_framework.py @@ -15,10 +15,10 @@ class OSXBuilder(Builder): def getToolchain(self, arch, target): return None - def getBuildCommand(self, arch, target): + def getBuildCommand(self, archs, target): buildcmd = [ "xcodebuild", - "ARCHS=%s" % arch, + "ARCHS=%s" % archs[0], "-sdk", target.lower(), "-configuration", "Release", "-parallelizeTargets", @@ -39,8 +39,8 @@ if __name__ == "__main__": parser.add_argument('--without', metavar='MODULE', default=[], action='append', help='OpenCV modules to exclude from the framework') args = parser.parse_args() - b = OSXBuilder(args.opencv, args.contrib, args.without, + b = OSXBuilder(args.opencv, args.contrib, False, False, args.without, [ - ("x86_64", "MacOSX") + (["x86_64"], "MacOSX") ]) b.build(args.out) From 6eed6a8e9f274ab5ef484bf7ed10f141a9705f6f Mon Sep 17 00:00:00 2001 From: catree Date: Tue, 24 Jan 2017 21:05:05 +0100 Subject: [PATCH 337/409] Add thrust tutorial in the GPU main page documentation. --- .../gpu/gpu-thrust-interop/gpu_thrust_interop.markdown | 4 ++-- doc/tutorials/gpu/table_of_content_gpu.markdown | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown b/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown index 64f763bd59..0332808cbb 100644 --- a/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown +++ b/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown @@ -1,4 +1,4 @@ -Using a cv::cuda::GpuMat with thrust +Using a cv::cuda::GpuMat with thrust {#tutorial_gpu_thrust_interop} =========================================== Goal @@ -67,4 +67,4 @@ Next we will determine how many values are greater than 0 by using thrust::count @snippet samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu pred_greater We will use those results to create an output buffer for storing the copied values, we will then use copy_if with the same predicate to populate the output buffer. -Lastly we will download the values into a CPU mat for viewing. \ No newline at end of file +Lastly we will download the values into a CPU mat for viewing. diff --git a/doc/tutorials/gpu/table_of_content_gpu.markdown b/doc/tutorials/gpu/table_of_content_gpu.markdown index fe4e2c8801..163f5e3b3f 100644 --- a/doc/tutorials/gpu/table_of_content_gpu.markdown +++ b/doc/tutorials/gpu/table_of_content_gpu.markdown @@ -13,3 +13,10 @@ run the OpenCV algorithms. This will give a good grasp on how to approach coding on the GPU module, once you already know how to handle the other modules. As a test case it will port the similarity methods from the tutorial @ref tutorial_video_input_psnr_ssim to the GPU. + +- @subpage tutorial_gpu_thrust_interop + + *Compatibility:* \>= OpenCV 3.0 + + This tutorial will show you how to wrap a GpuMat into a thrust iterator in order to be able to + use the functions in the thrust library. From ddca982c09ebf494e881518fd867b1cebdbe5b0f Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Wed, 25 Jan 2017 22:05:53 +0900 Subject: [PATCH 338/409] remove pure SSE part --- modules/imgproc/src/spatialgradient.cpp | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/modules/imgproc/src/spatialgradient.cpp b/modules/imgproc/src/spatialgradient.cpp index 9217558181..a84bd704eb 100644 --- a/modules/imgproc/src/spatialgradient.cpp +++ b/modules/imgproc/src/spatialgradient.cpp @@ -129,7 +129,7 @@ void spatialGradient( InputArray _src, OutputArray _dx, OutputArray _dy, int i_start = 0; int j_start = 0; -#if CV_SIMD128 && CV_SSE2 +#if CV_SIMD128 if(hasSIMD128()) { uchar *m_src; @@ -160,18 +160,13 @@ void spatialGradient( InputArray _src, OutputArray _dx, OutputArray _dy, n_dx = dx.ptr(i+1); n_dy = dy.ptr(i+1); - v_uint8x16 v_select_m = v_uint8x16(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0xFF); - // Process rest of columns 16-column chunks at a time for ( j = 1; j < W - 16; j += 16 ) { // Load top row for 3x3 Sobel filter v_uint8x16 v_um = v_load(&p_src[j-1]); + v_uint8x16 v_un = v_load(&p_src[j]); v_uint8x16 v_up = v_load(&p_src[j+1]); - // TODO: Replace _mm_slli_si128 with hal method - v_uint8x16 v_un = v_select(v_select_m, v_uint8x16(_mm_slli_si128(v_up.val, 1)), - v_uint8x16(_mm_srli_si128(v_um.val, 1))); v_uint16x8 v_um1, v_um2, v_un1, v_un2, v_up1, v_up2; v_expand(v_um, v_um1, v_um2); v_expand(v_un, v_un1, v_un2); @@ -185,10 +180,8 @@ void spatialGradient( InputArray _src, OutputArray _dx, OutputArray _dy, // Load second row for 3x3 Sobel filter v_um = v_load(&c_src[j-1]); + v_un = v_load(&c_src[j]); v_up = v_load(&c_src[j+1]); - // TODO: Replace _mm_slli_si128 with hal method - v_un = v_select(v_select_m, v_uint8x16(_mm_slli_si128(v_up.val, 1)), - v_uint8x16(_mm_srli_si128(v_um.val, 1))); v_expand(v_um, v_um1, v_um2); v_expand(v_un, v_un1, v_un2); v_expand(v_up, v_up1, v_up2); @@ -201,10 +194,8 @@ void spatialGradient( InputArray _src, OutputArray _dx, OutputArray _dy, // Load third row for 3x3 Sobel filter v_um = v_load(&n_src[j-1]); + v_un = v_load(&n_src[j]); v_up = v_load(&n_src[j+1]); - // TODO: Replace _mm_slli_si128 with hal method - v_un = v_select(v_select_m, v_uint8x16(_mm_slli_si128(v_up.val, 1)), - v_uint8x16(_mm_srli_si128(v_um.val, 1))); v_expand(v_um, v_um1, v_um2); v_expand(v_un, v_un1, v_un2); v_expand(v_up, v_up1, v_up2); @@ -236,10 +227,8 @@ void spatialGradient( InputArray _src, OutputArray _dx, OutputArray _dy, // Load fourth row for 3x3 Sobel filter v_um = v_load(&m_src[j-1]); + v_un = v_load(&m_src[j]); v_up = v_load(&m_src[j+1]); - // TODO: Replace _mm_slli_si128 with hal method - v_un = v_select(v_select_m, v_uint8x16(_mm_slli_si128(v_up.val, 1)), - v_uint8x16(_mm_srli_si128(v_um.val, 1))); v_expand(v_um, v_um1, v_um2); v_expand(v_un, v_un1, v_un2); v_expand(v_up, v_up1, v_up2); From 07979b02c35d15fe3c3145b2c0f79246b55500f6 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Wed, 25 Jan 2017 22:35:11 +0900 Subject: [PATCH 339/409] optimize corner detection series using AVX instructions - make sure SSE version will be still available for backward compatibility --- modules/imgproc/src/corner.cpp | 156 ++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 11 deletions(-) diff --git a/modules/imgproc/src/corner.cpp b/modules/imgproc/src/corner.cpp index dc84897be4..c276e86c5c 100644 --- a/modules/imgproc/src/corner.cpp +++ b/modules/imgproc/src/corner.cpp @@ -48,12 +48,86 @@ namespace cv { +#if CV_AVX +// load three 8-packed float vector and deinterleave +// probably it's better to write down somewhere else +static inline void load_deinterleave(const float* ptr, __m256& a, __m256& b, __m256& c) +{ + __m256 s0 = _mm256_loadu_ps(ptr); // a0, b0, c0, a1, b1, c1, a2, b2, + __m256 s1 = _mm256_loadu_ps(ptr + 8); // c2, a3, b3, c3, a4, b4, c4, a5, + __m256 s2 = _mm256_loadu_ps(ptr + 16); // b5, c5, a6, b6, c6, a7, b7, c7, + __m256 s3 = _mm256_permute2f128_ps(s1, s2, 0x21); // a4, b4, c4, a5, b5, c5, a6, b6, + __m256 s4 = _mm256_permute2f128_ps(s2, s2, 0x33); // c6, a7, b7, c7, c6, a7, b7, c7, + + __m256 v00 = _mm256_unpacklo_ps(s0, s3); // a0, a4, b0, b4, b1, b5, c1, c5, + __m256 v01 = _mm256_unpackhi_ps(s0, s3); // c0, c4, a1, a5, a2, a6, b2, b6, + __m256 v02 = _mm256_unpacklo_ps(s1, s4); // c2, c6, a3, a7, x, x, x, x, + __m256 v03 = _mm256_unpackhi_ps(s1, s4); // b3, b7, c3, c7, x, x, x, x, + __m256 v04 = _mm256_permute2f128_ps(v02, v03, 0x20); // c2, c6, a3, a7, b3, b7, c3, c7, + __m256 v05 = _mm256_permute2f128_ps(v01, v03, 0x21); // a2, a6, b2, b6, b3, b7, c3, c7, + + __m256 v10 = _mm256_unpacklo_ps(v00, v05); // a0, a2, a4, a6, b1, b3, b5, b7, + __m256 v11 = _mm256_unpackhi_ps(v00, v05); // b0, b2, b4, b6, c1, c3, c5, c7, + __m256 v12 = _mm256_unpacklo_ps(v01, v04); // c0, c2, c4, c6, x, x, x, x, + __m256 v13 = _mm256_unpackhi_ps(v01, v04); // a1, a3, a5, a7, x, x, x, x, + __m256 v14 = _mm256_permute2f128_ps(v11, v12, 0x20); // b0, b2, b4, b6, c0, c2, c4, c6, + __m256 v15 = _mm256_permute2f128_ps(v10, v11, 0x31); // b1, b3, b5, b7, c1, c3, c5, c7, + + __m256 v20 = _mm256_unpacklo_ps(v14, v15); // b0, b1, b2, b3, c0, c1, c2, c3, + __m256 v21 = _mm256_unpackhi_ps(v14, v15); // b4, b5, b6, b7, c4, c5, c6, c7, + __m256 v22 = _mm256_unpacklo_ps(v10, v13); // a0, a1, a2, a3, x, x, x, x, + __m256 v23 = _mm256_unpackhi_ps(v10, v13); // a4, a5, a6, a7, x, x, x, x, + + a = _mm256_permute2f128_ps(v22, v23, 0x20); // a0, a1, a2, a3, a4, a5, a6, a7, + b = _mm256_permute2f128_ps(v20, v21, 0x20); // b0, b1, b2, b3, b4, b5, b6, b7, + c = _mm256_permute2f128_ps(v20, v21, 0x31); // c0, c1, c2, c3, c4, c5, c6, c7, +} + +// realign four 3-packed vector to three 4-packed vector +static inline void v_pack4x3to3x4(const __m128i& s0, const __m128i& s1, const __m128i& s2, const __m128i& s3, __m128i& d0, __m128i& d1, __m128i& d2) +{ + d0 = _mm_or_si128(s0, _mm_slli_si128(s1, 12)); + d1 = _mm_or_si128(_mm_srli_si128(s1, 4), _mm_slli_si128(s2, 8)); + d2 = _mm_or_si128(_mm_srli_si128(s2, 8), _mm_slli_si128(s3, 4)); +} + +// separate high and low 128 bit and cast to __m128i +static inline void v_separate_lo_hi(const __m256& src, __m128i& lo, __m128i& hi) +{ + lo = _mm_castps_si128(_mm256_castps256_ps128(src)); + hi = _mm_castps_si128(_mm256_extractf128_ps(src, 1)); +} + +// interleave three 8-float vector and store +static inline void store_interleave(float* ptr, const __m256& a, const __m256& b, const __m256& c) +{ + __m128i a0, a1, b0, b1, c0, c1; + v_separate_lo_hi(a, a0, a1); + v_separate_lo_hi(b, b0, b1); + v_separate_lo_hi(c, c0, c1); + + v_uint32x4 z = v_setzero_u32(); + v_uint32x4 u0, u1, u2, u3; + v_transpose4x4(v_uint32x4(a0), v_uint32x4(b0), v_uint32x4(c0), z, u0, u1, u2, u3); + v_pack4x3to3x4(u0.val, u1.val, u2.val, u3.val, a0, b0, c0); + v_transpose4x4(v_uint32x4(a1), v_uint32x4(b1), v_uint32x4(c1), z, u0, u1, u2, u3); + v_pack4x3to3x4(u0.val, u1.val, u2.val, u3.val, a1, b1, c1); + + _mm256_storeu_ps(ptr, _mm256_setr_m128(_mm_castsi128_ps(a0), _mm_castsi128_ps(b0))); + _mm256_storeu_ps(ptr + 8, _mm256_setr_m128(_mm_castsi128_ps(c0), _mm_castsi128_ps(a1))); + _mm256_storeu_ps(ptr + 16, _mm256_setr_m128(_mm_castsi128_ps(b1), _mm_castsi128_ps(c1))); +} +#endif // CV_AVX + static void calcMinEigenVal( const Mat& _cov, Mat& _dst ) { int i, j; Size size = _cov.size(); +#if CV_AVX + bool haveAvx = checkHardwareSupport(CV_CPU_AVX); +#endif #if CV_SIMD128 - bool simd = hasSIMD128(); + bool haveSimd = hasSIMD128(); #endif if( _cov.isContinuous() && _dst.isContinuous() ) @@ -67,8 +141,25 @@ static void calcMinEigenVal( const Mat& _cov, Mat& _dst ) const float* cov = _cov.ptr(i); float* dst = _dst.ptr(i); j = 0; - #if CV_SIMD128 - if( simd ) +#if CV_AVX + if( haveAvx ) + { + __m256 half = _mm256_set1_ps(0.5f); + for( ; j <= size.width - 8; j += 8 ) + { + __m256 v_a, v_b, v_c, v_t; + load_deinterleave(cov + j*3, v_a, v_b, v_c); + v_a = _mm256_mul_ps(v_a, half); + v_c = _mm256_mul_ps(v_c, half); + v_t = _mm256_sub_ps(v_a, v_c); + v_t = _mm256_add_ps(_mm256_mul_ps(v_b, v_b), _mm256_mul_ps(v_t, v_t)); + _mm256_storeu_ps(dst + j, _mm256_sub_ps(_mm256_add_ps(v_a, v_c), _mm256_sqrt_ps(v_t))); + } + } +#endif // CV_AVX + +#if CV_SIMD128 + if( haveSimd ) { v_float32x4 half = v_setall_f32(0.5f); for( ; j <= size.width - v_float32x4::nlanes; j += v_float32x4::nlanes ) @@ -82,7 +173,8 @@ static void calcMinEigenVal( const Mat& _cov, Mat& _dst ) v_store(dst + j, (v_a + v_c) - v_sqrt(v_t)); } } - #endif +#endif // CV_SIMD128 + for( ; j < size.width; j++ ) { float a = cov[j*3]*0.5f; @@ -98,8 +190,11 @@ static void calcHarris( const Mat& _cov, Mat& _dst, double k ) { int i, j; Size size = _cov.size(); +#if CV_AVX + bool haveAvx = checkHardwareSupport(CV_CPU_AVX); +#endif #if CV_SIMD128 - bool simd = hasSIMD128(); + bool haveSimd = hasSIMD128(); #endif if( _cov.isContinuous() && _dst.isContinuous() ) @@ -114,8 +209,26 @@ static void calcHarris( const Mat& _cov, Mat& _dst, double k ) float* dst = _dst.ptr(i); j = 0; - #if CV_SIMD128 - if( simd ) +#if CV_AVX + if( haveAvx ) + { + __m256 v_k = _mm256_set1_ps((float)k); + + for( ; j <= size.width - 8; j += 8 ) + { + __m256 v_a, v_b, v_c; + load_deinterleave(cov + j * 3, v_a, v_b, v_c); + + __m256 v_ac_bb = _mm256_sub_ps(_mm256_mul_ps(v_a, v_c), _mm256_mul_ps(v_b, v_b)); + __m256 v_ac = _mm256_add_ps(v_a, v_c); + __m256 v_dst = _mm256_sub_ps(v_ac_bb, _mm256_mul_ps(v_k, _mm256_mul_ps(v_ac, v_ac))); + _mm256_storeu_ps(dst + j, v_dst); + } + } +#endif // CV_AVX + +#if CV_SIMD128 + if( haveSimd ) { v_float32x4 v_k = v_setall_f32((float)k); @@ -130,7 +243,7 @@ static void calcHarris( const Mat& _cov, Mat& _dst, double k ) v_store(dst + j, v_dst); } } - #endif +#endif // CV_SIMD128 for( ; j < size.width; j++ ) { @@ -231,6 +344,9 @@ cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size, if (tegra::useTegra() && tegra::cornerEigenValsVecs(src, eigenv, block_size, aperture_size, op_type, k, borderType)) return; #endif +#if CV_AVX + bool haveAvx = checkHardwareSupport(CV_CPU_AVX); +#endif #if CV_SIMD128 bool haveSimd = hasSIMD128(); #endif @@ -268,8 +384,26 @@ cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size, const float* dydata = Dy.ptr(i); j = 0; - #if CV_SIMD128 - if (haveSimd) +#if CV_AVX + if( haveAvx ) + { + for( ; j <= size.width - 8; j += 8 ) + { + __m256 v_dx = _mm256_loadu_ps(dxdata + j); + __m256 v_dy = _mm256_loadu_ps(dydata + j); + + __m256 v_dst0, v_dst1, v_dst2; + v_dst0 = _mm256_mul_ps(v_dx, v_dx); + v_dst1 = _mm256_mul_ps(v_dx, v_dy); + v_dst2 = _mm256_mul_ps(v_dy, v_dy); + + store_interleave(cov_data + j * 3, v_dst0, v_dst1, v_dst2); + } + } +#endif // CV_AVX + +#if CV_SIMD128 + if( haveSimd ) { for( ; j <= size.width - v_float32x4::nlanes; j += v_float32x4::nlanes ) { @@ -284,7 +418,7 @@ cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size, v_store_interleave(cov_data + j * 3, v_dst0, v_dst1, v_dst2); } } - #endif +#endif // CV_SIMD128 for( ; j < size.width; j++ ) { From c1dec72fb66986441765890078f8050132d38fe0 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 25 Jan 2017 19:42:09 +0300 Subject: [PATCH 340/409] java: force 64F in solvePnP test --- modules/calib3d/misc/java/test/Calib3dTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/calib3d/misc/java/test/Calib3dTest.java b/modules/calib3d/misc/java/test/Calib3dTest.java index add668f190..67193d9586 100644 --- a/modules/calib3d/misc/java/test/Calib3dTest.java +++ b/modules/calib3d/misc/java/test/Calib3dTest.java @@ -499,7 +499,7 @@ public class Calib3dTest extends OpenCVTestCase { } public void testSolvePnPListOfPoint3ListOfPointMatMatMatMat() { - Mat intrinsics = Mat.eye(3, 3, CvType.CV_32F); + Mat intrinsics = Mat.eye(3, 3, CvType.CV_64F); intrinsics.put(0, 0, 400); intrinsics.put(1, 1, 400); intrinsics.put(0, 2, 640 / 2); From 820fdbff2309150eb2b5a328ddac774b865ec8a1 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Thu, 26 Jan 2017 18:37:56 +0900 Subject: [PATCH 341/409] brush up AVX optimization in popcount - make sure SIMD optimization works even when AVX is not available --- modules/core/src/stat.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index 3336152a44..39e0fa7648 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -4261,7 +4261,9 @@ int normHamming(const uchar* a, int n) _r0 = _mm256_add_epi32(_r0, _mm256_shuffle_epi32(_r0, 2)); result = _mm256_extract_epi32_(_mm256_add_epi32(_r0, _mm256_permute2x128_si256(_r0, _r0, 1)), 0); } -#elif CV_POPCNT +#endif // CV_AVX2 + +#if CV_POPCNT if(checkHardwareSupport(CV_CPU_POPCNT)) { # if defined CV_POPCNT_U64 @@ -4275,7 +4277,9 @@ int normHamming(const uchar* a, int n) result += CV_POPCNT_U32(*(uint*)(a + i)); } } -#elif CV_SIMD128 +#endif // CV_POPCNT + +#if CV_SIMD128 if(hasSIMD128()) { v_uint32x4 t = v_setzero_u32(); @@ -4283,9 +4287,10 @@ int normHamming(const uchar* a, int n) { t += v_popcount(v_load(a + i)); } - result = v_reduce_sum(t); + result += v_reduce_sum(t); } -#endif +#endif // CV_SIMD128 + for(; i <= n - 4; i += 4) { result += popCountTable[a[i]] + popCountTable[a[i+1]] + @@ -4327,7 +4332,9 @@ int normHamming(const uchar* a, const uchar* b, int n) _r0 = _mm256_add_epi32(_r0, _mm256_shuffle_epi32(_r0, 2)); result = _mm256_extract_epi32_(_mm256_add_epi32(_r0, _mm256_permute2x128_si256(_r0, _r0, 1)), 0); } -#elif CV_POPCNT +#endif // CV_AVX2 + +#if CV_POPCNT if(checkHardwareSupport(CV_CPU_POPCNT)) { # if defined CV_POPCNT_U64 @@ -4341,7 +4348,9 @@ int normHamming(const uchar* a, const uchar* b, int n) result += CV_POPCNT_U32(*(uint*)(a + i) ^ *(uint*)(b + i)); } } -#elif CV_SIMD128 +#endif // CV_POPCNT + +#if CV_SIMD128 if(hasSIMD128()) { v_uint32x4 t = v_setzero_u32(); @@ -4349,9 +4358,10 @@ int normHamming(const uchar* a, const uchar* b, int n) { t += v_popcount(v_load(a + i) ^ v_load(b + i)); } - result = v_reduce_sum(t); + result += v_reduce_sum(t); } -#endif +#endif // CV_SIMD128 + for(; i <= n - 4; i += 4) { result += popCountTable[a[i] ^ b[i]] + popCountTable[a[i+1] ^ b[i+1]] + From 4e0351bafd701d5f71aab064b87acb20aeafb721 Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Thu, 26 Jan 2017 12:43:41 +0300 Subject: [PATCH 342/409] Clarify docs for MOG2::apply --- .../video/include/opencv2/video/background_segm.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/video/include/opencv2/video/background_segm.hpp b/modules/video/include/opencv2/video/background_segm.hpp index 2952d57c9e..f4c6e4cf0d 100644 --- a/modules/video/include/opencv2/video/background_segm.hpp +++ b/modules/video/include/opencv2/video/background_segm.hpp @@ -195,6 +195,17 @@ public: /** @brief Sets the shadow threshold */ CV_WRAP virtual void setShadowThreshold(double threshold) = 0; + + /** @brief Computes a foreground mask. + + @param image Next video frame. Floating point frame will be used without scaling and should be in range \f$[0,255]\f$. + @param fgmask The output foreground mask as an 8-bit binary image. + @param learningRate The value between 0 and 1 that indicates how fast the background model is + learnt. Negative parameter value makes the algorithm to use some automatically chosen learning + rate. 0 means that the background model is not updated at all, 1 means that the background model + is completely reinitialized from the last frame. + */ + CV_WRAP virtual void apply(InputArray image, OutputArray fgmask, double learningRate=-1) = 0; }; /** @brief Creates MOG2 Background Subtractor From ec47a0a6def8b60a26b6b3697ba3eda65d84ee66 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 26 Jan 2017 18:15:18 +0300 Subject: [PATCH 343/409] build: workaround for missing _mm256_setr_m128 in GCC --- modules/imgproc/src/corner.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/imgproc/src/corner.cpp b/modules/imgproc/src/corner.cpp index c276e86c5c..da2e08ddb6 100644 --- a/modules/imgproc/src/corner.cpp +++ b/modules/imgproc/src/corner.cpp @@ -113,9 +113,16 @@ static inline void store_interleave(float* ptr, const __m256& a, const __m256& b v_transpose4x4(v_uint32x4(a1), v_uint32x4(b1), v_uint32x4(c1), z, u0, u1, u2, u3); v_pack4x3to3x4(u0.val, u1.val, u2.val, u3.val, a1, b1, c1); +#if !defined(__GNUC__) || defined(__INTEL_COMPILER) _mm256_storeu_ps(ptr, _mm256_setr_m128(_mm_castsi128_ps(a0), _mm_castsi128_ps(b0))); _mm256_storeu_ps(ptr + 8, _mm256_setr_m128(_mm_castsi128_ps(c0), _mm_castsi128_ps(a1))); _mm256_storeu_ps(ptr + 16, _mm256_setr_m128(_mm_castsi128_ps(b1), _mm_castsi128_ps(c1))); +#else + // GCC: workaround for missing AVX intrinsic: "_mm256_setr_m128()" + _mm256_storeu_ps(ptr, _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_castsi128_ps(a0)), _mm_castsi128_ps(b0), 1)); + _mm256_storeu_ps(ptr + 8, _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_castsi128_ps(c0)), _mm_castsi128_ps(a1), 1)); + _mm256_storeu_ps(ptr + 16, _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_castsi128_ps(b1)), _mm_castsi128_ps(c1), 1)); +#endif } #endif // CV_AVX From eb768514c3c5e885f7bbe63e065f32b9a863b8d5 Mon Sep 17 00:00:00 2001 From: "PkLab.net" Date: Thu, 26 Jan 2017 17:10:32 +0100 Subject: [PATCH 344/409] Fix misplaced description CAP_PROP_SETTINGS and others minor changes in videoio doc --- modules/videoio/include/opencv2/videoio.hpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index 458e63d843..bb6ad2a508 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -118,6 +118,9 @@ enum VideoCaptureAPIs { }; /** @brief %VideoCapture generic properties identifier. + + Reading / writing properties involves many layers. Some unexpected result might happens along this chain. + Effective behaviour depends from device hardware, driver and API Backend. @sa videoio_flags_others, VideoCapture::get(), VideoCapture::set() */ enum VideoCaptureProperties { @@ -131,12 +134,12 @@ enum VideoCaptureProperties { CAP_PROP_FRAME_COUNT =7, //!< Number of frames in the video file. CAP_PROP_FORMAT =8, //!< Format of the %Mat objects returned by VideoCapture::retrieve(). CAP_PROP_MODE =9, //!< Backend-specific value indicating the current capture mode. - CAP_PROP_BRIGHTNESS =10, //!< Brightness of the image (only for cameras). + CAP_PROP_BRIGHTNESS =10, //!< Brightness of the image (only for those cameras that support). CAP_PROP_CONTRAST =11, //!< Contrast of the image (only for cameras). CAP_PROP_SATURATION =12, //!< Saturation of the image (only for cameras). CAP_PROP_HUE =13, //!< Hue of the image (only for cameras). - CAP_PROP_GAIN =14, //!< Gain of the image (only for cameras). - CAP_PROP_EXPOSURE =15, //!< Exposure (only for cameras). + CAP_PROP_GAIN =14, //!< Gain of the image (only for those cameras that support). + CAP_PROP_EXPOSURE =15, //!< Exposure (only for those cameras that support). CAP_PROP_CONVERT_RGB =16, //!< Boolean flags indicating whether images should be converted to RGB. CAP_PROP_WHITE_BALANCE_BLUE_U =17, //!< Currently unsupported. CAP_PROP_RECTIFICATION =18, //!< Rectification flag for stereo cameras (note: only supported by DC1394 v 2.x backend currently). @@ -157,7 +160,7 @@ enum VideoCaptureProperties { CAP_PROP_TILT =34, CAP_PROP_ROLL =35, CAP_PROP_IRIS =36, - CAP_PROP_SETTINGS =37, //! Pop up video/camera filter dialog (note: only supported by DSHOW backend currently. Property value is ignored) + CAP_PROP_SETTINGS =37, //!< Pop up video/camera filter dialog (note: only supported by DSHOW backend currently. The property value is ignored) CAP_PROP_BUFFERSIZE =38, CAP_PROP_AUTOFOCUS =39 }; @@ -668,9 +671,9 @@ public: @overload - This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts. Parameters are similar as the constructor VideoCapture(int index),except it takes an additional argument apiPreference. - @return open(cameraNum + apiPreference). + Definitely, is same as open(int index) where `index=cameraNum + apiPreference` + @return `true` if the camera has been successfully opened. */ CV_WRAP bool open(int cameraNum, int apiPreference); From d0bdf995011711bf0368a173d3462161bf3b9516 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Fri, 27 Jan 2017 18:42:58 +0900 Subject: [PATCH 345/409] check correct flag --- modules/core/src/precomp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/precomp.hpp b/modules/core/src/precomp.hpp index be30664acc..69446beb2a 100644 --- a/modules/core/src/precomp.hpp +++ b/modules/core/src/precomp.hpp @@ -75,7 +75,7 @@ #include #include -#define USE_SSE2 (cv::checkHardwareSupport(CV_CPU_SSE)) +#define USE_SSE2 (cv::checkHardwareSupport(CV_CPU_SSE2)) #define USE_SSE4_2 (cv::checkHardwareSupport(CV_CPU_SSE4_2)) #define USE_AVX (cv::checkHardwareSupport(CV_CPU_AVX)) #define USE_AVX2 (cv::checkHardwareSupport(CV_CPU_AVX2)) From 67f163892e822085fc69b350ffb2aee706c17108 Mon Sep 17 00:00:00 2001 From: Pavel Rojtberg Date: Fri, 27 Jan 2017 15:51:19 +0100 Subject: [PATCH 346/409] viz: fix compilation - we need the VTK includes before ocv_define_module --- modules/viz/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/viz/CMakeLists.txt b/modules/viz/CMakeLists.txt index a22f52fd87..21cda31d8f 100644 --- a/modules/viz/CMakeLists.txt +++ b/modules/viz/CMakeLists.txt @@ -3,9 +3,8 @@ if(NOT WITH_VTK OR NOT DEFINED HAVE_VTK OR NOT HAVE_VTK) endif() set(the_description "Viz") -ocv_define_module(viz opencv_core WRAP python) - include(${VTK_USE_FILE}) +ocv_define_module(viz opencv_core WRAP python) ocv_target_link_libraries(${the_module} ${VTK_LIBRARIES}) if(APPLE AND BUILD_opencv_viz) From 9408a5ef5dcfa9a216fb1e4eabf357510f72e580 Mon Sep 17 00:00:00 2001 From: Jiri Horner Date: Fri, 27 Jan 2017 16:55:29 +0100 Subject: [PATCH 347/409] fix conversion bug in estimateAffine2D* functions functions support points to have other datatypes than floats. * fix bug in coversion that overwrites src points with dst points --- modules/calib3d/src/ptsetreg.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/calib3d/src/ptsetreg.cpp b/modules/calib3d/src/ptsetreg.cpp index cbf8175d48..ba9b7865ab 100644 --- a/modules/calib3d/src/ptsetreg.cpp +++ b/modules/calib3d/src/ptsetreg.cpp @@ -802,11 +802,11 @@ Mat estimateAffine2D(InputArray _from, InputArray _to, OutputArray _inliers, if (from.type() != CV_32FC2 || to.type() != CV_32FC2) { - Mat tmp; - from.convertTo(tmp, CV_32FC2); - from = tmp; - to.convertTo(tmp, CV_32FC2); - to = tmp; + Mat tmp1, tmp2; + from.convertTo(tmp1, CV_32FC2); + from = tmp1; + to.convertTo(tmp2, CV_32FC2); + to = tmp2; } // convert to N x 1 vectors from = from.reshape(2, count); @@ -869,11 +869,11 @@ Mat estimateAffinePartial2D(InputArray _from, InputArray _to, OutputArray _inlie if (from.type() != CV_32FC2 || to.type() != CV_32FC2) { - Mat tmp; - from.convertTo(tmp, CV_32FC2); - from = tmp; - to.convertTo(tmp, CV_32FC2); - to = tmp; + Mat tmp1, tmp2; + from.convertTo(tmp1, CV_32FC2); + from = tmp1; + to.convertTo(tmp2, CV_32FC2); + to = tmp2; } // convert to N x 1 vectors from = from.reshape(2, count); From 4ee25c7e9587155f8c498f54bbbd0509a404ca1b Mon Sep 17 00:00:00 2001 From: Jiri Horner Date: Fri, 27 Jan 2017 17:52:35 +0100 Subject: [PATCH 348/409] add test for convertions in estimateAffine2D* functions test with integer points to cover conversion bugs. --- .../calib3d/test/test_affine2d_estimator.cpp | 28 +++++++++++++++++++ .../test/test_affine_partial2d_estimator.cpp | 27 ++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/modules/calib3d/test/test_affine2d_estimator.cpp b/modules/calib3d/test/test_affine2d_estimator.cpp index de9f7003e1..2007c497c4 100644 --- a/modules/calib3d/test/test_affine2d_estimator.cpp +++ b/modules/calib3d/test/test_affine2d_estimator.cpp @@ -127,4 +127,32 @@ TEST_P(EstimateAffine2D, testNPoints) } } +// test conversion from other datatypes than float +TEST_P(EstimateAffine2D, testConversion) +{ + Mat aff(2, 3, CV_32S); + cv::randu(aff, 1., 3.); + + std::vector fpts(3); + std::vector tpts(3); + + // setting points that are not in the same line + fpts[0] = Point2f( rngIn(1,2), rngIn(5,6) ); + fpts[1] = Point2f( rngIn(3,4), rngIn(3,4) ); + fpts[2] = Point2f( rngIn(1,2), rngIn(3,4) ); + + transform(fpts, tpts, aff); + + vector inliers; + Mat aff_est = estimateAffine2D(fpts, tpts, inliers, GetParam() /* method */); + + ASSERT_FALSE(aff_est.empty()); + + aff.convertTo(aff, CV_64F); // need to convert before compare + EXPECT_NEAR(0., cvtest::norm(aff_est, aff, NORM_INF), 1e-3); + + // all must be inliers + EXPECT_EQ(countNonZero(inliers), 3); +} + INSTANTIATE_TEST_CASE_P(Calib3d, EstimateAffine2D, Method::all()); diff --git a/modules/calib3d/test/test_affine_partial2d_estimator.cpp b/modules/calib3d/test/test_affine_partial2d_estimator.cpp index dde7d7da1c..a61739a52a 100644 --- a/modules/calib3d/test/test_affine_partial2d_estimator.cpp +++ b/modules/calib3d/test/test_affine_partial2d_estimator.cpp @@ -137,4 +137,31 @@ TEST_P(EstimateAffinePartial2D, testNPoints) } } +// test conversion from other datatypes than float +TEST_P(EstimateAffinePartial2D, testConversion) +{ + Mat aff = rngPartialAffMat(); + aff.convertTo(aff, CV_32S); // convert to int to transform ints properly + + std::vector fpts(3); + std::vector tpts(3); + + fpts[0] = Point2f( rngIn(1,2), rngIn(5,6) ); + fpts[1] = Point2f( rngIn(3,4), rngIn(3,4) ); + fpts[2] = Point2f( rngIn(1,2), rngIn(3,4) ); + + transform(fpts, tpts, aff); + + vector inliers; + Mat aff_est = estimateAffinePartial2D(fpts, tpts, inliers, GetParam() /* method */); + + ASSERT_FALSE(aff_est.empty()); + + aff.convertTo(aff, CV_64F); // need to convert back before compare + EXPECT_NEAR(0., cvtest::norm(aff_est, aff, NORM_INF), 1e-3); + + // all must be inliers + EXPECT_EQ(countNonZero(inliers), 3); +} + INSTANTIATE_TEST_CASE_P(Calib3d, EstimateAffinePartial2D, Method::all()); From d22df8c41ff7fcce665d2359d5e2691d20ba05a9 Mon Sep 17 00:00:00 2001 From: chrizandr Date: Sun, 29 Jan 2017 15:43:01 +0530 Subject: [PATCH 349/409] Add wrappers for load functions for DTrees and Boost classifiers --- modules/ml/include/opencv2/ml.hpp | 22 ++++++++++++++++++++++ modules/ml/src/boost.cpp | 5 +++++ modules/ml/src/tree.cpp | 6 ++++++ 3 files changed, 33 insertions(+) diff --git a/modules/ml/include/opencv2/ml.hpp b/modules/ml/include/opencv2/ml.hpp index 3614a91298..0369272d9e 100644 --- a/modules/ml/include/opencv2/ml.hpp +++ b/modules/ml/include/opencv2/ml.hpp @@ -1115,6 +1115,17 @@ public: file using Algorithm::load\(filename). */ CV_WRAP static Ptr create(); + + /** @brief Loads and creates a serialized DTrees from a file + * + * Use DTree::save to serialize and store an DTree to disk. + * Load the DTree from this file again, by calling this function with the path to the file. + * Optionally specify the node for the file containing the classifier + * + * @param filepath path to serialized DTree + * @param nodeName name of node containing the classifier + */ + CV_WRAP static Ptr load(const String& filepath , const String& nodeName = String()); }; /****************************************************************************************\ @@ -1229,6 +1240,17 @@ public: /** Creates the empty model. Use StatModel::train to train the model, Algorithm::load\(filename) to load the pre-trained model. */ CV_WRAP static Ptr create(); + + /** @brief Loads and creates a serialized Boost from a file + * + * Use Boost::save to serialize and store an RTree to disk. + * Load the Boost from this file again, by calling this function with the path to the file. + * Optionally specify the node for the file containing the classifier + * + * @param filepath path to serialized Boost + * @param nodeName name of node containing the classifier + */ + CV_WRAP static Ptr load(const String& filepath , const String& nodeName = String()); }; /****************************************************************************************\ diff --git a/modules/ml/src/boost.cpp b/modules/ml/src/boost.cpp index 3b6bd7aee8..bc0ba00f90 100644 --- a/modules/ml/src/boost.cpp +++ b/modules/ml/src/boost.cpp @@ -507,6 +507,11 @@ Ptr Boost::create() return makePtr(); } +Ptr Boost::load(const String& filepath, const String& nodeName) +{ + return Algorithm::load(filepath, nodeName); +} + }} /* End of file. */ diff --git a/modules/ml/src/tree.cpp b/modules/ml/src/tree.cpp index c02b7440e7..6e066177bf 100644 --- a/modules/ml/src/tree.cpp +++ b/modules/ml/src/tree.cpp @@ -1941,6 +1941,12 @@ Ptr DTrees::create() return makePtr(); } +Ptr DTrees::load(const String& filepath, const String& nodeName) +{ + return Algorithm::load(filepath, nodeName); +} + + } } From 519fbdb8ab934fa0d99e58cd6cacc92cbd830d5e Mon Sep 17 00:00:00 2001 From: chrizandr Date: Sun, 29 Jan 2017 18:51:55 +0530 Subject: [PATCH 350/409] Wrappers for load methods of EM, LR, SVMSGD and Normal Bayes Classifier --- modules/ml/include/opencv2/ml.hpp | 44 +++++++++++++++++++++++++++++++ modules/ml/src/em.cpp | 5 ++++ modules/ml/src/lr.cpp | 6 +++++ modules/ml/src/nbayes.cpp | 5 ++++ modules/ml/src/svmsgd.cpp | 6 +++++ 5 files changed, 66 insertions(+) diff --git a/modules/ml/include/opencv2/ml.hpp b/modules/ml/include/opencv2/ml.hpp index 3614a91298..2067ac8375 100644 --- a/modules/ml/include/opencv2/ml.hpp +++ b/modules/ml/include/opencv2/ml.hpp @@ -393,6 +393,17 @@ public: /** Creates empty model Use StatModel::train to train the model after creation. */ CV_WRAP static Ptr create(); + + /** @brief Loads and creates a serialized NormalBayesClassifier from a file + * + * Use NormalBayesClassifier::save to serialize and store an NormalBayesClassifier to disk. + * Load the NormalBayesClassifier from this file again, by calling this function with the path to the file. + * Optionally specify the node for the file containing the classifier + * + * @param filepath path to serialized NormalBayesClassifier + * @param nodeName name of node containing the classifier + */ + CV_WRAP static Ptr load(const String& filepath , const String& nodeName = String()); }; /****************************************************************************************\ @@ -927,6 +938,17 @@ public: can use one of the EM::train\* methods or load it from file using Algorithm::load\(filename). */ CV_WRAP static Ptr create(); + + /** @brief Loads and creates a serialized EM from a file + * + * Use EM::save to serialize and store an EM to disk. + * Load the EM from this file again, by calling this function with the path to the file. + * Optionally specify the node for the file containing the classifier + * + * @param filepath path to serialized EM + * @param nodeName name of node containing the classifier + */ + CV_WRAP static Ptr load(const String& filepath , const String& nodeName = String()); }; /****************************************************************************************\ @@ -1512,6 +1534,17 @@ public: Creates Logistic Regression model with parameters given. */ CV_WRAP static Ptr create(); + + /** @brief Loads and creates a serialized LogisticRegression from a file + * + * Use LogisticRegression::save to serialize and store an LogisticRegression to disk. + * Load the LogisticRegression from this file again, by calling this function with the path to the file. + * Optionally specify the node for the file containing the classifier + * + * @param filepath path to serialized LogisticRegression + * @param nodeName name of node containing the classifier + */ + CV_WRAP static Ptr load(const String& filepath , const String& nodeName = String()); }; @@ -1627,6 +1660,17 @@ public: */ CV_WRAP static Ptr create(); + /** @brief Loads and creates a serialized SVMSGD from a file + * + * Use SVMSGD::save to serialize and store an SVMSGD to disk. + * Load the SVMSGD from this file again, by calling this function with the path to the file. + * Optionally specify the node for the file containing the classifier + * + * @param filepath path to serialized SVMSGD + * @param nodeName name of node containing the classifier + */ + CV_WRAP static Ptr load(const String& filepath , const String& nodeName = String()); + /** @brief Function sets optimal parameters values for chosen SVM SGD model. * @param svmsgdType is the type of SVMSGD classifier. * @param marginType is the type of margin constraint. diff --git a/modules/ml/src/em.cpp b/modules/ml/src/em.cpp index 5b833cdae0..40993ff441 100644 --- a/modules/ml/src/em.cpp +++ b/modules/ml/src/em.cpp @@ -845,6 +845,11 @@ Ptr EM::create() return makePtr(); } +Ptr EM::load(const String& filepath, const String& nodeName) +{ + return Algorithm::load(filepath, nodeName); +} + } } // namespace cv diff --git a/modules/ml/src/lr.cpp b/modules/ml/src/lr.cpp index c3c314228b..8198003da5 100644 --- a/modules/ml/src/lr.cpp +++ b/modules/ml/src/lr.cpp @@ -127,6 +127,12 @@ Ptr LogisticRegression::create() return makePtr(); } +Ptr LogisticRegression::load(const String& filepath, const String& nodeName) +{ + return Algorithm::load(filepath, nodeName); +} + + bool LogisticRegressionImpl::train(const Ptr& trainData, int) { // return value diff --git a/modules/ml/src/nbayes.cpp b/modules/ml/src/nbayes.cpp index c46367cefa..ae82a1463f 100644 --- a/modules/ml/src/nbayes.cpp +++ b/modules/ml/src/nbayes.cpp @@ -458,6 +458,11 @@ Ptr NormalBayesClassifier::create() return p; } +Ptr NormalBayesClassifier::load(const String& filepath, const String& nodeName) +{ + return Algorithm::load(filepath, nodeName); +} + } } diff --git a/modules/ml/src/svmsgd.cpp b/modules/ml/src/svmsgd.cpp index 0ef9175dad..4b92079978 100644 --- a/modules/ml/src/svmsgd.cpp +++ b/modules/ml/src/svmsgd.cpp @@ -134,6 +134,12 @@ Ptr SVMSGD::create() return makePtr(); } +Ptr SVMSGD::load(const String& filepath, const String& nodeName) +{ + return Algorithm::load(filepath, nodeName); +} + + void SVMSGDImpl::normalizeSamples(Mat &samples, Mat &average, float &multiplier) { int featuresCount = samples.cols; From a789c085882339fe04862ed66b899b3b81a0e55f Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Mon, 30 Jan 2017 17:11:19 +0300 Subject: [PATCH 351/409] ml::EM: add documentation for predict() method --- modules/ml/include/opencv2/ml.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/ml/include/opencv2/ml.hpp b/modules/ml/include/opencv2/ml.hpp index 2067ac8375..e9182a8352 100644 --- a/modules/ml/include/opencv2/ml.hpp +++ b/modules/ml/include/opencv2/ml.hpp @@ -829,6 +829,15 @@ public: */ CV_WRAP virtual void getCovs(CV_OUT std::vector& covs) const = 0; + /** @brief Returns posterior probabilities for the provided samples + + @param samples The input samples, floating-point matrix + @param results The optional output \f$ nSamples \times nClusters\f$ matrix of results. It contains + posterior probabilities for each sample from the input + @param flags This parameter will be ignored + */ + CV_WRAP virtual float predict( InputArray samples, OutputArray results=noArray(), int flags=0 ) const = 0; + /** @brief Returns a likelihood logarithm value and an index of the most probable mixture component for the given sample. From fd711219a286d2a90690de67f50ea7be11ba9464 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Tue, 31 Jan 2017 05:36:27 +0900 Subject: [PATCH 352/409] use universal intrinsic in VBLAS - brush up v_reduce_sum of SSE version --- .../include/opencv2/core/hal/intrin_sse.hpp | 16 +- modules/core/src/lapack.cpp | 218 +++++++++--------- 2 files changed, 118 insertions(+), 116 deletions(-) diff --git a/modules/core/include/opencv2/core/hal/intrin_sse.hpp b/modules/core/include/opencv2/core/hal/intrin_sse.hpp index 9ff10c9b47..40821e09fe 100644 --- a/modules/core/include/opencv2/core/hal/intrin_sse.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_sse.hpp @@ -1101,6 +1101,15 @@ OPENCV_HAL_IMPL_SSE_REDUCE_OP_8(int16x8, short, max, epi16, (short)-32768) OPENCV_HAL_IMPL_SSE_REDUCE_OP_8(int16x8, short, min, epi16, (short)-32768) OPENCV_HAL_IMPL_SSE_REDUCE_OP_8_SUM(int16x8, short, 16) +#define OPENCV_HAL_IMPL_SSE_REDUCE_OP_4_SUM(_Tpvec, scalartype, regtype, suffix, cast_from, cast_to, extract) \ +inline scalartype v_reduce_sum(const _Tpvec& a) \ +{ \ + regtype val = a.val; \ + val = _mm_add_##suffix(val, cast_to(_mm_srli_si128(cast_from(val), 8))); \ + val = _mm_add_##suffix(val, cast_to(_mm_srli_si128(cast_from(val), 4))); \ + return (scalartype)_mm_cvt##extract(val); \ +} + #define OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(_Tpvec, scalartype, func, scalar_func) \ inline scalartype v_reduce_##func(const _Tpvec& a) \ { \ @@ -1111,13 +1120,14 @@ inline scalartype v_reduce_##func(const _Tpvec& a) \ return scalar_func(s0, s1); \ } -OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_uint32x4, unsigned, sum, OPENCV_HAL_ADD) +OPENCV_HAL_IMPL_SSE_REDUCE_OP_4_SUM(v_uint32x4, unsigned, __m128i, epi32, OPENCV_HAL_NOP, OPENCV_HAL_NOP, si128_si32) +OPENCV_HAL_IMPL_SSE_REDUCE_OP_4_SUM(v_int32x4, int, __m128i, epi32, OPENCV_HAL_NOP, OPENCV_HAL_NOP, si128_si32) +OPENCV_HAL_IMPL_SSE_REDUCE_OP_4_SUM(v_float32x4, float, __m128, ps, _mm_castps_si128, _mm_castsi128_ps, ss_f32) + OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_uint32x4, unsigned, max, std::max) OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_uint32x4, unsigned, min, std::min) -OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_int32x4, int, sum, OPENCV_HAL_ADD) OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_int32x4, int, max, std::max) OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_int32x4, int, min, std::min) -OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_float32x4, float, sum, OPENCV_HAL_ADD) OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_float32x4, float, max, std::max) OPENCV_HAL_IMPL_SSE_REDUCE_OP_4(v_float32x4, float, min, std::min) diff --git a/modules/core/src/lapack.cpp b/modules/core/src/lapack.cpp index a602eecd80..800eb0ec87 100644 --- a/modules/core/src/lapack.cpp +++ b/modules/core/src/lapack.cpp @@ -262,25 +262,21 @@ template struct VBLAS int givensx(T*, T*, int, T, T, T*, T*) const { return 0; } }; -#if CV_SSE2 +#if CV_SIMD128 template<> inline int VBLAS::dot(const float* a, const float* b, int n, float* result) const { if( n < 8 ) return 0; int k = 0; - __m128 s0 = _mm_setzero_ps(), s1 = _mm_setzero_ps(); - for( ; k <= n - 8; k += 8 ) + v_float32x4 s0 = v_setzero_f32(); + for( ; k <= n - v_float32x4::nlanes; k += v_float32x4::nlanes ) { - __m128 a0 = _mm_load_ps(a + k), a1 = _mm_load_ps(a + k + 4); - __m128 b0 = _mm_load_ps(b + k), b1 = _mm_load_ps(b + k + 4); + v_float32x4 a0 = v_load(a + k); + v_float32x4 b0 = v_load(b + k); - s0 = _mm_add_ps(s0, _mm_mul_ps(a0, b0)); - s1 = _mm_add_ps(s1, _mm_mul_ps(a1, b1)); + s0 += a0 * b0; } - s0 = _mm_add_ps(s0, s1); - float sbuf[4]; - _mm_storeu_ps(sbuf, s0); - *result = sbuf[0] + sbuf[1] + sbuf[2] + sbuf[3]; + *result = v_reduce_sum(s0); return k; } @@ -290,15 +286,15 @@ template<> inline int VBLAS::givens(float* a, float* b, int n, float c, f if( n < 4 ) return 0; int k = 0; - __m128 c4 = _mm_set1_ps(c), s4 = _mm_set1_ps(s); - for( ; k <= n - 4; k += 4 ) + v_float32x4 c4 = v_setall_f32(c), s4 = v_setall_f32(s); + for( ; k <= n - v_float32x4::nlanes; k += v_float32x4::nlanes ) { - __m128 a0 = _mm_load_ps(a + k); - __m128 b0 = _mm_load_ps(b + k); - __m128 t0 = _mm_add_ps(_mm_mul_ps(a0, c4), _mm_mul_ps(b0, s4)); - __m128 t1 = _mm_sub_ps(_mm_mul_ps(b0, c4), _mm_mul_ps(a0, s4)); - _mm_store_ps(a + k, t0); - _mm_store_ps(b + k, t1); + v_float32x4 a0 = v_load(a + k); + v_float32x4 b0 = v_load(b + k); + v_float32x4 t0 = (a0 * c4) + (b0 * s4); + v_float32x4 t1 = (b0 * c4) - (a0 * s4); + v_store(a + k, t0); + v_store(b + k, t1); } return k; } @@ -310,45 +306,40 @@ template<> inline int VBLAS::givensx(float* a, float* b, int n, float c, if( n < 4 ) return 0; int k = 0; - __m128 c4 = _mm_set1_ps(c), s4 = _mm_set1_ps(s); - __m128 sa = _mm_setzero_ps(), sb = _mm_setzero_ps(); - for( ; k <= n - 4; k += 4 ) + v_float32x4 c4 = v_setall_f32(c), s4 = v_setall_f32(s); + v_float32x4 sa = v_setzero_f32(), sb = v_setzero_f32(); + for( ; k <= n - v_float32x4::nlanes; k += v_float32x4::nlanes ) { - __m128 a0 = _mm_load_ps(a + k); - __m128 b0 = _mm_load_ps(b + k); - __m128 t0 = _mm_add_ps(_mm_mul_ps(a0, c4), _mm_mul_ps(b0, s4)); - __m128 t1 = _mm_sub_ps(_mm_mul_ps(b0, c4), _mm_mul_ps(a0, s4)); - _mm_store_ps(a + k, t0); - _mm_store_ps(b + k, t1); - sa = _mm_add_ps(sa, _mm_mul_ps(t0, t0)); - sb = _mm_add_ps(sb, _mm_mul_ps(t1, t1)); + v_float32x4 a0 = v_load(a + k); + v_float32x4 b0 = v_load(b + k); + v_float32x4 t0 = (a0 * c4) + (b0 * s4); + v_float32x4 t1 = (b0 * c4) - (a0 * s4); + v_store(a + k, t0); + v_store(b + k, t1); + sa += t0 + t0; + sb += t1 + t1; } - float abuf[4], bbuf[4]; - _mm_storeu_ps(abuf, sa); - _mm_storeu_ps(bbuf, sb); - *anorm = abuf[0] + abuf[1] + abuf[2] + abuf[3]; - *bnorm = bbuf[0] + bbuf[1] + bbuf[2] + bbuf[3]; + *anorm = v_reduce_sum(sa); + *bnorm = v_reduce_sum(sb); return k; } - +#if CV_SIMD128_64F template<> inline int VBLAS::dot(const double* a, const double* b, int n, double* result) const { if( n < 4 ) return 0; int k = 0; - __m128d s0 = _mm_setzero_pd(), s1 = _mm_setzero_pd(); - for( ; k <= n - 4; k += 4 ) + v_float64x2 s0 = v_setzero_f64(); + for( ; k <= n - v_float64x2::nlanes; k += v_float64x2::nlanes ) { - __m128d a0 = _mm_load_pd(a + k), a1 = _mm_load_pd(a + k + 2); - __m128d b0 = _mm_load_pd(b + k), b1 = _mm_load_pd(b + k + 2); + v_float64x2 a0 = v_load(a + k); + v_float64x2 b0 = v_load(b + k); - s0 = _mm_add_pd(s0, _mm_mul_pd(a0, b0)); - s1 = _mm_add_pd(s1, _mm_mul_pd(a1, b1)); + s0 += a0 * b0; } - s0 = _mm_add_pd(s0, s1); double sbuf[2]; - _mm_storeu_pd(sbuf, s0); + v_store(sbuf, s0); *result = sbuf[0] + sbuf[1]; return k; } @@ -357,15 +348,15 @@ template<> inline int VBLAS::dot(const double* a, const double* b, int n template<> inline int VBLAS::givens(double* a, double* b, int n, double c, double s) const { int k = 0; - __m128d c2 = _mm_set1_pd(c), s2 = _mm_set1_pd(s); - for( ; k <= n - 2; k += 2 ) + v_float64x2 c2 = v_setall_f64(c), s2 = v_setall_f64(s); + for( ; k <= n - v_float64x2::nlanes; k += v_float64x2::nlanes ) { - __m128d a0 = _mm_load_pd(a + k); - __m128d b0 = _mm_load_pd(b + k); - __m128d t0 = _mm_add_pd(_mm_mul_pd(a0, c2), _mm_mul_pd(b0, s2)); - __m128d t1 = _mm_sub_pd(_mm_mul_pd(b0, c2), _mm_mul_pd(a0, s2)); - _mm_store_pd(a + k, t0); - _mm_store_pd(b + k, t1); + v_float64x2 a0 = v_load(a + k); + v_float64x2 b0 = v_load(b + k); + v_float64x2 t0 = (a0 * c2) + (b0 * s2); + v_float64x2 t1 = (b0 * c2) - (a0 * s2); + v_store(a + k, t0); + v_store(b + k, t1); } return k; } @@ -375,27 +366,28 @@ template<> inline int VBLAS::givensx(double* a, double* b, int n, double double* anorm, double* bnorm) const { int k = 0; - __m128d c2 = _mm_set1_pd(c), s2 = _mm_set1_pd(s); - __m128d sa = _mm_setzero_pd(), sb = _mm_setzero_pd(); - for( ; k <= n - 2; k += 2 ) + v_float64x2 c2 = v_setall_f64(c), s2 = v_setall_f64(s); + v_float64x2 sa = v_setzero_f64(), sb = v_setzero_f64(); + for( ; k <= n - v_float64x2::nlanes; k += v_float64x2::nlanes ) { - __m128d a0 = _mm_load_pd(a + k); - __m128d b0 = _mm_load_pd(b + k); - __m128d t0 = _mm_add_pd(_mm_mul_pd(a0, c2), _mm_mul_pd(b0, s2)); - __m128d t1 = _mm_sub_pd(_mm_mul_pd(b0, c2), _mm_mul_pd(a0, s2)); - _mm_store_pd(a + k, t0); - _mm_store_pd(b + k, t1); - sa = _mm_add_pd(sa, _mm_mul_pd(t0, t0)); - sb = _mm_add_pd(sb, _mm_mul_pd(t1, t1)); + v_float64x2 a0 = v_load(a + k); + v_float64x2 b0 = v_load(b + k); + v_float64x2 t0 = (a0 * c2) + (b0 * s2); + v_float64x2 t1 = (b0 * c2) - (a0 * s2); + v_store(a + k, t0); + v_store(b + k, t1); + sa += t0 * t0; + sb += t1 * t1; } double abuf[2], bbuf[2]; - _mm_storeu_pd(abuf, sa); - _mm_storeu_pd(bbuf, sb); + v_store(abuf, sa); + v_store(bbuf, sb); *anorm = abuf[0] + abuf[1]; *bnorm = bbuf[0] + bbuf[1]; return k; } -#endif +#endif //CV_SIMD128_64F +#endif //CV_SIMD128 template void JacobiSVDImpl_(_Tp* At, size_t astep, _Tp* _W, _Tp* Vt, size_t vstep, @@ -905,24 +897,24 @@ double cv::invert( InputArray _src, OutputArray _dst, int method ) d = 1./d; #if CV_SSE2 - if(USE_SSE2) - { - __m128 zero = _mm_setzero_ps(); - __m128 t0 = _mm_loadl_pi(zero, (const __m64*)srcdata); //t0 = sf(0,0) sf(0,1) - __m128 t1 = _mm_loadh_pi(zero, (const __m64*)(srcdata+srcstep)); //t1 = sf(1,0) sf(1,1) - __m128 s0 = _mm_or_ps(t0, t1); - __m128 det =_mm_set1_ps((float)d); - s0 = _mm_mul_ps(s0, det); - static const uchar CV_DECL_ALIGNED(16) inv[16] = {0,0,0,0,0,0,0,0x80,0,0,0,0x80,0,0,0,0}; - __m128 pattern = _mm_load_ps((const float*)inv); - s0 = _mm_xor_ps(s0, pattern);//==-1*s0 - s0 = _mm_shuffle_ps(s0, s0, _MM_SHUFFLE(0,2,1,3)); - _mm_storel_pi((__m64*)dstdata, s0); - _mm_storeh_pi((__m64*)((float*)(dstdata+dststep)), s0); - } - else + if(USE_SSE2) + { + __m128 zero = _mm_setzero_ps(); + __m128 t0 = _mm_loadl_pi(zero, (const __m64*)srcdata); //t0 = sf(0,0) sf(0,1) + __m128 t1 = _mm_loadh_pi(zero, (const __m64*)(srcdata+srcstep)); //t1 = sf(1,0) sf(1,1) + __m128 s0 = _mm_or_ps(t0, t1); + __m128 det =_mm_set1_ps((float)d); + s0 = _mm_mul_ps(s0, det); + static const uchar CV_DECL_ALIGNED(16) inv[16] = {0,0,0,0,0,0,0,0x80,0,0,0,0x80,0,0,0,0}; + __m128 pattern = _mm_load_ps((const float*)inv); + s0 = _mm_xor_ps(s0, pattern);//==-1*s0 + s0 = _mm_shuffle_ps(s0, s0, _MM_SHUFFLE(0,2,1,3)); + _mm_storel_pi((__m64*)dstdata, s0); + _mm_storeh_pi((__m64*)((float*)(dstdata+dststep)), s0); + } + else #endif - { + { double t0, t1; t0 = Sf(0,0)*d; t1 = Sf(1,1)*d; @@ -932,7 +924,7 @@ double cv::invert( InputArray _src, OutputArray _dst, int method ) t1 = -Sf(1,0)*d; Df(0,1) = (float)t0; Df(1,0) = (float)t1; - } + } } } @@ -944,38 +936,38 @@ double cv::invert( InputArray _src, OutputArray _dst, int method ) result = true; d = 1./d; #if CV_SSE2 - if(USE_SSE2) - { - __m128d s0 = _mm_loadu_pd((const double*)srcdata); //s0 = sf(0,0) sf(0,1) - __m128d s1 = _mm_loadu_pd ((const double*)(srcdata+srcstep));//s1 = sf(1,0) sf(1,1) - __m128d sm = _mm_unpacklo_pd(s0, _mm_load_sd((const double*)(srcdata+srcstep)+1)); //sm = sf(0,0) sf(1,1) - main diagonal - __m128d ss = _mm_shuffle_pd(s0, s1, _MM_SHUFFLE2(0,1)); //ss = sf(0,1) sf(1,0) - secondary diagonal - __m128d det = _mm_load1_pd((const double*)&d); - sm = _mm_mul_pd(sm, det); + if(USE_SSE2) + { + __m128d s0 = _mm_loadu_pd((const double*)srcdata); //s0 = sf(0,0) sf(0,1) + __m128d s1 = _mm_loadu_pd ((const double*)(srcdata+srcstep));//s1 = sf(1,0) sf(1,1) + __m128d sm = _mm_unpacklo_pd(s0, _mm_load_sd((const double*)(srcdata+srcstep)+1)); //sm = sf(0,0) sf(1,1) - main diagonal + __m128d ss = _mm_shuffle_pd(s0, s1, _MM_SHUFFLE2(0,1)); //ss = sf(0,1) sf(1,0) - secondary diagonal + __m128d det = _mm_load1_pd((const double*)&d); + sm = _mm_mul_pd(sm, det); - static const uchar CV_DECL_ALIGNED(16) inv[8] = {0,0,0,0,0,0,0,0x80}; - __m128d pattern = _mm_load1_pd((double*)inv); - ss = _mm_mul_pd(ss, det); - ss = _mm_xor_pd(ss, pattern);//==-1*ss + static const uchar CV_DECL_ALIGNED(16) inv[8] = {0,0,0,0,0,0,0,0x80}; + __m128d pattern = _mm_load1_pd((double*)inv); + ss = _mm_mul_pd(ss, det); + ss = _mm_xor_pd(ss, pattern);//==-1*ss - s0 = _mm_shuffle_pd(sm, ss, _MM_SHUFFLE2(0,1)); - s1 = _mm_shuffle_pd(ss, sm, _MM_SHUFFLE2(0,1)); - _mm_storeu_pd((double*)dstdata, s0); - _mm_storeu_pd((double*)(dstdata+dststep), s1); - } - else + s0 = _mm_shuffle_pd(sm, ss, _MM_SHUFFLE2(0,1)); + s1 = _mm_shuffle_pd(ss, sm, _MM_SHUFFLE2(0,1)); + _mm_storeu_pd((double*)dstdata, s0); + _mm_storeu_pd((double*)(dstdata+dststep), s1); + } + else #endif - { - double t0, t1; - t0 = Sd(0,0)*d; - t1 = Sd(1,1)*d; - Dd(1,1) = t0; - Dd(0,0) = t1; - t0 = -Sd(0,1)*d; - t1 = -Sd(1,0)*d; - Dd(0,1) = t0; - Dd(1,0) = t1; - } + { + double t0, t1; + t0 = Sd(0,0)*d; + t1 = Sd(1,1)*d; + Dd(1,1) = t0; + Dd(0,0) = t1; + t0 = -Sd(0,1)*d; + t1 = -Sd(1,0)*d; + Dd(0,1) = t0; + Dd(1,0) = t1; + } } } } From b417b4dbee83926d627d5cbbf9472731966232dd Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Tue, 31 Jan 2017 12:05:08 +0300 Subject: [PATCH 353/409] KMeans improvement - fixed returned compactness value - added centers drawing to the example app - added compactness test --- modules/core/src/kmeans.cpp | 31 +++++++++++------- modules/core/test/test_math.cpp | 57 ++++++++++++++++++++++++--------- samples/cpp/kmeans.cpp | 8 ++++- 3 files changed, 68 insertions(+), 28 deletions(-) diff --git a/modules/core/src/kmeans.cpp b/modules/core/src/kmeans.cpp index df017adca3..584efcf334 100644 --- a/modules/core/src/kmeans.cpp +++ b/modules/core/src/kmeans.cpp @@ -165,11 +165,13 @@ public: KMeansDistanceComputer( double *_distances, int *_labels, const Mat& _data, - const Mat& _centers ) + const Mat& _centers, + bool _onlyDistance = false ) : distances(_distances), labels(_labels), data(_data), - centers(_centers) + centers(_centers), + onlyDistance(_onlyDistance) { } @@ -183,6 +185,12 @@ public: for( int i = begin; i(i); + if (onlyDistance) + { + const float* center = centers.ptr(labels[i]); + distances[i] = normL2Sqr(sample, center, dims); + continue; + } int k_best = 0; double min_dist = DBL_MAX; @@ -210,6 +218,7 @@ private: int *labels; const Mat& data; const Mat& centers; + bool onlyDistance; }; } @@ -259,6 +268,7 @@ double cv::kmeans( InputArray _data, int K, Mat centers(K, dims, type), old_centers(K, dims, type), temp(1, dims, type); std::vector counters(K); std::vector _box(dims); + Mat dists(1, N, CV_64F); Vec2f* box = &_box[0]; double best_compactness = DBL_MAX, compactness = 0; RNG& rng = theRNG(); @@ -430,19 +440,16 @@ double cv::kmeans( InputArray _data, int K, } } - if( ++iter == MAX(criteria.maxCount, 2) || max_center_shift <= criteria.epsilon ) - break; + bool isLastIter = (++iter == MAX(criteria.maxCount, 2) || max_center_shift <= criteria.epsilon); // assign labels - Mat dists(1, N, CV_64F); + dists = 0; double* dist = dists.ptr(0); - parallel_for_(Range(0, N), - KMeansDistanceComputer(dist, labels, data, centers)); - compactness = 0; - for( i = 0; i < N; i++ ) - { - compactness += dist[i]; - } + parallel_for_(Range(0, N), KMeansDistanceComputer(dist, labels, data, centers, isLastIter)); + compactness = sum(dists)[0]; + + if (isLastIter) + break; } if( compactness < best_compactness ) diff --git a/modules/core/test/test_math.cpp b/modules/core/test/test_math.cpp index 3870d31cd5..e9fc57ed0b 100644 --- a/modules/core/test/test_math.cpp +++ b/modules/core/test/test_math.cpp @@ -2748,21 +2748,23 @@ public: protected: void run(int inVariant) { + RNG& rng = ts->get_rng(); int i, iter = 0, N = 0, N0 = 0, K = 0, dims = 0; Mat labels; - try + { - RNG& rng = theRNG(); const int MAX_DIM=5; int MAX_POINTS = 100, maxIter = 100; for( iter = 0; iter < maxIter; iter++ ) { ts->update_context(this, iter, true); dims = rng.uniform(inVariant == MAT_1_N_CDIM ? 2 : 1, MAX_DIM+1); - N = rng.uniform(1, MAX_POINTS+1); + N = rng.uniform(2, MAX_POINTS+1); N0 = rng.uniform(1, MAX(N/10, 2)); K = rng.uniform(1, N+1); + Mat centers; + if (inVariant == VECTOR) { dims = 2; @@ -2775,7 +2777,7 @@ protected: data[i] = data0[rng.uniform(0, N0)]; kmeans(data, K, labels, TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 30, 0), - 5, KMEANS_PP_CENTERS); + 5, KMEANS_PP_CENTERS, centers); } else { @@ -2820,28 +2822,24 @@ protected: } kmeans(data, K, labels, TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 30, 0), - 5, KMEANS_PP_CENTERS); + 5, KMEANS_PP_CENTERS, centers); } + ASSERT_EQ(centers.rows, K); + ASSERT_EQ(labels.rows, N); + Mat hist(K, 1, CV_32S, Scalar(0)); for( i = 0; i < N; i++ ) { int l = labels.at(i); - CV_Assert(0 <= l && l < K); + ASSERT_GE(l, 0); + ASSERT_LT(l, K); hist.at(l)++; } for( i = 0; i < K; i++ ) - CV_Assert( hist.at(i) != 0 ); + ASSERT_GT(hist.at(i), 0); } } - catch(...) - { - ts->printf(cvtest::TS::LOG, - "context: iteration=%d, N=%d, N0=%d, K=%d\n", - iter, N, N0, K); - std::cout << labels << std::endl; - ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); - } } }; @@ -2859,6 +2857,35 @@ TEST_P(Core_KMeans_InputVariants, singular) INSTANTIATE_TEST_CASE_P(AllVariants, Core_KMeans_InputVariants, KMeansInputVariant::all()); +TEST(Core_KMeans, compactness) +{ + const int N = 1024; + const int attempts = 4; + const TermCriteria crit = TermCriteria(TermCriteria::COUNT, 5, 0); // low number of iterations + cvtest::TS& ts = *cvtest::TS::ptr(); + for (int K = 1; K <= N; K *= 2) + { + Mat data(N, 1, CV_32FC2); + cvtest::randUni(ts.get_rng(), data, Scalar(-200, -200), Scalar(200, 200)); + Mat labels, centers; + double compactness = kmeans(data, K, labels, crit, attempts, KMEANS_PP_CENTERS, centers); + centers = centers.reshape(2); + EXPECT_EQ(labels.rows, N); + EXPECT_EQ(centers.rows, K); + EXPECT_GE(compactness, 0.0); + double expected = 0.0; + for (int i = 0; i < N; ++i) + { + int l = labels.at(i); + Point2f d = data.at(i) - centers.at(l); + expected += d.x * d.x + d.y * d.y; + } + EXPECT_NEAR(expected, compactness, expected * 1e-8); + if (K == N) + EXPECT_DOUBLE_EQ(compactness, 0.0); + } +} + TEST(CovariationMatrixVectorOfMat, accuracy) { unsigned int col_problem_size = 8, row_problem_size = 8, vector_size = 16; diff --git a/samples/cpp/kmeans.cpp b/samples/cpp/kmeans.cpp index e90ec2c108..eeba97a3ff 100644 --- a/samples/cpp/kmeans.cpp +++ b/samples/cpp/kmeans.cpp @@ -53,7 +53,7 @@ int main( int /*argc*/, char** /*argv*/ ) randShuffle(points, 1, &rng); - kmeans(points, clusterCount, labels, + double compactness = kmeans(points, clusterCount, labels, TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 10, 1.0), 3, KMEANS_PP_CENTERS, centers); @@ -65,6 +65,12 @@ int main( int /*argc*/, char** /*argv*/ ) Point ipt = points.at(i); circle( img, ipt, 2, colorTab[clusterIdx], FILLED, LINE_AA ); } + for (i = 0; i < centers.rows; ++i) + { + Point2f c = centers.at(i); + circle( img, c, 40, colorTab[i], 1, LINE_AA ); + } + cout << "Compactness: " << compactness << endl; imshow("clusters", img); From 50d350bcd1b6b5c2af12dade2a7c2df426b5c621 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Tue, 31 Jan 2017 12:36:26 +0300 Subject: [PATCH 354/409] Enable GNU STL debug mode for debug builds --- CMakeLists.txt | 1 + cmake/OpenCVModule.cmake | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c79ad2be6e..91fad31958 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -295,6 +295,7 @@ OCV_OPTION(OPENCV_WARNINGS_ARE_ERRORS "Treat warnings as errors" OCV_OPTION(ANDROID_EXAMPLES_WITH_LIBS "Build binaries of Android examples with native libraries" OFF IF ANDROID ) OCV_OPTION(ENABLE_IMPL_COLLECTION "Collect implementation data on function call" OFF ) OCV_OPTION(ENABLE_INSTRUMENTATION "Instrument functions to collect calls trace and performance" OFF ) +OCV_OPTION(ENABLE_GNU_STL_DEBUG "Enable GNU STL Debug mode (defines _GLIBCXX_DEBUG)" OFF IF ((NOT CMAKE_VERSION VERSION_LESS "2.8.11") AND CMAKE_COMPILER_IS_GNUCXX) ) OCV_OPTION(GENERATE_ABI_DESCRIPTOR "Generate XML file for abi_compliance_checker tool" OFF IF UNIX) OCV_OPTION(DOWNLOAD_EXTERNAL_TEST_DATA "Download external test data (Python executable and OPENCV_TEST_DATA_PATH environment variable may be required)" OFF ) diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index 6e6fe16c32..ce2bc7e08a 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -858,10 +858,13 @@ macro(_ocv_create_module) if((NOT DEFINED OPENCV_MODULE_TYPE AND BUILD_SHARED_LIBS) OR (DEFINED OPENCV_MODULE_TYPE AND OPENCV_MODULE_TYPE STREQUAL SHARED)) - set_target_properties(${the_module} PROPERTIES COMPILE_DEFINITIONS CVAPI_EXPORTS) set_target_properties(${the_module} PROPERTIES DEFINE_SYMBOL CVAPI_EXPORTS) endif() + if (ENABLE_GNU_STL_DEBUG) + target_compile_definitions(${the_module} PUBLIC _GLIBCXX_DEBUG) + endif() + if(MSVC) if(CMAKE_CROSSCOMPILING) set_target_properties(${the_module} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:secchk") From f3cb5084cf1ede04a287766f539ab00162c0eb68 Mon Sep 17 00:00:00 2001 From: Juha Reunanen Date: Sun, 29 Jan 2017 17:32:40 +0200 Subject: [PATCH 355/409] Fix #8093: CV_DbgAssert that the result of area() fits in the return value --- modules/core/include/opencv2/core/types.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/types.hpp b/modules/core/include/opencv2/core/types.hpp index d5c64ca787..13cbe68981 100644 --- a/modules/core/include/opencv2/core/types.hpp +++ b/modules/core/include/opencv2/core/types.hpp @@ -1592,7 +1592,10 @@ Size_<_Tp>& Size_<_Tp>::operator = (const Size_<_Tp>& sz) template inline _Tp Size_<_Tp>::area() const { - return width * height; + const _Tp result = width * height; + CV_DbgAssert(!std::numeric_limits<_Tp>::is_integer + || width == 0 || result / width == height); // make sure the result fits in the return value + return result; } template static inline @@ -1731,7 +1734,10 @@ Size_<_Tp> Rect_<_Tp>::size() const template inline _Tp Rect_<_Tp>::area() const { - return width * height; + const _Tp result = width * height; + CV_DbgAssert(!std::numeric_limits<_Tp>::is_integer + || width == 0 || result / width == height); // make sure the result fits in the return value + return result; } template template inline From 9228a8bee6a89e5dc6092e6cd7c2c67d0ef9b4e6 Mon Sep 17 00:00:00 2001 From: Pavel Vlasov Date: Tue, 31 Jan 2017 16:39:09 +0300 Subject: [PATCH 356/409] Fixes IPP build on linux; --- cmake/OpenCVFindIPP.cmake | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmake/OpenCVFindIPP.cmake b/cmake/OpenCVFindIPP.cmake index 6913052ff4..ba3f5de118 100644 --- a/cmake/OpenCVFindIPP.cmake +++ b/cmake/OpenCVFindIPP.cmake @@ -182,15 +182,15 @@ macro(ipp_detect_version) if(HAVE_IPP_ICV_ONLY) _ipp_add_library(icv) else() - _ipp_add_library(core) - _ipp_add_library(s) + _ipp_add_library(m) + _ipp_add_library(cv) _ipp_add_library(i) _ipp_add_library(cc) - _ipp_add_library(cv) + _ipp_add_library(s) _ipp_add_library(vm) - _ipp_add_library(m) + _ipp_add_library(core) - if(UNIX) + if(UNIX AND IPP_VERSION_MAJOR LESS 2017) get_filename_component(INTEL_COMPILER_LIBRARY_DIR ${IPP_ROOT_DIR}/../lib REALPATH) if(NOT EXISTS ${INTEL_COMPILER_LIBRARY_DIR}) get_filename_component(INTEL_COMPILER_LIBRARY_DIR ${IPP_ROOT_DIR}/../compiler/lib REALPATH) @@ -223,7 +223,7 @@ macro(ipp_detect_version) _ipp_add_compiler_library(irc) _ipp_add_compiler_library(imf) _ipp_add_compiler_library(svml) - endif(UNIX) + endif() endif() #message(STATUS "IPP libs: ${IPP_LIBRARIES}") From a47b7a34bef422f5d5d33f2d62ece04966fc3a2a Mon Sep 17 00:00:00 2001 From: Pavel Vlasov Date: Wed, 1 Feb 2017 10:29:35 +0300 Subject: [PATCH 357/409] Adds IPP control functions to bindings export --- modules/core/include/opencv2/core/base.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/core/include/opencv2/core/base.hpp b/modules/core/include/opencv2/core/base.hpp index 017b484525..3705ac9eb8 100644 --- a/modules/core/include/opencv2/core/base.hpp +++ b/modules/core/include/opencv2/core/base.hpp @@ -667,13 +667,13 @@ namespace cudev namespace ipp { -CV_EXPORTS int getIppFeatures(); -CV_EXPORTS void setIppStatus(int status, const char * const funcname = NULL, const char * const filename = NULL, +CV_EXPORTS int getIppFeatures(); +CV_EXPORTS void setIppStatus(int status, const char * const funcname = NULL, const char * const filename = NULL, int line = 0); -CV_EXPORTS int getIppStatus(); -CV_EXPORTS String getIppErrorLocation(); -CV_EXPORTS bool useIPP(); -CV_EXPORTS void setUseIPP(bool flag); +CV_EXPORTS int getIppStatus(); +CV_EXPORTS String getIppErrorLocation(); +CV_EXPORTS_W bool useIPP(); +CV_EXPORTS_W void setUseIPP(bool flag); } // ipp From ece3fac7efb07bf01a7875d82be51a69c0d8a1c2 Mon Sep 17 00:00:00 2001 From: PkLab Date: Wed, 1 Feb 2017 15:37:24 +0100 Subject: [PATCH 358/409] Merge pull request #8112 from PkLab:Fix_7898 CoInitialize/CoUninitialize in VFW classes (#8112) --- modules/videoio/src/cap_vfw.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/modules/videoio/src/cap_vfw.cpp b/modules/videoio/src/cap_vfw.cpp index 555e848305..43a977212e 100644 --- a/modules/videoio/src/cap_vfw.cpp +++ b/modules/videoio/src/cap_vfw.cpp @@ -312,8 +312,16 @@ CvCapture* cvCreateFileCapture_VFW (const char* filename) class CvCaptureCAM_VFW : public CvCapture { public: - CvCaptureCAM_VFW() { init(); } - virtual ~CvCaptureCAM_VFW() { close(); } + CvCaptureCAM_VFW() + { + CoInitialize(NULL); + init(); + } + virtual ~CvCaptureCAM_VFW() + { + close(); + CoUninitialize(); + } virtual bool open( int index ); virtual void close(); @@ -673,8 +681,16 @@ CvCapture* cvCreateCameraCapture_VFW( int index ) class CvVideoWriter_VFW : public CvVideoWriter { public: - CvVideoWriter_VFW() { init(); } - virtual ~CvVideoWriter_VFW() { close(); } + CvVideoWriter_VFW() + { + CoInitialize(NULL); + init(); + } + virtual ~CvVideoWriter_VFW() + { + close(); + CoUninitialize(); + } virtual bool open( const char* filename, int fourcc, double fps, CvSize frameSize, bool isColor ); From 9a1835ce6676836ce278d723da4ff55a8f900ff1 Mon Sep 17 00:00:00 2001 From: msk-repo01 Date: Thu, 2 Feb 2017 13:47:09 -0800 Subject: [PATCH 359/409] Fix for opencv/opencv#8105 (compiling with mingw32) fix for opencv/opencv#8105, compilation issue with mingw32 (in google/googletest#721 a similar issue was solved and the reason was described as MinGW defines _CRITICAL_SECTION and _RTL_CRITICAL_SECTION as two separate (equivalent) structs, instead of using typedef) --- modules/ts/include/opencv2/ts/ts_gtest.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/ts/include/opencv2/ts/ts_gtest.h b/modules/ts/include/opencv2/ts/ts_gtest.h index d886c97b6b..2153381ab9 100644 --- a/modules/ts/include/opencv2/ts/ts_gtest.h +++ b/modules/ts/include/opencv2/ts/ts_gtest.h @@ -720,10 +720,16 @@ # include # endif // In order to avoid having to include , use forward declaration -// assuming CRITICAL_SECTION is a typedef of _RTL_CRITICAL_SECTION. -// This assumption is verified by -// WindowsTypesTest.CRITICAL_SECTIONIs_RTL_CRITICAL_SECTION. -struct _RTL_CRITICAL_SECTION; +# if GTEST_OS_WINDOWS_MINGW + // MinGW defined _CRITICAL_SECTION and _RTL_CRITICAL_SECTION as two + // separate (equivalent) structs, instead of using typedef + typedef struct _CRITICAL_SECTION GTEST_CRITICAL_SECTION; +# else + // assuming CRITICAL_SECTION is a typedef of _RTL_CRITICAL_SECTION. + // This assumption is verified by + // WindowsTypesTest.CRITICAL_SECTIONIs_RTL_CRITICAL_SECTION. + typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION; +# endif #else // This assumes that non-Windows OSes provide unistd.h. For OSes where this // is not the case, we need to include headers that provide the functions @@ -3057,7 +3063,7 @@ class GTEST_API_ Mutex { // by the linker. MutexType type_; long critical_section_init_phase_; // NOLINT - _RTL_CRITICAL_SECTION* critical_section_; + GTEST_CRITICAL_SECTION* critical_section_; GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); }; From b6561ce57dab8f03a6ff2c8b631a39216c409e5f Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Fri, 3 Feb 2017 13:54:22 +0300 Subject: [PATCH 360/409] Fixed addressing structure population for integral image output --- 3rdparty/openvx/hal/openvx_hal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/openvx/hal/openvx_hal.cpp b/3rdparty/openvx/hal/openvx_hal.cpp index f7962d1eb5..1e87a9f240 100644 --- a/3rdparty/openvx/hal/openvx_hal.cpp +++ b/3rdparty/openvx/hal/openvx_hal.cpp @@ -1064,7 +1064,7 @@ int ovx_hal_integral(int depth, int sdepth, int, const uchar * a, size_t astep, ia = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U8, ivx::Image::createAddressing(w, h, 1, (vx_int32)astep), const_cast(a)), ib = ivx::Image::createFromHandle(ctx, VX_DF_IMAGE_U32, - ivx::Image::createAddressing(w, h, 1, (vx_int32)bstep), (unsigned int *)(b + bstep + sizeof(unsigned int))); + ivx::Image::createAddressing(w, h, 4, (vx_int32)bstep), (unsigned int *)(b + bstep + sizeof(unsigned int))); ivx::IVX_CHECK_STATUS(vxuIntegralImage(ctx, ia, ib)); memset(b, 0, (w + 1) * sizeof(unsigned int)); b += bstep; From 488eb11ba8088e1a860a90224de60a35f8ce8753 Mon Sep 17 00:00:00 2001 From: LaurentBerger Date: Sat, 4 Feb 2017 11:10:50 +0100 Subject: [PATCH 361/409] Add a link to MatExpr in Detailed Description of Mat --- modules/core/include/opencv2/core/mat.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 39c197ea3a..0953b362b9 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -736,6 +736,8 @@ Finally, there are STL-style iterators that are smart enough to skip gaps betwee @endcode The matrix iterators are random-access iterators, so they can be passed to any STL algorithm, including std::sort(). + +@note Matrix Expressions and arithmetic see MatExpr */ class CV_EXPORTS Mat { From 18dbed0e0dfab9477ed7514eb3e1ff4034743e7f Mon Sep 17 00:00:00 2001 From: atinfinity Date: Sun, 5 Feb 2017 22:10:37 +0900 Subject: [PATCH 362/409] added dense flow sample --- samples/gpu/pyrlk_optical_flow.cpp | 147 ++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 4 deletions(-) diff --git a/samples/gpu/pyrlk_optical_flow.cpp b/samples/gpu/pyrlk_optical_flow.cpp index f13487b622..315301b6e3 100644 --- a/samples/gpu/pyrlk_optical_flow.cpp +++ b/samples/gpu/pyrlk_optical_flow.cpp @@ -2,12 +2,13 @@ #include #include "opencv2/core.hpp" -#include +#include "opencv2/core/utility.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" #include "opencv2/video.hpp" #include "opencv2/cudaoptflow.hpp" #include "opencv2/cudaimgproc.hpp" +#include "opencv2/cudaarithm.hpp" using namespace std; using namespace cv; @@ -66,6 +67,132 @@ static void drawArrows(Mat& frame, const vector& prevPts, const vector< } } +inline bool isFlowCorrect(Point2f u) +{ + return !cvIsNaN(u.x) && !cvIsNaN(u.y) && fabs(u.x) < 1e9 && fabs(u.y) < 1e9; +} + +static Vec3b computeColor(float fx, float fy) +{ + static bool first = true; + + // relative lengths of color transitions: + // these are chosen based on perceptual similarity + // (e.g. one can distinguish more shades between red and yellow + // than between yellow and green) + const int RY = 15; + const int YG = 6; + const int GC = 4; + const int CB = 11; + const int BM = 13; + const int MR = 6; + const int NCOLS = RY + YG + GC + CB + BM + MR; + static Vec3i colorWheel[NCOLS]; + + if (first) + { + int k = 0; + + for (int i = 0; i < RY; ++i, ++k) + colorWheel[k] = Vec3i(255, 255 * i / RY, 0); + + for (int i = 0; i < YG; ++i, ++k) + colorWheel[k] = Vec3i(255 - 255 * i / YG, 255, 0); + + for (int i = 0; i < GC; ++i, ++k) + colorWheel[k] = Vec3i(0, 255, 255 * i / GC); + + for (int i = 0; i < CB; ++i, ++k) + colorWheel[k] = Vec3i(0, 255 - 255 * i / CB, 255); + + for (int i = 0; i < BM; ++i, ++k) + colorWheel[k] = Vec3i(255 * i / BM, 0, 255); + + for (int i = 0; i < MR; ++i, ++k) + colorWheel[k] = Vec3i(255, 0, 255 - 255 * i / MR); + + first = false; + } + + const float rad = sqrt(fx * fx + fy * fy); + const float a = atan2(-fy, -fx) / (float)CV_PI; + + const float fk = (a + 1.0f) / 2.0f * (NCOLS - 1); + const int k0 = static_cast(fk); + const int k1 = (k0 + 1) % NCOLS; + const float f = fk - k0; + + Vec3b pix; + + for (int b = 0; b < 3; b++) + { + const float col0 = colorWheel[k0][b] / 255.0f; + const float col1 = colorWheel[k1][b] / 255.0f; + + float col = (1 - f) * col0 + f * col1; + + if (rad <= 1) + col = 1 - rad * (1 - col); // increase saturation with radius + else + col *= .75; // out of range + + pix[2 - b] = static_cast(255.0 * col); + } + + return pix; +} + +static void drawOpticalFlow(const Mat_& flowx, const Mat_& flowy, Mat& dst, float maxmotion = -1) +{ + dst.create(flowx.size(), CV_8UC3); + dst.setTo(Scalar::all(0)); + + // determine motion range: + float maxrad = maxmotion; + + if (maxmotion <= 0) + { + maxrad = 1; + for (int y = 0; y < flowx.rows; ++y) + { + for (int x = 0; x < flowx.cols; ++x) + { + Point2f u(flowx(y, x), flowy(y, x)); + + if (!isFlowCorrect(u)) + continue; + + maxrad = max(maxrad, sqrt(u.x * u.x + u.y * u.y)); + } + } + } + + for (int y = 0; y < flowx.rows; ++y) + { + for (int x = 0; x < flowx.cols; ++x) + { + Point2f u(flowx(y, x), flowy(y, x)); + + if (isFlowCorrect(u)) + dst.at(y, x) = computeColor(u.x / maxrad, u.y / maxrad); + } + } +} + +static void showFlow(const char* name, const GpuMat& d_flow) +{ + GpuMat planes[2]; + cuda::split(d_flow, planes); + + Mat flowx(planes[0]); + Mat flowy(planes[1]); + + Mat out; + drawOpticalFlow(flowx, flowy, out, 10); + + imshow(name, out); +} + template inline T clamp (T x, T a, T b) { return ((x) > (a) ? ((x) < (b) ? (x) : (b)) : (a)); @@ -148,7 +275,7 @@ int main(int argc, const char* argv[]) // Sparse - Ptr d_pyrLK = cuda::SparsePyrLKOpticalFlow::create( + Ptr d_pyrLK_sparse = cuda::SparsePyrLKOpticalFlow::create( Size(winSize, winSize), maxLevel, iters); GpuMat d_frame0(frame0); @@ -157,7 +284,16 @@ int main(int argc, const char* argv[]) GpuMat d_nextPts; GpuMat d_status; - d_pyrLK->calc(useGray ? d_frame0Gray : d_frame0, useGray ? d_frame1Gray : d_frame1, d_prevPts, d_nextPts, d_status); + d_pyrLK_sparse->calc(useGray ? d_frame0Gray : d_frame0, useGray ? d_frame1Gray : d_frame1, d_prevPts, d_nextPts, d_status); + + // Dense + + Ptr d_pyrLK_dense = cuda::DensePyrLKOpticalFlow::create( + Size(winSize, winSize), maxLevel, iters); + + GpuMat d_flow(frame0.size(), CV_32FC2); + + d_pyrLK_dense->calc(d_frame0Gray, d_frame1Gray, d_flow); // Draw arrows @@ -171,9 +307,12 @@ int main(int argc, const char* argv[]) download(d_status, status); drawArrows(frame0, prevPts, nextPts, status, Scalar(255, 0, 0)); - imshow("PyrLK [Sparse]", frame0); + // Draw flows + + showFlow("PyrLK [Dense] Flow Field", d_flow); + waitKey(); return 0; From 678e114e5fc3fddc23f3ec1619a479895d0707ca Mon Sep 17 00:00:00 2001 From: Tony Theodore Date: Mon, 6 Feb 2017 04:15:10 +1100 Subject: [PATCH 363/409] highgui: use forward slash for gl.h include The backslash causes build failure when cross-compiling with mingw-w64 --- modules/highgui/src/window_w32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp index cadbdf7069..012e4d147a 100644 --- a/modules/highgui/src/window_w32.cpp +++ b/modules/highgui/src/window_w32.cpp @@ -65,7 +65,7 @@ #include #include #include "opencv2/highgui.hpp" -#include +#include #endif static const char* trackbar_text = From bfb18d29a6f0890956c739ae6dd2c6f179a0e60d Mon Sep 17 00:00:00 2001 From: StevenPuttemans Date: Mon, 6 Feb 2017 10:38:03 +0100 Subject: [PATCH 364/409] add explanation to detectMultiScale overload functionality --- modules/objdetect/include/opencv2/objdetect.hpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index cd444d21db..432bee4d03 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -294,7 +294,21 @@ public: Size maxSize=Size() ); /** @overload - if `outputRejectLevels` is `true` returns `rejectLevels` and `levelWeights` + This function allows you to retrieve the final stage decision certainty of classification. + For this, one needs to set `outputRejectLevels` on true and provide the `rejectLevels` and `levelWeights` parameter. + For each resulting detection, `levelWeights` will then contain the certainty of classification at the final stage. + This value can then be used to separate strong from weaker classifications. + + A code sample on how to use it efficiently can be found below: + @code + Mat img; + vector weights; + vector levels; + vector detections; + CascadeClassifier model("/path/to/your/model.xml"); + model.detectMultiScale(img, detections, levels, weights, 1.1, 3, 0, Size(), Size(), true); + cerr << "Detection " << detections[0] << " with weight " << weights[0] << endl; + @endcode */ CV_WRAP_AS(detectMultiScale3) void detectMultiScale( InputArray image, CV_OUT std::vector& objects, From 88f5e2f3f7b84edeac21dfdb96c96d026888268d Mon Sep 17 00:00:00 2001 From: chrizandr Date: Mon, 6 Feb 2017 16:05:48 +0530 Subject: [PATCH 365/409] Corrected typo in py_histogram_begins tutorial --- .../py_histogram_begins/py_histogram_begins.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/py_tutorials/py_imgproc/py_histograms/py_histogram_begins/py_histogram_begins.markdown b/doc/py_tutorials/py_imgproc/py_histograms/py_histogram_begins/py_histogram_begins.markdown index 540b0906ec..7d7eaac622 100644 --- a/doc/py_tutorials/py_imgproc/py_histograms/py_histogram_begins/py_histogram_begins.markdown +++ b/doc/py_tutorials/py_imgproc/py_histograms/py_histogram_begins/py_histogram_begins.markdown @@ -46,7 +46,7 @@ histograms](http://docs.opencv.org/doc/tutorials/imgproc/histograms/histogram_ca So what you do is simply split the whole histogram to 16 sub-parts and value of each sub-part is the sum of all pixel count in it. This each sub-part is called "BIN". In first case, number of bins -where 256 (one for each pixel) while in second case, it is only 16. BINS is represented by the term +were 256 (one for each pixel) while in second case, it is only 16. BINS is represented by the term **histSize** in OpenCV docs. **DIMS** : It is the number of parameters for which we collect the data. In this case, we collect From fa4d32f7dde6a6ca5ba688e16a4984dfd0cb15ec Mon Sep 17 00:00:00 2001 From: LaurentBerger Date: Mon, 6 Feb 2017 22:49:18 +0100 Subject: [PATCH 366/409] Solve issue 8136 opencv doc of threshold --- modules/imgproc/include/opencv2/imgproc.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 243d72b32e..3680492077 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -2709,7 +2709,7 @@ CV_EXPORTS_W void createHanningWindow(OutputArray dst, Size winSize, int type); /** @brief Applies a fixed-level threshold to each array element. -The function applies fixed-level thresholding to a single-channel array. The function is typically +The function applies fixed-level thresholding to a multiple-channel array. The function is typically used to get a bi-level (binary) image out of a grayscale image ( cv::compare could be also used for this purpose) or for removing a noise, that is, filtering out pixels with too small or too large values. There are several types of thresholding supported by the function. They are determined by @@ -2721,8 +2721,8 @@ or Triangle algorithm and uses it instead of the specified thresh . The function computed threshold value. Currently, the Otsu's and Triangle methods are implemented only for 8-bit images. -@param src input array (single-channel, 8-bit or 32-bit floating point). -@param dst output array of the same size and type as src. +@param src input array (multiple-channel, 8-bit or 32-bit floating point). +@param dst output array of the same size and type and the same number of channels as src. @param thresh threshold value. @param maxval maximum value to use with the THRESH_BINARY and THRESH_BINARY_INV thresholding types. From b3c1bd788a313071ba33239baf7ff80edef8e5b1 Mon Sep 17 00:00:00 2001 From: LaurentBerger Date: Tue, 7 Feb 2017 10:14:24 +0100 Subject: [PATCH 367/409] Add note about OTSU and TRIANGLE (comment sovrasov) --- modules/imgproc/include/opencv2/imgproc.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 3680492077..c2c694b36b 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -2721,6 +2721,8 @@ or Triangle algorithm and uses it instead of the specified thresh . The function computed threshold value. Currently, the Otsu's and Triangle methods are implemented only for 8-bit images. +@note Input image should be single channel only in case of CV_THRESH_OTSU or CV_THRESH_TRIANGLE flags + @param src input array (multiple-channel, 8-bit or 32-bit floating point). @param dst output array of the same size and type and the same number of channels as src. @param thresh threshold value. From 22d8a81a578c35749935ea9c696b807e45467e4c Mon Sep 17 00:00:00 2001 From: StevenPuttemans Date: Tue, 7 Feb 2017 11:37:42 +0100 Subject: [PATCH 368/409] enable processing of 16, 32 and 64 bit 4 channel data using GDAL1.2 --- modules/imgcodecs/src/grfmt_gdal.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/imgcodecs/src/grfmt_gdal.cpp b/modules/imgcodecs/src/grfmt_gdal.cpp index 8865ae4948..4bb8dd02bc 100644 --- a/modules/imgcodecs/src/grfmt_gdal.cpp +++ b/modules/imgcodecs/src/grfmt_gdal.cpp @@ -308,6 +308,11 @@ void write_pixel( const double& pixelValue, // input: 4 channel, output: 4 channel else if( gdalChannels == 4 && image.channels() == 4 ){ if( image.depth() == CV_8U ){ image.at(row,col)[channel] = newValue; } + else if( image.depth() == CV_16U ){ image.at(row,col)[channel] = newValue; } + else if( image.depth() == CV_16S ){ image.at(row,col)[channel] = newValue; } + else if( image.depth() == CV_32S ){ image.at(row,col)[channel] = newValue; } + else if( image.depth() == CV_32F ){ image.at(row,col)[channel] = newValue; } + else if( image.depth() == CV_64F ){ image.at(row,col)[channel] = newValue; } else{ throw std::runtime_error("Unknown image depth, gdal: 4, image: 4"); } } @@ -574,4 +579,4 @@ bool GdalDecoder::checkSignature( const String& signature )const{ } /// End of cv Namespace -#endif /**< End of HAVE_GDAL Definition */ \ No newline at end of file +#endif /**< End of HAVE_GDAL Definition */ From 4950f542d12b133b23c7c753907874e91d5a7fa6 Mon Sep 17 00:00:00 2001 From: Vitaly Tuzov Date: Tue, 24 Jan 2017 15:32:32 +0300 Subject: [PATCH 369/409] Fix for SGBM compute() buffer allocation failure on big images --- modules/calib3d/src/stereosgbm.cpp | 6 +++--- modules/core/include/opencv2/core/mat.hpp | 8 +++++++ modules/core/src/matrix.cpp | 26 +++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/modules/calib3d/src/stereosgbm.cpp b/modules/calib3d/src/stereosgbm.cpp index 27ce62e6f9..d5c160b0fc 100644 --- a/modules/calib3d/src/stereosgbm.cpp +++ b/modules/calib3d/src/stereosgbm.cpp @@ -353,7 +353,7 @@ static void computeDisparitySGBM( const Mat& img1, const Mat& img2, if( buffer.empty() || !buffer.isContinuous() || buffer.cols*buffer.rows*buffer.elemSize() < totalBufSize ) - buffer.create(1, (int)totalBufSize, CV_8U); + buffer.reserveBuffer(totalBufSize); // summary cost over different (nDirs) directions CostType* Cbuf = (CostType*)alignPtr(buffer.ptr(), ALIGN); @@ -939,7 +939,7 @@ void getBufferPointers(Mat& buffer, int width, int width1, int D, int num_ch, in 16; //to compensate for the alignPtr shifts if( buffer.empty() || !buffer.isContinuous() || buffer.cols*buffer.rows*buffer.elemSize() < totalBufSize ) - buffer.create(1, (int)totalBufSize, CV_8U); + buffer.reserveBuffer(totalBufSize); // set up all the pointers: curCostVolumeLine = (CostType*)alignPtr(buffer.ptr(), 16); @@ -1615,7 +1615,7 @@ void filterSpecklesImpl(cv::Mat& img, int newVal, int maxSpeckleSize, int maxDif int width = img.cols, height = img.rows, npixels = width*height; size_t bufSize = npixels*(int)(sizeof(Point2s) + sizeof(int) + sizeof(uchar)); if( !_buf.isContinuous() || _buf.empty() || _buf.cols*_buf.rows*_buf.elemSize() < bufSize ) - _buf.create(1, (int)bufSize, CV_8U); + _buf.reserveBuffer(bufSize); uchar* buf = _buf.ptr(); int i, j, dstep = (int)(img.step/sizeof(T)); diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 39c197ea3a..b4c6b4e419 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -1416,6 +1416,14 @@ public: */ void reserve(size_t sz); + /** @brief Reserves space for the certain number of bytes. + + The method reserves space for sz bytes. If the matrix already has enough space to store sz bytes, + nothing happens. If matrix has to be reallocated its previous content could be lost. + @param sz Number of bytes. + */ + void reserveBuffer(size_t sz); + /** @brief Changes the number of matrix rows. The methods change the number of matrix rows. If the matrix is reallocated, the first diff --git a/modules/core/src/matrix.cpp b/modules/core/src/matrix.cpp index c542a9ac70..32eed8abaf 100644 --- a/modules/core/src/matrix.cpp +++ b/modules/core/src/matrix.cpp @@ -830,6 +830,32 @@ void Mat::reserve(size_t nelems) dataend = data + step.p[0]*r; } +void Mat::reserveBuffer(size_t nbytes) +{ + size_t esz = 1; + int mtype = CV_8UC1; + if (!empty()) + { + if (!isSubmatrix() && data + nbytes <= dataend)//Should it be datalimit? + return; + esz = elemSize(); + mtype = type(); + } + + size_t nelems = (nbytes - 1) / esz + 1; + +#if SIZE_MAX > UINT_MAX + CV_Assert(nelems <= size_t(INT_MAX)*size_t(INT_MAX)); + int newrows = nelems > size_t(INT_MAX) ? nelems > 0x400*size_t(INT_MAX) ? nelems > 0x100000 * size_t(INT_MAX) ? nelems > 0x40000000 * size_t(INT_MAX) ? + size_t(INT_MAX) : 0x40000000 : 0x100000 : 0x400 : 1; +#else + int newrows = nelems > size_t(INT_MAX) ? 2 : 1; +#endif + int newcols = (int)((nelems - 1) / newrows + 1); + + create(newrows, newcols, mtype); +} + void Mat::resize(size_t nelems) { From 0689b43a7cfb0078c12dbd68a300b1f6268873ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sejkora?= Date: Thu, 9 Feb 2017 02:07:06 +0100 Subject: [PATCH 370/409] Fixed two typos in the Filter2D tutorial --- doc/tutorials/imgproc/imgtrans/filter_2d/filter_2d.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tutorials/imgproc/imgtrans/filter_2d/filter_2d.markdown b/doc/tutorials/imgproc/imgtrans/filter_2d/filter_2d.markdown index a09146419d..50215567fa 100644 --- a/doc/tutorials/imgproc/imgtrans/filter_2d/filter_2d.markdown +++ b/doc/tutorials/imgproc/imgtrans/filter_2d/filter_2d.markdown @@ -20,8 +20,8 @@ In a very general sense, convolution is an operation between every part of an im ### What is a kernel? -A kernel is essentially a fixed size array of numerical coefficeints along with an *anchor point* in -that array, which is tipically located at the center. +A kernel is essentially a fixed size array of numerical coefficients along with an *anchor point* in +that array, which is typically located at the center. ![](images/filter_2d_tutorial_kernel_theory.png) From 9ac9e9e29ad6bc0393986040ae702a24af9e8f50 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 9 Feb 2017 13:23:35 +0300 Subject: [PATCH 371/409] core: fix String::end() implementation --- modules/core/include/opencv2/core/cvstd.hpp | 2 +- modules/core/test/test_misc.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/cvstd.hpp b/modules/core/include/opencv2/core/cvstd.hpp index 2d40bd069f..28dae3db5f 100644 --- a/modules/core/include/opencv2/core/cvstd.hpp +++ b/modules/core/include/opencv2/core/cvstd.hpp @@ -748,7 +748,7 @@ const char* String::begin() const inline const char* String::end() const { - return len_ ? cstr_ + 1 : 0; + return len_ ? cstr_ + len_ : NULL; } inline diff --git a/modules/core/test/test_misc.cpp b/modules/core/test/test_misc.cpp index 9e69ffb664..e018d74a15 100644 --- a/modules/core/test/test_misc.cpp +++ b/modules/core/test/test_misc.cpp @@ -138,3 +138,10 @@ TEST(Core_String, find_last_of__with__empty_string) // npos is not exported: EXPECT_EQ(cv::String::npos, p); EXPECT_EQ(std::string::npos, p); } + +TEST(Core_String, end_method_regression) +{ + cv::String old_string = "012345"; + cv::String new_string(old_string.begin(), old_string.end()); + EXPECT_EQ(6u, new_string.size()); +} From 03fb85f17956a6968610c856668a9203fb2a1b3c Mon Sep 17 00:00:00 2001 From: Adeel Date: Thu, 9 Feb 2017 20:11:00 +0500 Subject: [PATCH 372/409] update fix for #8105 (compiling with mingw32) taken from https://github.com/google/googletest/pull/856 --- modules/ts/include/opencv2/ts/ts_gtest.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ts/include/opencv2/ts/ts_gtest.h b/modules/ts/include/opencv2/ts/ts_gtest.h index 2153381ab9..ea1e62c338 100644 --- a/modules/ts/include/opencv2/ts/ts_gtest.h +++ b/modules/ts/include/opencv2/ts/ts_gtest.h @@ -720,7 +720,7 @@ # include # endif // In order to avoid having to include , use forward declaration -# if GTEST_OS_WINDOWS_MINGW +#if GTEST_OS_WINDOWS_MINGW && !defined(__MINGW64_VERSION_MAJOR) // MinGW defined _CRITICAL_SECTION and _RTL_CRITICAL_SECTION as two // separate (equivalent) structs, instead of using typedef typedef struct _CRITICAL_SECTION GTEST_CRITICAL_SECTION; From 1b842c47621fdec4c6d67691141f49c346b049ab Mon Sep 17 00:00:00 2001 From: Ilija Puaca Date: Thu, 9 Feb 2017 11:45:12 +0100 Subject: [PATCH 373/409] Updated version of libwebp to 0.6.0. Removed iOS from build exceptions for libwebp --- 3rdparty/libwebp/CMakeLists.txt | 4 +- 3rdparty/libwebp/dec/Makefile.am | 28 + 3rdparty/libwebp/dec/alpha.c | 115 -- 3rdparty/libwebp/dec/alpha_dec.c | 232 +++ 3rdparty/libwebp/dec/alphai_dec.h | 54 + .../libwebp/dec/{buffer.c => buffer_dec.c} | 139 +- 3rdparty/libwebp/dec/common_dec.h | 54 + 3rdparty/libwebp/dec/{frame.c => frame_dec.c} | 620 +++--- 3rdparty/libwebp/dec/{idec.c => idec_dec.c} | 298 +-- 3rdparty/libwebp/dec/{io.c => io_dec.c} | 356 ++-- 3rdparty/libwebp/dec/{quant.c => quant_dec.c} | 11 +- 3rdparty/libwebp/dec/{tree.c => tree_dec.c} | 177 +- 3rdparty/libwebp/dec/{vp8.c => vp8_dec.c} | 473 ++--- .../libwebp/dec/{decode_vp8.h => vp8_dec.h} | 7 +- 3rdparty/libwebp/dec/{vp8i.h => vp8i_dec.h} | 216 +- 3rdparty/libwebp/dec/vp8l.c | 1249 ------------ 3rdparty/libwebp/dec/vp8l_dec.c | 1671 +++++++++++++++ 3rdparty/libwebp/dec/{vp8li.h => vp8li_dec.h} | 49 +- 3rdparty/libwebp/dec/{webp.c => webp_dec.c} | 198 +- 3rdparty/libwebp/dec/{webpi.h => webpi_dec.h} | 35 +- 3rdparty/libwebp/demux/Makefile.am | 14 + 3rdparty/libwebp/demux/anim_decode.c | 454 +++++ 3rdparty/libwebp/demux/demux.c | 397 ++-- 3rdparty/libwebp/demux/libwebpdemux.pc.in | 11 + 3rdparty/libwebp/demux/libwebpdemux.rc | 41 + 3rdparty/libwebp/dsp/Makefile.am | 153 ++ 3rdparty/libwebp/dsp/alpha_processing.c | 397 ++++ .../dsp/alpha_processing_mips_dsp_r2.c | 141 ++ 3rdparty/libwebp/dsp/alpha_processing_neon.c | 191 ++ 3rdparty/libwebp/dsp/alpha_processing_sse2.c | 285 +++ 3rdparty/libwebp/dsp/alpha_processing_sse41.c | 92 + 3rdparty/libwebp/dsp/argb.c | 68 + 3rdparty/libwebp/dsp/argb_mips_dsp_r2.c | 110 + 3rdparty/libwebp/dsp/argb_sse2.c | 67 + 3rdparty/libwebp/dsp/common_sse2.h | 194 ++ 3rdparty/libwebp/{enc => dsp}/cost.c | 268 +-- 3rdparty/libwebp/dsp/cost_mips32.c | 154 ++ 3rdparty/libwebp/dsp/cost_mips_dsp_r2.c | 107 + 3rdparty/libwebp/dsp/cost_sse2.c | 119 ++ 3rdparty/libwebp/dsp/cpu.c | 165 +- 3rdparty/libwebp/dsp/dec.c | 368 ++-- 3rdparty/libwebp/dsp/dec_clip_tables.c | 366 ++++ 3rdparty/libwebp/dsp/dec_mips32.c | 587 ++++++ 3rdparty/libwebp/dsp/dec_mips_dsp_r2.c | 994 +++++++++ 3rdparty/libwebp/dsp/dec_msa.c | 1019 ++++++++++ 3rdparty/libwebp/dsp/dec_neon.c | 1532 ++++++++++++-- 3rdparty/libwebp/dsp/dec_sse2.c | 1089 ++++++---- 3rdparty/libwebp/dsp/dec_sse41.c | 46 + 3rdparty/libwebp/dsp/dsp.h | 453 ++++- 3rdparty/libwebp/dsp/enc.c | 352 +++- 3rdparty/libwebp/dsp/enc_avx2.c | 21 + 3rdparty/libwebp/dsp/enc_mips32.c | 672 ++++++ 3rdparty/libwebp/dsp/enc_mips_dsp_r2.c | 1510 ++++++++++++++ 3rdparty/libwebp/dsp/enc_msa.c | 892 ++++++++ 3rdparty/libwebp/dsp/enc_neon.c | 929 ++++++--- 3rdparty/libwebp/dsp/enc_sse2.c | 1665 +++++++++------ 3rdparty/libwebp/dsp/enc_sse41.c | 339 ++++ 3rdparty/libwebp/dsp/filters.c | 273 +++ 3rdparty/libwebp/dsp/filters_mips_dsp_r2.c | 395 ++++ 3rdparty/libwebp/dsp/filters_msa.c | 202 ++ 3rdparty/libwebp/dsp/filters_neon.c | 327 +++ 3rdparty/libwebp/dsp/filters_sse2.c | 330 +++ 3rdparty/libwebp/dsp/lossless.c | 1198 +++-------- 3rdparty/libwebp/dsp/lossless.h | 213 +- 3rdparty/libwebp/dsp/lossless_common.h | 210 ++ 3rdparty/libwebp/dsp/lossless_enc.c | 964 +++++++++ 3rdparty/libwebp/dsp/lossless_enc_mips32.c | 431 ++++ .../libwebp/dsp/lossless_enc_mips_dsp_r2.c | 275 +++ 3rdparty/libwebp/dsp/lossless_enc_msa.c | 147 ++ 3rdparty/libwebp/dsp/lossless_enc_neon.c | 143 ++ 3rdparty/libwebp/dsp/lossless_enc_sse2.c | 711 +++++++ 3rdparty/libwebp/dsp/lossless_enc_sse41.c | 53 + 3rdparty/libwebp/dsp/lossless_mips_dsp_r2.c | 689 +++++++ 3rdparty/libwebp/dsp/lossless_msa.c | 355 ++++ 3rdparty/libwebp/dsp/lossless_neon.c | 642 ++++++ 3rdparty/libwebp/dsp/lossless_sse2.c | 677 +++++++ 3rdparty/libwebp/dsp/mips_macro.h | 200 ++ 3rdparty/libwebp/dsp/msa_macro.h | 1390 +++++++++++++ 3rdparty/libwebp/dsp/neon.h | 100 + 3rdparty/libwebp/dsp/rescaler.c | 244 +++ 3rdparty/libwebp/dsp/rescaler_mips32.c | 291 +++ 3rdparty/libwebp/dsp/rescaler_mips_dsp_r2.c | 314 +++ 3rdparty/libwebp/dsp/rescaler_msa.c | 444 ++++ 3rdparty/libwebp/dsp/rescaler_neon.c | 186 ++ 3rdparty/libwebp/dsp/rescaler_sse2.c | 375 ++++ 3rdparty/libwebp/dsp/upsampling.c | 253 +-- 3rdparty/libwebp/dsp/upsampling_mips_dsp_r2.c | 282 +++ 3rdparty/libwebp/dsp/upsampling_msa.c | 678 +++++++ 3rdparty/libwebp/dsp/upsampling_neon.c | 313 ++- 3rdparty/libwebp/dsp/upsampling_sse2.c | 190 +- 3rdparty/libwebp/dsp/yuv.c | 292 ++- 3rdparty/libwebp/dsp/yuv.h | 234 +-- 3rdparty/libwebp/dsp/yuv_mips32.c | 103 + 3rdparty/libwebp/dsp/yuv_mips_dsp_r2.c | 134 ++ 3rdparty/libwebp/dsp/yuv_sse2.c | 863 ++++++++ 3rdparty/libwebp/enc/Makefile.am | 42 + 3rdparty/libwebp/enc/{alpha.c => alpha_enc.c} | 365 ++-- .../enc/{analysis.c => analysis_enc.c} | 260 ++- 3rdparty/libwebp/enc/backward_references.c | 894 -------- 3rdparty/libwebp/enc/backward_references.h | 219 -- .../libwebp/enc/backward_references_enc.c | 1800 +++++++++++++++++ .../libwebp/enc/backward_references_enc.h | 207 ++ .../libwebp/enc/{config.c => config_enc.c} | 115 +- 3rdparty/libwebp/enc/cost_enc.c | 342 ++++ 3rdparty/libwebp/enc/{cost.h => cost_enc.h} | 45 +- .../libwebp/enc/delta_palettization_enc.c | 455 +++++ .../libwebp/enc/delta_palettization_enc.h | 25 + 3rdparty/libwebp/enc/filter.c | 411 ---- 3rdparty/libwebp/enc/filter_enc.c | 219 ++ 3rdparty/libwebp/enc/{frame.c => frame_enc.c} | 663 +++--- 3rdparty/libwebp/enc/histogram.c | 514 ----- 3rdparty/libwebp/enc/histogram_enc.c | 990 +++++++++ .../enc/{histogram.h => histogram_enc.h} | 74 +- .../enc/{iterator.c => iterator_enc.c} | 207 +- 3rdparty/libwebp/enc/layer.c | 51 - 3rdparty/libwebp/enc/near_lossless_enc.c | 122 ++ 3rdparty/libwebp/enc/picture.c | 1115 ---------- 3rdparty/libwebp/enc/picture_csp_enc.c | 1175 +++++++++++ 3rdparty/libwebp/enc/picture_enc.c | 293 +++ 3rdparty/libwebp/enc/picture_psnr_enc.c | 213 ++ 3rdparty/libwebp/enc/picture_rescale_enc.c | 264 +++ 3rdparty/libwebp/enc/picture_tools_enc.c | 226 +++ 3rdparty/libwebp/enc/predictor_enc.c | 750 +++++++ 3rdparty/libwebp/enc/{quant.c => quant_enc.c} | 795 +++++--- .../libwebp/enc/{syntax.c => syntax_enc.c} | 112 +- 3rdparty/libwebp/enc/{token.c => token_enc.c} | 178 +- 3rdparty/libwebp/enc/{tree.c => tree_enc.c} | 24 +- .../libwebp/enc/{vp8enci.h => vp8i_enc.h} | 366 ++-- 3rdparty/libwebp/enc/vp8l.c | 1175 ----------- 3rdparty/libwebp/enc/vp8l_enc.c | 1667 +++++++++++++++ 3rdparty/libwebp/enc/vp8li.h | 70 - 3rdparty/libwebp/enc/vp8li_enc.h | 95 + .../libwebp/enc/{webpenc.c => webp_enc.c} | 218 +- 3rdparty/libwebp/mux/Makefile.am | 19 + 3rdparty/libwebp/mux/anim_encode.c | 1579 +++++++++++++++ 3rdparty/libwebp/mux/animi.h | 43 + 3rdparty/libwebp/mux/libwebpmux.pc.in | 12 + 3rdparty/libwebp/mux/libwebpmux.rc | 41 + 3rdparty/libwebp/mux/muxedit.c | 424 ++-- 3rdparty/libwebp/mux/muxi.h | 76 +- 3rdparty/libwebp/mux/muxinternal.c | 155 +- 3rdparty/libwebp/mux/muxread.c | 194 +- 3rdparty/libwebp/utils/Makefile.am | 50 + 3rdparty/libwebp/utils/bit_reader.c | 214 -- 3rdparty/libwebp/utils/bit_reader.h | 331 --- 3rdparty/libwebp/utils/bit_reader_inl_utils.h | 190 ++ 3rdparty/libwebp/utils/bit_reader_utils.c | 222 ++ 3rdparty/libwebp/utils/bit_reader_utils.h | 174 ++ .../{bit_writer.c => bit_writer_utils.c} | 181 +- .../{bit_writer.h => bit_writer_utils.h} | 97 +- .../{color_cache.c => color_cache_utils.c} | 21 +- .../{color_cache.h => color_cache_utils.h} | 33 +- 3rdparty/libwebp/utils/endian_inl_utils.h | 100 + 3rdparty/libwebp/utils/filters.c | 230 --- 3rdparty/libwebp/utils/filters.h | 57 - 3rdparty/libwebp/utils/filters_utils.c | 76 + .../{dec/layer.c => utils/filters_utils.h} | 31 +- 3rdparty/libwebp/utils/huffman.c | 240 --- 3rdparty/libwebp/utils/huffman.h | 80 - ...uffman_encode.c => huffman_encode_utils.c} | 83 +- ...uffman_encode.h => huffman_encode_utils.h} | 19 +- 3rdparty/libwebp/utils/huffman_utils.c | 223 ++ 3rdparty/libwebp/utils/huffman_utils.h | 88 + 3rdparty/libwebp/utils/quant_levels_dec.c | 30 - .../libwebp/utils/quant_levels_dec_utils.c | 284 +++ ..._levels_dec.h => quant_levels_dec_utils.h} | 15 +- .../{quant_levels.c => quant_levels_utils.c} | 9 +- .../{quant_levels.h => quant_levels_utils.h} | 4 +- 3rdparty/libwebp/utils/random_utils.c | 43 + 3rdparty/libwebp/utils/random_utils.h | 63 + 3rdparty/libwebp/utils/rescaler.c | 154 -- 3rdparty/libwebp/utils/rescaler.h | 78 - 3rdparty/libwebp/utils/rescaler_utils.c | 146 ++ 3rdparty/libwebp/utils/rescaler_utils.h | 101 + 3rdparty/libwebp/utils/thread.h | 92 - .../utils/{thread.c => thread_utils.c} | 229 ++- 3rdparty/libwebp/utils/thread_utils.h | 93 + 3rdparty/libwebp/utils/utils.c | 295 ++- 3rdparty/libwebp/utils/utils.h | 109 +- 3rdparty/libwebp/webp/decode.h | 71 +- 3rdparty/libwebp/webp/demux.h | 246 ++- 3rdparty/libwebp/webp/encode.h | 131 +- 3rdparty/libwebp/webp/format_constants.h | 5 +- 3rdparty/libwebp/webp/mux.h | 329 ++- 3rdparty/libwebp/webp/mux_types.h | 17 +- 3rdparty/libwebp/webp/types.h | 13 +- CMakeLists.txt | 2 +- modules/imgcodecs/src/grfmt_webp.cpp | 2 +- 188 files changed, 47266 insertions(+), 14703 deletions(-) create mode 100644 3rdparty/libwebp/dec/Makefile.am delete mode 100644 3rdparty/libwebp/dec/alpha.c create mode 100644 3rdparty/libwebp/dec/alpha_dec.c create mode 100644 3rdparty/libwebp/dec/alphai_dec.h rename 3rdparty/libwebp/dec/{buffer.c => buffer_dec.c} (58%) create mode 100644 3rdparty/libwebp/dec/common_dec.h rename 3rdparty/libwebp/dec/{frame.c => frame_dec.c} (58%) rename 3rdparty/libwebp/dec/{idec.c => idec_dec.c} (75%) rename 3rdparty/libwebp/dec/{io.c => io_dec.c} (66%) rename 3rdparty/libwebp/dec/{quant.c => quant_dec.c} (95%) rename 3rdparty/libwebp/dec/{tree.c => tree_dec.c} (86%) rename 3rdparty/libwebp/dec/{vp8.c => vp8_dec.c} (60%) rename 3rdparty/libwebp/dec/{decode_vp8.h => vp8_dec.h} (97%) rename 3rdparty/libwebp/dec/{vp8i.h => vp8i_dec.h} (60%) delete mode 100644 3rdparty/libwebp/dec/vp8l.c create mode 100644 3rdparty/libwebp/dec/vp8l_dec.c rename 3rdparty/libwebp/dec/{vp8li.h => vp8li_dec.h} (71%) rename 3rdparty/libwebp/dec/{webp.c => webp_dec.c} (82%) rename 3rdparty/libwebp/dec/{webpi.h => webpi_dec.h} (78%) create mode 100644 3rdparty/libwebp/demux/Makefile.am create mode 100644 3rdparty/libwebp/demux/anim_decode.c create mode 100644 3rdparty/libwebp/demux/libwebpdemux.pc.in create mode 100644 3rdparty/libwebp/demux/libwebpdemux.rc create mode 100644 3rdparty/libwebp/dsp/Makefile.am create mode 100644 3rdparty/libwebp/dsp/alpha_processing.c create mode 100644 3rdparty/libwebp/dsp/alpha_processing_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/alpha_processing_neon.c create mode 100644 3rdparty/libwebp/dsp/alpha_processing_sse2.c create mode 100644 3rdparty/libwebp/dsp/alpha_processing_sse41.c create mode 100644 3rdparty/libwebp/dsp/argb.c create mode 100644 3rdparty/libwebp/dsp/argb_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/argb_sse2.c create mode 100644 3rdparty/libwebp/dsp/common_sse2.h rename 3rdparty/libwebp/{enc => dsp}/cost.c (61%) create mode 100644 3rdparty/libwebp/dsp/cost_mips32.c create mode 100644 3rdparty/libwebp/dsp/cost_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/cost_sse2.c create mode 100644 3rdparty/libwebp/dsp/dec_clip_tables.c create mode 100644 3rdparty/libwebp/dsp/dec_mips32.c create mode 100644 3rdparty/libwebp/dsp/dec_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/dec_msa.c create mode 100644 3rdparty/libwebp/dsp/dec_sse41.c create mode 100644 3rdparty/libwebp/dsp/enc_avx2.c create mode 100644 3rdparty/libwebp/dsp/enc_mips32.c create mode 100644 3rdparty/libwebp/dsp/enc_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/enc_msa.c create mode 100644 3rdparty/libwebp/dsp/enc_sse41.c create mode 100644 3rdparty/libwebp/dsp/filters.c create mode 100644 3rdparty/libwebp/dsp/filters_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/filters_msa.c create mode 100644 3rdparty/libwebp/dsp/filters_neon.c create mode 100644 3rdparty/libwebp/dsp/filters_sse2.c create mode 100644 3rdparty/libwebp/dsp/lossless_common.h create mode 100644 3rdparty/libwebp/dsp/lossless_enc.c create mode 100644 3rdparty/libwebp/dsp/lossless_enc_mips32.c create mode 100644 3rdparty/libwebp/dsp/lossless_enc_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/lossless_enc_msa.c create mode 100644 3rdparty/libwebp/dsp/lossless_enc_neon.c create mode 100644 3rdparty/libwebp/dsp/lossless_enc_sse2.c create mode 100644 3rdparty/libwebp/dsp/lossless_enc_sse41.c create mode 100644 3rdparty/libwebp/dsp/lossless_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/lossless_msa.c create mode 100644 3rdparty/libwebp/dsp/lossless_neon.c create mode 100644 3rdparty/libwebp/dsp/lossless_sse2.c create mode 100644 3rdparty/libwebp/dsp/mips_macro.h create mode 100644 3rdparty/libwebp/dsp/msa_macro.h create mode 100644 3rdparty/libwebp/dsp/neon.h create mode 100644 3rdparty/libwebp/dsp/rescaler.c create mode 100644 3rdparty/libwebp/dsp/rescaler_mips32.c create mode 100644 3rdparty/libwebp/dsp/rescaler_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/rescaler_msa.c create mode 100644 3rdparty/libwebp/dsp/rescaler_neon.c create mode 100644 3rdparty/libwebp/dsp/rescaler_sse2.c create mode 100644 3rdparty/libwebp/dsp/upsampling_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/upsampling_msa.c create mode 100644 3rdparty/libwebp/dsp/yuv_mips32.c create mode 100644 3rdparty/libwebp/dsp/yuv_mips_dsp_r2.c create mode 100644 3rdparty/libwebp/dsp/yuv_sse2.c create mode 100644 3rdparty/libwebp/enc/Makefile.am rename 3rdparty/libwebp/enc/{alpha.c => alpha_enc.c} (52%) rename 3rdparty/libwebp/enc/{analysis.c => analysis_enc.c} (61%) delete mode 100644 3rdparty/libwebp/enc/backward_references.c delete mode 100644 3rdparty/libwebp/enc/backward_references.h create mode 100644 3rdparty/libwebp/enc/backward_references_enc.c create mode 100644 3rdparty/libwebp/enc/backward_references_enc.h rename 3rdparty/libwebp/enc/{config.c => config_enc.c} (55%) create mode 100644 3rdparty/libwebp/enc/cost_enc.c rename 3rdparty/libwebp/enc/{cost.h => cost_enc.h} (55%) create mode 100644 3rdparty/libwebp/enc/delta_palettization_enc.c create mode 100644 3rdparty/libwebp/enc/delta_palettization_enc.h delete mode 100644 3rdparty/libwebp/enc/filter.c create mode 100644 3rdparty/libwebp/enc/filter_enc.c rename 3rdparty/libwebp/enc/{frame.c => frame_enc.c} (60%) delete mode 100644 3rdparty/libwebp/enc/histogram.c create mode 100644 3rdparty/libwebp/enc/histogram_enc.c rename 3rdparty/libwebp/enc/{histogram.h => histogram_enc.h} (57%) rename 3rdparty/libwebp/enc/{iterator.c => iterator_enc.c} (68%) delete mode 100644 3rdparty/libwebp/enc/layer.c create mode 100644 3rdparty/libwebp/enc/near_lossless_enc.c delete mode 100644 3rdparty/libwebp/enc/picture.c create mode 100644 3rdparty/libwebp/enc/picture_csp_enc.c create mode 100644 3rdparty/libwebp/enc/picture_enc.c create mode 100644 3rdparty/libwebp/enc/picture_psnr_enc.c create mode 100644 3rdparty/libwebp/enc/picture_rescale_enc.c create mode 100644 3rdparty/libwebp/enc/picture_tools_enc.c create mode 100644 3rdparty/libwebp/enc/predictor_enc.c rename 3rdparty/libwebp/enc/{quant.c => quant_enc.c} (55%) rename 3rdparty/libwebp/enc/{syntax.c => syntax_enc.c} (80%) rename 3rdparty/libwebp/enc/{token.c => token_enc.c} (51%) rename 3rdparty/libwebp/enc/{tree.c => tree_enc.c} (97%) rename 3rdparty/libwebp/enc/{vp8enci.h => vp8i_enc.h} (64%) delete mode 100644 3rdparty/libwebp/enc/vp8l.c create mode 100644 3rdparty/libwebp/enc/vp8l_enc.c delete mode 100644 3rdparty/libwebp/enc/vp8li.h create mode 100644 3rdparty/libwebp/enc/vp8li_enc.h rename 3rdparty/libwebp/enc/{webpenc.c => webp_enc.c} (69%) create mode 100644 3rdparty/libwebp/mux/Makefile.am create mode 100644 3rdparty/libwebp/mux/anim_encode.c create mode 100644 3rdparty/libwebp/mux/animi.h create mode 100644 3rdparty/libwebp/mux/libwebpmux.pc.in create mode 100644 3rdparty/libwebp/mux/libwebpmux.rc create mode 100644 3rdparty/libwebp/utils/Makefile.am delete mode 100644 3rdparty/libwebp/utils/bit_reader.c delete mode 100644 3rdparty/libwebp/utils/bit_reader.h create mode 100644 3rdparty/libwebp/utils/bit_reader_inl_utils.h create mode 100644 3rdparty/libwebp/utils/bit_reader_utils.c create mode 100644 3rdparty/libwebp/utils/bit_reader_utils.h rename 3rdparty/libwebp/utils/{bit_writer.c => bit_writer_utils.c} (62%) rename 3rdparty/libwebp/utils/{bit_writer.h => bit_writer_utils.h} (52%) rename 3rdparty/libwebp/utils/{color_cache.c => color_cache_utils.c} (72%) rename 3rdparty/libwebp/utils/{color_cache.h => color_cache_utils.h} (68%) create mode 100644 3rdparty/libwebp/utils/endian_inl_utils.h delete mode 100644 3rdparty/libwebp/utils/filters.c delete mode 100644 3rdparty/libwebp/utils/filters.h create mode 100644 3rdparty/libwebp/utils/filters_utils.c rename 3rdparty/libwebp/{dec/layer.c => utils/filters_utils.h} (51%) delete mode 100644 3rdparty/libwebp/utils/huffman.c delete mode 100644 3rdparty/libwebp/utils/huffman.h rename 3rdparty/libwebp/utils/{huffman_encode.c => huffman_encode_utils.c} (87%) rename 3rdparty/libwebp/utils/{huffman_encode.h => huffman_encode_utils.h} (69%) create mode 100644 3rdparty/libwebp/utils/huffman_utils.c create mode 100644 3rdparty/libwebp/utils/huffman_utils.h delete mode 100644 3rdparty/libwebp/utils/quant_levels_dec.c create mode 100644 3rdparty/libwebp/utils/quant_levels_dec_utils.c rename 3rdparty/libwebp/utils/{quant_levels_dec.h => quant_levels_dec_utils.h} (71%) rename 3rdparty/libwebp/utils/{quant_levels.c => quant_levels_utils.c} (95%) rename 3rdparty/libwebp/utils/{quant_levels.h => quant_levels_utils.h} (92%) create mode 100644 3rdparty/libwebp/utils/random_utils.c create mode 100644 3rdparty/libwebp/utils/random_utils.h delete mode 100644 3rdparty/libwebp/utils/rescaler.c delete mode 100644 3rdparty/libwebp/utils/rescaler.h create mode 100644 3rdparty/libwebp/utils/rescaler_utils.c create mode 100644 3rdparty/libwebp/utils/rescaler_utils.h delete mode 100644 3rdparty/libwebp/utils/thread.h rename 3rdparty/libwebp/utils/{thread.c => thread_utils.c} (51%) create mode 100644 3rdparty/libwebp/utils/thread_utils.h diff --git a/3rdparty/libwebp/CMakeLists.txt b/3rdparty/libwebp/CMakeLists.txt index e9ac92a2ca..004697e495 100644 --- a/3rdparty/libwebp/CMakeLists.txt +++ b/3rdparty/libwebp/CMakeLists.txt @@ -7,8 +7,8 @@ project(${WEBP_LIBRARY}) ocv_include_directories(${CMAKE_CURRENT_SOURCE_DIR}) ocv_include_directories("${CMAKE_CURRENT_SOURCE_DIR}/cpu-features") -file(GLOB lib_srcs dec/*.c dsp/*.c enc/*.c mux/*.c utils/*.c webp/*.c) -file(GLOB lib_hdrs dec/*.h dsp/*.h enc/*.h mux/*.h utils/*.h webp/*.h) +file(GLOB lib_srcs dec/*.c demux/*.c dsp/*.c enc/*.c mux/*.c utils/*.c webp/*.c) +file(GLOB lib_hdrs dec/*.h demux/*.h dsp/*.h enc/*.h mux/*.h utils/*.h webp/*.h) # FIXIT if(ANDROID AND ARMEABI_V7A AND NOT NEON) diff --git a/3rdparty/libwebp/dec/Makefile.am b/3rdparty/libwebp/dec/Makefile.am new file mode 100644 index 0000000000..8919c42b23 --- /dev/null +++ b/3rdparty/libwebp/dec/Makefile.am @@ -0,0 +1,28 @@ +noinst_LTLIBRARIES = libwebpdecode.la + +libwebpdecode_la_SOURCES = +libwebpdecode_la_SOURCES += alpha_dec.c +libwebpdecode_la_SOURCES += alphai_dec.h +libwebpdecode_la_SOURCES += buffer_dec.c +libwebpdecode_la_SOURCES += common_dec.h +libwebpdecode_la_SOURCES += vp8_dec.h +libwebpdecode_la_SOURCES += frame_dec.c +libwebpdecode_la_SOURCES += idec_dec.c +libwebpdecode_la_SOURCES += io_dec.c +libwebpdecode_la_SOURCES += quant_dec.c +libwebpdecode_la_SOURCES += tree_dec.c +libwebpdecode_la_SOURCES += vp8_dec.c +libwebpdecode_la_SOURCES += vp8i_dec.h +libwebpdecode_la_SOURCES += vp8l_dec.c +libwebpdecode_la_SOURCES += vp8li_dec.h +libwebpdecode_la_SOURCES += webp_dec.c +libwebpdecode_la_SOURCES += webpi_dec.h + +libwebpdecodeinclude_HEADERS = +libwebpdecodeinclude_HEADERS += ../webp/decode.h +libwebpdecodeinclude_HEADERS += ../webp/types.h +noinst_HEADERS = +noinst_HEADERS += ../webp/format_constants.h + +libwebpdecode_la_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE) +libwebpdecodeincludedir = $(includedir)/webp diff --git a/3rdparty/libwebp/dec/alpha.c b/3rdparty/libwebp/dec/alpha.c deleted file mode 100644 index b5e68919ca..0000000000 --- a/3rdparty/libwebp/dec/alpha.c +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Alpha-plane decompression. -// -// Author: Skal (pascal.massimino@gmail.com) - -#include -#include "./vp8i.h" -#include "./vp8li.h" -#include "../utils/filters.h" -#include "../utils/quant_levels_dec.h" -#include "../webp/format_constants.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -//------------------------------------------------------------------------------ -// Decodes the compressed data 'data' of size 'data_size' into the 'output'. -// The 'output' buffer should be pre-allocated and must be of the same -// dimension 'height'x'width', as that of the image. -// -// Returns 1 on successfully decoding the compressed alpha and -// 0 if either: -// error in bit-stream header (invalid compression mode or filter), or -// error returned by appropriate compression method. - -static int DecodeAlpha(const uint8_t* data, size_t data_size, - int width, int height, uint8_t* output) { - WEBP_FILTER_TYPE filter; - int pre_processing; - int rsrv; - int ok = 0; - int method; - const uint8_t* const alpha_data = data + ALPHA_HEADER_LEN; - const size_t alpha_data_size = data_size - ALPHA_HEADER_LEN; - - assert(width > 0 && height > 0); - assert(data != NULL && output != NULL); - - if (data_size <= ALPHA_HEADER_LEN) { - return 0; - } - - method = (data[0] >> 0) & 0x03; - filter = (data[0] >> 2) & 0x03; - pre_processing = (data[0] >> 4) & 0x03; - rsrv = (data[0] >> 6) & 0x03; - if (method < ALPHA_NO_COMPRESSION || - method > ALPHA_LOSSLESS_COMPRESSION || - filter >= WEBP_FILTER_LAST || - pre_processing > ALPHA_PREPROCESSED_LEVELS || - rsrv != 0) { - return 0; - } - - if (method == ALPHA_NO_COMPRESSION) { - const size_t alpha_decoded_size = height * width; - ok = (alpha_data_size >= alpha_decoded_size); - if (ok) memcpy(output, alpha_data, alpha_decoded_size); - } else { - ok = VP8LDecodeAlphaImageStream(width, height, alpha_data, alpha_data_size, - output); - } - - if (ok) { - WebPUnfilterFunc unfilter_func = WebPUnfilters[filter]; - if (unfilter_func != NULL) { - // TODO(vikas): Implement on-the-fly decoding & filter mechanism to decode - // and apply filter per image-row. - unfilter_func(width, height, width, output); - } - if (pre_processing == ALPHA_PREPROCESSED_LEVELS) { - ok = DequantizeLevels(output, width, height); - } - } - - return ok; -} - -//------------------------------------------------------------------------------ - -const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, - int row, int num_rows) { - const int width = dec->pic_hdr_.width_; - const int height = dec->pic_hdr_.height_; - - if (row < 0 || num_rows < 0 || row + num_rows > height) { - return NULL; // sanity check. - } - - if (row == 0) { - // Decode everything during the first call. - assert(!dec->is_alpha_decoded_); - if (!DecodeAlpha(dec->alpha_data_, (size_t)dec->alpha_data_size_, - width, height, dec->alpha_plane_)) { - return NULL; // Error. - } - dec->is_alpha_decoded_ = 1; - } - - // Return a pointer to the current decoded row. - return dec->alpha_plane_ + row * width; -} - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dec/alpha_dec.c b/3rdparty/libwebp/dec/alpha_dec.c new file mode 100644 index 0000000000..83ffd4b609 --- /dev/null +++ b/3rdparty/libwebp/dec/alpha_dec.c @@ -0,0 +1,232 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Alpha-plane decompression. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include "./alphai_dec.h" +#include "./vp8i_dec.h" +#include "./vp8li_dec.h" +#include "../dsp/dsp.h" +#include "../utils/quant_levels_dec_utils.h" +#include "../utils/utils.h" +#include "../webp/format_constants.h" + +//------------------------------------------------------------------------------ +// ALPHDecoder object. + +// Allocates a new alpha decoder instance. +static ALPHDecoder* ALPHNew(void) { + ALPHDecoder* const dec = (ALPHDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + return dec; +} + +// Clears and deallocates an alpha decoder instance. +static void ALPHDelete(ALPHDecoder* const dec) { + if (dec != NULL) { + VP8LDelete(dec->vp8l_dec_); + dec->vp8l_dec_ = NULL; + WebPSafeFree(dec); + } +} + +//------------------------------------------------------------------------------ +// Decoding. + +// Initialize alpha decoding by parsing the alpha header and decoding the image +// header for alpha data stored using lossless compression. +// Returns false in case of error in alpha header (data too short, invalid +// compression method or filter, error in lossless header data etc). +static int ALPHInit(ALPHDecoder* const dec, const uint8_t* data, + size_t data_size, const VP8Io* const src_io, + uint8_t* output) { + int ok = 0; + const uint8_t* const alpha_data = data + ALPHA_HEADER_LEN; + const size_t alpha_data_size = data_size - ALPHA_HEADER_LEN; + int rsrv; + VP8Io* const io = &dec->io_; + + assert(data != NULL && output != NULL && src_io != NULL); + + VP8FiltersInit(); + dec->output_ = output; + dec->width_ = src_io->width; + dec->height_ = src_io->height; + assert(dec->width_ > 0 && dec->height_ > 0); + + if (data_size <= ALPHA_HEADER_LEN) { + return 0; + } + + dec->method_ = (data[0] >> 0) & 0x03; + dec->filter_ = (WEBP_FILTER_TYPE)((data[0] >> 2) & 0x03); + dec->pre_processing_ = (data[0] >> 4) & 0x03; + rsrv = (data[0] >> 6) & 0x03; + if (dec->method_ < ALPHA_NO_COMPRESSION || + dec->method_ > ALPHA_LOSSLESS_COMPRESSION || + dec->filter_ >= WEBP_FILTER_LAST || + dec->pre_processing_ > ALPHA_PREPROCESSED_LEVELS || + rsrv != 0) { + return 0; + } + + // Copy the necessary parameters from src_io to io + VP8InitIo(io); + WebPInitCustomIo(NULL, io); + io->opaque = dec; + io->width = src_io->width; + io->height = src_io->height; + + io->use_cropping = src_io->use_cropping; + io->crop_left = src_io->crop_left; + io->crop_right = src_io->crop_right; + io->crop_top = src_io->crop_top; + io->crop_bottom = src_io->crop_bottom; + // No need to copy the scaling parameters. + + if (dec->method_ == ALPHA_NO_COMPRESSION) { + const size_t alpha_decoded_size = dec->width_ * dec->height_; + ok = (alpha_data_size >= alpha_decoded_size); + } else { + assert(dec->method_ == ALPHA_LOSSLESS_COMPRESSION); + ok = VP8LDecodeAlphaHeader(dec, alpha_data, alpha_data_size); + } + + return ok; +} + +// Decodes, unfilters and dequantizes *at least* 'num_rows' rows of alpha +// starting from row number 'row'. It assumes that rows up to (row - 1) have +// already been decoded. +// Returns false in case of bitstream error. +static int ALPHDecode(VP8Decoder* const dec, int row, int num_rows) { + ALPHDecoder* const alph_dec = dec->alph_dec_; + const int width = alph_dec->width_; + const int height = alph_dec->io_.crop_bottom; + if (alph_dec->method_ == ALPHA_NO_COMPRESSION) { + int y; + const uint8_t* prev_line = dec->alpha_prev_line_; + const uint8_t* deltas = dec->alpha_data_ + ALPHA_HEADER_LEN + row * width; + uint8_t* dst = dec->alpha_plane_ + row * width; + assert(deltas <= &dec->alpha_data_[dec->alpha_data_size_]); + if (alph_dec->filter_ != WEBP_FILTER_NONE) { + assert(WebPUnfilters[alph_dec->filter_] != NULL); + for (y = 0; y < num_rows; ++y) { + WebPUnfilters[alph_dec->filter_](prev_line, deltas, dst, width); + prev_line = dst; + dst += width; + deltas += width; + } + } else { + for (y = 0; y < num_rows; ++y) { + memcpy(dst, deltas, width * sizeof(*dst)); + prev_line = dst; + dst += width; + deltas += width; + } + } + dec->alpha_prev_line_ = prev_line; + } else { // alph_dec->method_ == ALPHA_LOSSLESS_COMPRESSION + assert(alph_dec->vp8l_dec_ != NULL); + if (!VP8LDecodeAlphaImageStream(alph_dec, row + num_rows)) { + return 0; + } + } + + if (row + num_rows >= height) { + dec->is_alpha_decoded_ = 1; + } + return 1; +} + +static int AllocateAlphaPlane(VP8Decoder* const dec, const VP8Io* const io) { + const int stride = io->width; + const int height = io->crop_bottom; + const uint64_t alpha_size = (uint64_t)stride * height; + assert(dec->alpha_plane_mem_ == NULL); + dec->alpha_plane_mem_ = + (uint8_t*)WebPSafeMalloc(alpha_size, sizeof(*dec->alpha_plane_)); + if (dec->alpha_plane_mem_ == NULL) { + return 0; + } + dec->alpha_plane_ = dec->alpha_plane_mem_; + dec->alpha_prev_line_ = NULL; + return 1; +} + +void WebPDeallocateAlphaMemory(VP8Decoder* const dec) { + assert(dec != NULL); + WebPSafeFree(dec->alpha_plane_mem_); + dec->alpha_plane_mem_ = NULL; + dec->alpha_plane_ = NULL; + ALPHDelete(dec->alph_dec_); + dec->alph_dec_ = NULL; +} + +//------------------------------------------------------------------------------ +// Main entry point. + +const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, + const VP8Io* const io, + int row, int num_rows) { + const int width = io->width; + const int height = io->crop_bottom; + + assert(dec != NULL && io != NULL); + + if (row < 0 || num_rows <= 0 || row + num_rows > height) { + return NULL; // sanity check. + } + + if (!dec->is_alpha_decoded_) { + if (dec->alph_dec_ == NULL) { // Initialize decoder. + dec->alph_dec_ = ALPHNew(); + if (dec->alph_dec_ == NULL) return NULL; + if (!AllocateAlphaPlane(dec, io)) goto Error; + if (!ALPHInit(dec->alph_dec_, dec->alpha_data_, dec->alpha_data_size_, + io, dec->alpha_plane_)) { + goto Error; + } + // if we allowed use of alpha dithering, check whether it's needed at all + if (dec->alph_dec_->pre_processing_ != ALPHA_PREPROCESSED_LEVELS) { + dec->alpha_dithering_ = 0; // disable dithering + } else { + num_rows = height - row; // decode everything in one pass + } + } + + assert(dec->alph_dec_ != NULL); + assert(row + num_rows <= height); + if (!ALPHDecode(dec, row, num_rows)) goto Error; + + if (dec->is_alpha_decoded_) { // finished? + ALPHDelete(dec->alph_dec_); + dec->alph_dec_ = NULL; + if (dec->alpha_dithering_ > 0) { + uint8_t* const alpha = dec->alpha_plane_ + io->crop_top * width + + io->crop_left; + if (!WebPDequantizeLevels(alpha, + io->crop_right - io->crop_left, + io->crop_bottom - io->crop_top, + width, dec->alpha_dithering_)) { + goto Error; + } + } + } + } + + // Return a pointer to the current decoded row. + return dec->alpha_plane_ + row * width; + + Error: + WebPDeallocateAlphaMemory(dec); + return NULL; +} diff --git a/3rdparty/libwebp/dec/alphai_dec.h b/3rdparty/libwebp/dec/alphai_dec.h new file mode 100644 index 0000000000..561e8151ee --- /dev/null +++ b/3rdparty/libwebp/dec/alphai_dec.h @@ -0,0 +1,54 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Alpha decoder: internal header. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_DEC_ALPHAI_H_ +#define WEBP_DEC_ALPHAI_H_ + +#include "./webpi_dec.h" +#include "../utils/filters_utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct VP8LDecoder; // Defined in dec/vp8li.h. + +typedef struct ALPHDecoder ALPHDecoder; +struct ALPHDecoder { + int width_; + int height_; + int method_; + WEBP_FILTER_TYPE filter_; + int pre_processing_; + struct VP8LDecoder* vp8l_dec_; + VP8Io io_; + int use_8b_decode_; // Although alpha channel requires only 1 byte per + // pixel, sometimes VP8LDecoder may need to allocate + // 4 bytes per pixel internally during decode. + uint8_t* output_; + const uint8_t* prev_line_; // last output row (or NULL) +}; + +//------------------------------------------------------------------------------ +// internal functions. Not public. + +// Deallocate memory associated to dec->alpha_plane_ decoding +void WebPDeallocateAlphaMemory(VP8Decoder* const dec); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_DEC_ALPHAI_H_ */ diff --git a/3rdparty/libwebp/dec/buffer.c b/3rdparty/libwebp/dec/buffer_dec.c similarity index 58% rename from 3rdparty/libwebp/dec/buffer.c rename to 3rdparty/libwebp/dec/buffer_dec.c index 3855715239..c685fd5646 100644 --- a/3rdparty/libwebp/dec/buffer.c +++ b/3rdparty/libwebp/dec/buffer_dec.c @@ -13,14 +13,10 @@ #include -#include "./vp8i.h" -#include "./webpi.h" +#include "./vp8i_dec.h" +#include "./webpi_dec.h" #include "../utils/utils.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - //------------------------------------------------------------------------------ // WebPDecBuffer @@ -37,6 +33,11 @@ static int IsValidColorspace(int webp_csp_mode) { return (webp_csp_mode >= MODE_RGB && webp_csp_mode < MODE_LAST); } +// strictly speaking, the very last (or first, if flipped) row +// doesn't require padding. +#define MIN_BUFFER_SIZE(WIDTH, HEIGHT, STRIDE) \ + (uint64_t)(STRIDE) * ((HEIGHT) - 1) + (WIDTH) + static VP8StatusCode CheckDecBuffer(const WebPDecBuffer* const buffer) { int ok = 1; const WEBP_CSP_MODE mode = buffer->colorspace; @@ -46,33 +47,41 @@ static VP8StatusCode CheckDecBuffer(const WebPDecBuffer* const buffer) { ok = 0; } else if (!WebPIsRGBMode(mode)) { // YUV checks const WebPYUVABuffer* const buf = &buffer->u.YUVA; - const uint64_t y_size = (uint64_t)buf->y_stride * height; - const uint64_t u_size = (uint64_t)buf->u_stride * ((height + 1) / 2); - const uint64_t v_size = (uint64_t)buf->v_stride * ((height + 1) / 2); - const uint64_t a_size = (uint64_t)buf->a_stride * height; + const int uv_width = (width + 1) / 2; + const int uv_height = (height + 1) / 2; + const int y_stride = abs(buf->y_stride); + const int u_stride = abs(buf->u_stride); + const int v_stride = abs(buf->v_stride); + const int a_stride = abs(buf->a_stride); + const uint64_t y_size = MIN_BUFFER_SIZE(width, height, y_stride); + const uint64_t u_size = MIN_BUFFER_SIZE(uv_width, uv_height, u_stride); + const uint64_t v_size = MIN_BUFFER_SIZE(uv_width, uv_height, v_stride); + const uint64_t a_size = MIN_BUFFER_SIZE(width, height, a_stride); ok &= (y_size <= buf->y_size); ok &= (u_size <= buf->u_size); ok &= (v_size <= buf->v_size); - ok &= (buf->y_stride >= width); - ok &= (buf->u_stride >= (width + 1) / 2); - ok &= (buf->v_stride >= (width + 1) / 2); + ok &= (y_stride >= width); + ok &= (u_stride >= uv_width); + ok &= (v_stride >= uv_width); ok &= (buf->y != NULL); ok &= (buf->u != NULL); ok &= (buf->v != NULL); if (mode == MODE_YUVA) { - ok &= (buf->a_stride >= width); + ok &= (a_stride >= width); ok &= (a_size <= buf->a_size); ok &= (buf->a != NULL); } } else { // RGB checks const WebPRGBABuffer* const buf = &buffer->u.RGBA; - const uint64_t size = (uint64_t)buf->stride * height; + const int stride = abs(buf->stride); + const uint64_t size = MIN_BUFFER_SIZE(width, height, stride); ok &= (size <= buf->size); - ok &= (buf->stride >= width * kModeBpp[mode]); + ok &= (stride >= width * kModeBpp[mode]); ok &= (buf->rgba != NULL); } return ok ? VP8_STATUS_OK : VP8_STATUS_INVALID_PARAM; } +#undef MIN_BUFFER_SIZE static VP8StatusCode AllocateBuffer(WebPDecBuffer* const buffer) { const int w = buffer->width; @@ -83,7 +92,7 @@ static VP8StatusCode AllocateBuffer(WebPDecBuffer* const buffer) { return VP8_STATUS_INVALID_PARAM; } - if (!buffer->is_external_memory && buffer->private_memory == NULL) { + if (buffer->is_external_memory <= 0 && buffer->private_memory == NULL) { uint8_t* output; int uv_stride = 0, a_stride = 0; uint64_t uv_size = 0, a_size = 0, total_size; @@ -135,9 +144,35 @@ static VP8StatusCode AllocateBuffer(WebPDecBuffer* const buffer) { return CheckDecBuffer(buffer); } +VP8StatusCode WebPFlipBuffer(WebPDecBuffer* const buffer) { + if (buffer == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + if (WebPIsRGBMode(buffer->colorspace)) { + WebPRGBABuffer* const buf = &buffer->u.RGBA; + buf->rgba += (buffer->height - 1) * buf->stride; + buf->stride = -buf->stride; + } else { + WebPYUVABuffer* const buf = &buffer->u.YUVA; + const int H = buffer->height; + buf->y += (H - 1) * buf->y_stride; + buf->y_stride = -buf->y_stride; + buf->u += ((H - 1) >> 1) * buf->u_stride; + buf->u_stride = -buf->u_stride; + buf->v += ((H - 1) >> 1) * buf->v_stride; + buf->v_stride = -buf->v_stride; + if (buf->a != NULL) { + buf->a += (H - 1) * buf->a_stride; + buf->a_stride = -buf->a_stride; + } + } + return VP8_STATUS_OK; +} + VP8StatusCode WebPAllocateDecBuffer(int w, int h, const WebPDecoderOptions* const options, WebPDecBuffer* const out) { + VP8StatusCode status; if (out == NULL || w <= 0 || h <= 0) { return VP8_STATUS_INVALID_PARAM; } @@ -154,18 +189,28 @@ VP8StatusCode WebPAllocateDecBuffer(int w, int h, h = ch; } if (options->use_scaling) { - if (options->scaled_width <= 0 || options->scaled_height <= 0) { + int scaled_width = options->scaled_width; + int scaled_height = options->scaled_height; + if (!WebPRescalerGetScaledDimensions( + w, h, &scaled_width, &scaled_height)) { return VP8_STATUS_INVALID_PARAM; } - w = options->scaled_width; - h = options->scaled_height; + w = scaled_width; + h = scaled_height; } } out->width = w; out->height = h; - // Then, allocate buffer for real - return AllocateBuffer(out); + // Then, allocate buffer for real. + status = AllocateBuffer(out); + if (status != VP8_STATUS_OK) return status; + + // Use the stride trick if vertical flip is needed. + if (options != NULL && options->flip) { + status = WebPFlipBuffer(out); + } + return status; } //------------------------------------------------------------------------------ @@ -182,8 +227,9 @@ int WebPInitDecBufferInternal(WebPDecBuffer* buffer, int version) { void WebPFreeDecBuffer(WebPDecBuffer* buffer) { if (buffer != NULL) { - if (!buffer->is_external_memory) - free(buffer->private_memory); + if (buffer->is_external_memory <= 0) { + WebPSafeFree(buffer->private_memory); + } buffer->private_memory = NULL; } } @@ -210,8 +256,45 @@ void WebPGrabDecBuffer(WebPDecBuffer* const src, WebPDecBuffer* const dst) { } } -//------------------------------------------------------------------------------ +VP8StatusCode WebPCopyDecBufferPixels(const WebPDecBuffer* const src_buf, + WebPDecBuffer* const dst_buf) { + assert(src_buf != NULL && dst_buf != NULL); + assert(src_buf->colorspace == dst_buf->colorspace); -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif + dst_buf->width = src_buf->width; + dst_buf->height = src_buf->height; + if (CheckDecBuffer(dst_buf) != VP8_STATUS_OK) { + return VP8_STATUS_INVALID_PARAM; + } + if (WebPIsRGBMode(src_buf->colorspace)) { + const WebPRGBABuffer* const src = &src_buf->u.RGBA; + const WebPRGBABuffer* const dst = &dst_buf->u.RGBA; + WebPCopyPlane(src->rgba, src->stride, dst->rgba, dst->stride, + src_buf->width * kModeBpp[src_buf->colorspace], + src_buf->height); + } else { + const WebPYUVABuffer* const src = &src_buf->u.YUVA; + const WebPYUVABuffer* const dst = &dst_buf->u.YUVA; + WebPCopyPlane(src->y, src->y_stride, dst->y, dst->y_stride, + src_buf->width, src_buf->height); + WebPCopyPlane(src->u, src->u_stride, dst->u, dst->u_stride, + (src_buf->width + 1) / 2, (src_buf->height + 1) / 2); + WebPCopyPlane(src->v, src->v_stride, dst->v, dst->v_stride, + (src_buf->width + 1) / 2, (src_buf->height + 1) / 2); + if (WebPIsAlphaMode(src_buf->colorspace)) { + WebPCopyPlane(src->a, src->a_stride, dst->a, dst->a_stride, + src_buf->width, src_buf->height); + } + } + return VP8_STATUS_OK; +} + +int WebPAvoidSlowMemory(const WebPDecBuffer* const output, + const WebPBitstreamFeatures* const features) { + assert(output != NULL); + return (output->is_external_memory >= 2) && + WebPIsPremultipliedMode(output->colorspace) && + (features != NULL && features->has_alpha); +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/dec/common_dec.h b/3rdparty/libwebp/dec/common_dec.h new file mode 100644 index 0000000000..6961e22470 --- /dev/null +++ b/3rdparty/libwebp/dec/common_dec.h @@ -0,0 +1,54 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Definitions and macros common to encoding and decoding +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DEC_COMMON_H_ +#define WEBP_DEC_COMMON_H_ + +// intra prediction modes +enum { B_DC_PRED = 0, // 4x4 modes + B_TM_PRED = 1, + B_VE_PRED = 2, + B_HE_PRED = 3, + B_RD_PRED = 4, + B_VR_PRED = 5, + B_LD_PRED = 6, + B_VL_PRED = 7, + B_HD_PRED = 8, + B_HU_PRED = 9, + NUM_BMODES = B_HU_PRED + 1 - B_DC_PRED, // = 10 + + // Luma16 or UV modes + DC_PRED = B_DC_PRED, V_PRED = B_VE_PRED, + H_PRED = B_HE_PRED, TM_PRED = B_TM_PRED, + B_PRED = NUM_BMODES, // refined I4x4 mode + NUM_PRED_MODES = 4, + + // special modes + B_DC_PRED_NOTOP = 4, + B_DC_PRED_NOLEFT = 5, + B_DC_PRED_NOTOPLEFT = 6, + NUM_B_DC_MODES = 7 }; + +enum { MB_FEATURE_TREE_PROBS = 3, + NUM_MB_SEGMENTS = 4, + NUM_REF_LF_DELTAS = 4, + NUM_MODE_LF_DELTAS = 4, // I4x4, ZERO, *, SPLIT + MAX_NUM_PARTITIONS = 8, + // Probabilities + NUM_TYPES = 4, // 0: i16-AC, 1: i16-DC, 2:chroma-AC, 3:i4-AC + NUM_BANDS = 8, + NUM_CTX = 3, + NUM_PROBAS = 11 + }; + +#endif // WEBP_DEC_COMMON_H_ diff --git a/3rdparty/libwebp/dec/frame.c b/3rdparty/libwebp/dec/frame_dec.c similarity index 58% rename from 3rdparty/libwebp/dec/frame.c rename to 3rdparty/libwebp/dec/frame_dec.c index 5f6a7d9823..f91e27f7c8 100644 --- a/3rdparty/libwebp/dec/frame.c +++ b/3rdparty/libwebp/dec/frame_dec.c @@ -12,14 +12,183 @@ // Author: Skal (pascal.massimino@gmail.com) #include -#include "./vp8i.h" +#include "./vp8i_dec.h" #include "../utils/utils.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +//------------------------------------------------------------------------------ +// Main reconstruction function. -#define ALIGN_MASK (32 - 1) +static const int kScan[16] = { + 0 + 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS, + 0 + 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS, + 0 + 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS, + 0 + 12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS +}; + +static int CheckMode(int mb_x, int mb_y, int mode) { + if (mode == B_DC_PRED) { + if (mb_x == 0) { + return (mb_y == 0) ? B_DC_PRED_NOTOPLEFT : B_DC_PRED_NOLEFT; + } else { + return (mb_y == 0) ? B_DC_PRED_NOTOP : B_DC_PRED; + } + } + return mode; +} + +static void Copy32b(uint8_t* const dst, const uint8_t* const src) { + memcpy(dst, src, 4); +} + +static WEBP_INLINE void DoTransform(uint32_t bits, const int16_t* const src, + uint8_t* const dst) { + switch (bits >> 30) { + case 3: + VP8Transform(src, dst, 0); + break; + case 2: + VP8TransformAC3(src, dst); + break; + case 1: + VP8TransformDC(src, dst); + break; + default: + break; + } +} + +static void DoUVTransform(uint32_t bits, const int16_t* const src, + uint8_t* const dst) { + if (bits & 0xff) { // any non-zero coeff at all? + if (bits & 0xaa) { // any non-zero AC coefficient? + VP8TransformUV(src, dst); // note we don't use the AC3 variant for U/V + } else { + VP8TransformDCUV(src, dst); + } + } +} + +static void ReconstructRow(const VP8Decoder* const dec, + const VP8ThreadContext* ctx) { + int j; + int mb_x; + const int mb_y = ctx->mb_y_; + const int cache_id = ctx->id_; + uint8_t* const y_dst = dec->yuv_b_ + Y_OFF; + uint8_t* const u_dst = dec->yuv_b_ + U_OFF; + uint8_t* const v_dst = dec->yuv_b_ + V_OFF; + + // Initialize left-most block. + for (j = 0; j < 16; ++j) { + y_dst[j * BPS - 1] = 129; + } + for (j = 0; j < 8; ++j) { + u_dst[j * BPS - 1] = 129; + v_dst[j * BPS - 1] = 129; + } + + // Init top-left sample on left column too. + if (mb_y > 0) { + y_dst[-1 - BPS] = u_dst[-1 - BPS] = v_dst[-1 - BPS] = 129; + } else { + // we only need to do this init once at block (0,0). + // Afterward, it remains valid for the whole topmost row. + memset(y_dst - BPS - 1, 127, 16 + 4 + 1); + memset(u_dst - BPS - 1, 127, 8 + 1); + memset(v_dst - BPS - 1, 127, 8 + 1); + } + + // Reconstruct one row. + for (mb_x = 0; mb_x < dec->mb_w_; ++mb_x) { + const VP8MBData* const block = ctx->mb_data_ + mb_x; + + // Rotate in the left samples from previously decoded block. We move four + // pixels at a time for alignment reason, and because of in-loop filter. + if (mb_x > 0) { + for (j = -1; j < 16; ++j) { + Copy32b(&y_dst[j * BPS - 4], &y_dst[j * BPS + 12]); + } + for (j = -1; j < 8; ++j) { + Copy32b(&u_dst[j * BPS - 4], &u_dst[j * BPS + 4]); + Copy32b(&v_dst[j * BPS - 4], &v_dst[j * BPS + 4]); + } + } + { + // bring top samples into the cache + VP8TopSamples* const top_yuv = dec->yuv_t_ + mb_x; + const int16_t* const coeffs = block->coeffs_; + uint32_t bits = block->non_zero_y_; + int n; + + if (mb_y > 0) { + memcpy(y_dst - BPS, top_yuv[0].y, 16); + memcpy(u_dst - BPS, top_yuv[0].u, 8); + memcpy(v_dst - BPS, top_yuv[0].v, 8); + } + + // predict and add residuals + if (block->is_i4x4_) { // 4x4 + uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16); + + if (mb_y > 0) { + if (mb_x >= dec->mb_w_ - 1) { // on rightmost border + memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); + } else { + memcpy(top_right, top_yuv[1].y, sizeof(*top_right)); + } + } + // replicate the top-right pixels below + top_right[BPS] = top_right[2 * BPS] = top_right[3 * BPS] = top_right[0]; + + // predict and add residuals for all 4x4 blocks in turn. + for (n = 0; n < 16; ++n, bits <<= 2) { + uint8_t* const dst = y_dst + kScan[n]; + VP8PredLuma4[block->imodes_[n]](dst); + DoTransform(bits, coeffs + n * 16, dst); + } + } else { // 16x16 + const int pred_func = CheckMode(mb_x, mb_y, block->imodes_[0]); + VP8PredLuma16[pred_func](y_dst); + if (bits != 0) { + for (n = 0; n < 16; ++n, bits <<= 2) { + DoTransform(bits, coeffs + n * 16, y_dst + kScan[n]); + } + } + } + { + // Chroma + const uint32_t bits_uv = block->non_zero_uv_; + const int pred_func = CheckMode(mb_x, mb_y, block->uvmode_); + VP8PredChroma8[pred_func](u_dst); + VP8PredChroma8[pred_func](v_dst); + DoUVTransform(bits_uv >> 0, coeffs + 16 * 16, u_dst); + DoUVTransform(bits_uv >> 8, coeffs + 20 * 16, v_dst); + } + + // stash away top samples for next block + if (mb_y < dec->mb_h_ - 1) { + memcpy(top_yuv[0].y, y_dst + 15 * BPS, 16); + memcpy(top_yuv[0].u, u_dst + 7 * BPS, 8); + memcpy(top_yuv[0].v, v_dst + 7 * BPS, 8); + } + } + // Transfer reconstructed samples from yuv_b_ cache to final destination. + { + const int y_offset = cache_id * 16 * dec->cache_y_stride_; + const int uv_offset = cache_id * 8 * dec->cache_uv_stride_; + uint8_t* const y_out = dec->cache_y_ + mb_x * 16 + y_offset; + uint8_t* const u_out = dec->cache_u_ + mb_x * 8 + uv_offset; + uint8_t* const v_out = dec->cache_v_ + mb_x * 8 + uv_offset; + for (j = 0; j < 16; ++j) { + memcpy(y_out + j * dec->cache_y_stride_, y_dst + j * BPS, 16); + } + for (j = 0; j < 8; ++j) { + memcpy(u_out + j * dec->cache_uv_stride_, u_dst + j * BPS, 8); + memcpy(v_out + j * dec->cache_uv_stride_, v_dst + j * BPS, 8); + } + } + } +} //------------------------------------------------------------------------------ // Filtering @@ -31,25 +200,18 @@ extern "C" { // U/V, so it's 8 samples total (because of the 2x upsampling). static const uint8_t kFilterExtraRows[3] = { 0, 2, 8 }; -static WEBP_INLINE int hev_thresh_from_level(int level, int keyframe) { - if (keyframe) { - return (level >= 40) ? 2 : (level >= 15) ? 1 : 0; - } else { - return (level >= 40) ? 3 : (level >= 20) ? 2 : (level >= 15) ? 1 : 0; - } -} - static void DoFilter(const VP8Decoder* const dec, int mb_x, int mb_y) { const VP8ThreadContext* const ctx = &dec->thread_ctx_; + const int cache_id = ctx->id_; const int y_bps = dec->cache_y_stride_; - VP8FInfo* const f_info = ctx->f_info_ + mb_x; - uint8_t* const y_dst = dec->cache_y_ + ctx->id_ * 16 * y_bps + mb_x * 16; - const int level = f_info->f_level_; + const VP8FInfo* const f_info = ctx->f_info_ + mb_x; + uint8_t* const y_dst = dec->cache_y_ + cache_id * 16 * y_bps + mb_x * 16; const int ilevel = f_info->f_ilevel_; - const int limit = 2 * level + ilevel; - if (level == 0) { + const int limit = f_info->f_limit_; + if (limit == 0) { return; } + assert(limit >= 3); if (dec->filter_type_ == 1) { // simple if (mb_x > 0) { VP8SimpleHFilter16(y_dst, y_bps, limit + 4); @@ -65,10 +227,9 @@ static void DoFilter(const VP8Decoder* const dec, int mb_x, int mb_y) { } } else { // complex const int uv_bps = dec->cache_uv_stride_; - uint8_t* const u_dst = dec->cache_u_ + ctx->id_ * 8 * uv_bps + mb_x * 8; - uint8_t* const v_dst = dec->cache_v_ + ctx->id_ * 8 * uv_bps + mb_x * 8; - const int hev_thresh = - hev_thresh_from_level(level, dec->frm_hdr_.key_frame_); + uint8_t* const u_dst = dec->cache_u_ + cache_id * 8 * uv_bps + mb_x * 8; + uint8_t* const v_dst = dec->cache_v_ + cache_id * 8 * uv_bps + mb_x * 8; + const int hev_thresh = f_info->hev_thresh_; if (mb_x > 0) { VP8HFilter16(y_dst, y_bps, limit + 4, ilevel, hev_thresh); VP8HFilter8(u_dst, v_dst, uv_bps, limit + 4, ilevel, hev_thresh); @@ -121,32 +282,110 @@ static void PrecomputeFilterStrengths(VP8Decoder* const dec) { VP8FInfo* const info = &dec->fstrengths_[s][i4x4]; int level = base_level; if (hdr->use_lf_delta_) { - // TODO(skal): only CURRENT is handled for now. level += hdr->ref_lf_delta_[0]; if (i4x4) { level += hdr->mode_lf_delta_[0]; } } level = (level < 0) ? 0 : (level > 63) ? 63 : level; - info->f_level_ = level; - - if (hdr->sharpness_ > 0) { - if (hdr->sharpness_ > 4) { - level >>= 2; - } else { - level >>= 1; - } - if (level > 9 - hdr->sharpness_) { - level = 9 - hdr->sharpness_; + if (level > 0) { + int ilevel = level; + if (hdr->sharpness_ > 0) { + if (hdr->sharpness_ > 4) { + ilevel >>= 2; + } else { + ilevel >>= 1; + } + if (ilevel > 9 - hdr->sharpness_) { + ilevel = 9 - hdr->sharpness_; + } } + if (ilevel < 1) ilevel = 1; + info->f_ilevel_ = ilevel; + info->f_limit_ = 2 * level + ilevel; + info->hev_thresh_ = (level >= 40) ? 2 : (level >= 15) ? 1 : 0; + } else { + info->f_limit_ = 0; // no filtering } - info->f_ilevel_ = (level < 1) ? 1 : level; - info->f_inner_ = 0; + info->f_inner_ = i4x4; } } } } +//------------------------------------------------------------------------------ +// Dithering + +// minimal amp that will provide a non-zero dithering effect +#define MIN_DITHER_AMP 4 + +#define DITHER_AMP_TAB_SIZE 12 +static const int kQuantToDitherAmp[DITHER_AMP_TAB_SIZE] = { + // roughly, it's dqm->uv_mat_[1] + 8, 7, 6, 4, 4, 2, 2, 2, 1, 1, 1, 1 +}; + +void VP8InitDithering(const WebPDecoderOptions* const options, + VP8Decoder* const dec) { + assert(dec != NULL); + if (options != NULL) { + const int d = options->dithering_strength; + const int max_amp = (1 << VP8_RANDOM_DITHER_FIX) - 1; + const int f = (d < 0) ? 0 : (d > 100) ? max_amp : (d * max_amp / 100); + if (f > 0) { + int s; + int all_amp = 0; + for (s = 0; s < NUM_MB_SEGMENTS; ++s) { + VP8QuantMatrix* const dqm = &dec->dqm_[s]; + if (dqm->uv_quant_ < DITHER_AMP_TAB_SIZE) { + // TODO(skal): should we specially dither more for uv_quant_ < 0? + const int idx = (dqm->uv_quant_ < 0) ? 0 : dqm->uv_quant_; + dqm->dither_ = (f * kQuantToDitherAmp[idx]) >> 3; + } + all_amp |= dqm->dither_; + } + if (all_amp != 0) { + VP8InitRandom(&dec->dithering_rg_, 1.0f); + dec->dither_ = 1; + } + } + // potentially allow alpha dithering + dec->alpha_dithering_ = options->alpha_dithering_strength; + if (dec->alpha_dithering_ > 100) { + dec->alpha_dithering_ = 100; + } else if (dec->alpha_dithering_ < 0) { + dec->alpha_dithering_ = 0; + } + } +} + +// Convert to range: [-2,2] for dither=50, [-4,4] for dither=100 +static void Dither8x8(VP8Random* const rg, uint8_t* dst, int bps, int amp) { + uint8_t dither[64]; + int i; + for (i = 0; i < 8 * 8; ++i) { + dither[i] = VP8RandomBits2(rg, VP8_DITHER_AMP_BITS + 1, amp); + } + VP8DitherCombine8x8(dither, dst, bps); +} + +static void DitherRow(VP8Decoder* const dec) { + int mb_x; + assert(dec->dither_); + for (mb_x = dec->tl_mb_x_; mb_x < dec->br_mb_x_; ++mb_x) { + const VP8ThreadContext* const ctx = &dec->thread_ctx_; + const VP8MBData* const data = ctx->mb_data_ + mb_x; + const int cache_id = ctx->id_; + const int uv_bps = dec->cache_uv_stride_; + if (data->dither_ >= MIN_DITHER_AMP) { + uint8_t* const u_dst = dec->cache_u_ + cache_id * 8 * uv_bps + mb_x * 8; + uint8_t* const v_dst = dec->cache_v_ + cache_id * 8 * uv_bps + mb_x * 8; + Dither8x8(&dec->dithering_rg_, u_dst, uv_bps, data->dither_); + Dither8x8(&dec->dithering_rg_, v_dst, uv_bps, data->dither_); + } + } +} + //------------------------------------------------------------------------------ // This function is called after a row of macroblocks is finished decoding. // It also takes into account the following restrictions: @@ -164,25 +403,35 @@ static void PrecomputeFilterStrengths(VP8Decoder* const dec) { static int FinishRow(VP8Decoder* const dec, VP8Io* const io) { int ok = 1; const VP8ThreadContext* const ctx = &dec->thread_ctx_; + const int cache_id = ctx->id_; const int extra_y_rows = kFilterExtraRows[dec->filter_type_]; const int ysize = extra_y_rows * dec->cache_y_stride_; const int uvsize = (extra_y_rows / 2) * dec->cache_uv_stride_; - const int y_offset = ctx->id_ * 16 * dec->cache_y_stride_; - const int uv_offset = ctx->id_ * 8 * dec->cache_uv_stride_; + const int y_offset = cache_id * 16 * dec->cache_y_stride_; + const int uv_offset = cache_id * 8 * dec->cache_uv_stride_; uint8_t* const ydst = dec->cache_y_ - ysize + y_offset; uint8_t* const udst = dec->cache_u_ - uvsize + uv_offset; uint8_t* const vdst = dec->cache_v_ - uvsize + uv_offset; - const int first_row = (ctx->mb_y_ == 0); - const int last_row = (ctx->mb_y_ >= dec->br_mb_y_ - 1); - int y_start = MACROBLOCK_VPOS(ctx->mb_y_); - int y_end = MACROBLOCK_VPOS(ctx->mb_y_ + 1); + const int mb_y = ctx->mb_y_; + const int is_first_row = (mb_y == 0); + const int is_last_row = (mb_y >= dec->br_mb_y_ - 1); + + if (dec->mt_method_ == 2) { + ReconstructRow(dec, ctx); + } if (ctx->filter_row_) { FilterRow(dec); } - if (io->put) { - if (!first_row) { + if (dec->dither_) { + DitherRow(dec); + } + + if (io->put != NULL) { + int y_start = MACROBLOCK_VPOS(mb_y); + int y_end = MACROBLOCK_VPOS(mb_y + 1); + if (!is_first_row) { y_start -= extra_y_rows; io->y = ydst; io->u = udst; @@ -193,7 +442,7 @@ static int FinishRow(VP8Decoder* const dec, VP8Io* const io) { io->v = dec->cache_v_ + uv_offset; } - if (!last_row) { + if (!is_last_row) { y_end -= extra_y_rows; } if (y_end > io->crop_bottom) { @@ -201,12 +450,9 @@ static int FinishRow(VP8Decoder* const dec, VP8Io* const io) { } io->a = NULL; if (dec->alpha_data_ != NULL && y_start < y_end) { - // TODO(skal): several things to correct here: - // * testing presence of alpha with dec->alpha_data_ is not a good idea - // * we're actually decompressing the full plane only once. It should be - // more obvious from signature. - // * we could free alpha_data_ right after this call, but we don't own. - io->a = VP8DecompressAlphaRows(dec, y_start, y_end - y_start); + // TODO(skal): testing presence of alpha with dec->alpha_data_ is not a + // good idea. + io->a = VP8DecompressAlphaRows(dec, io, y_start, y_end - y_start); if (io->a == NULL) { return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, "Could not decode alpha data."); @@ -237,8 +483,8 @@ static int FinishRow(VP8Decoder* const dec, VP8Io* const io) { } } // rotate top samples if needed - if (ctx->id_ + 1 == dec->num_caches_) { - if (!last_row) { + if (cache_id + 1 == dec->num_caches_) { + if (!is_last_row) { memcpy(dec->cache_y_ - ysize, ydst + 16 * dec->cache_y_stride_, ysize); memcpy(dec->cache_u_ - uvsize, udst + 8 * dec->cache_uv_stride_, uvsize); memcpy(dec->cache_v_ - uvsize, vdst + 8 * dec->cache_uv_stride_, uvsize); @@ -255,27 +501,40 @@ static int FinishRow(VP8Decoder* const dec, VP8Io* const io) { int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io) { int ok = 1; VP8ThreadContext* const ctx = &dec->thread_ctx_; - if (!dec->use_threads_) { + const int filter_row = + (dec->filter_type_ > 0) && + (dec->mb_y_ >= dec->tl_mb_y_) && (dec->mb_y_ <= dec->br_mb_y_); + if (dec->mt_method_ == 0) { // ctx->id_ and ctx->f_info_ are already set ctx->mb_y_ = dec->mb_y_; - ctx->filter_row_ = dec->filter_row_; + ctx->filter_row_ = filter_row; + ReconstructRow(dec, ctx); ok = FinishRow(dec, io); } else { WebPWorker* const worker = &dec->worker_; // Finish previous job *before* updating context - ok &= WebPWorkerSync(worker); + ok &= WebPGetWorkerInterface()->Sync(worker); assert(worker->status_ == OK); if (ok) { // spawn a new deblocking/output job ctx->io_ = *io; ctx->id_ = dec->cache_id_; ctx->mb_y_ = dec->mb_y_; - ctx->filter_row_ = dec->filter_row_; - if (ctx->filter_row_) { // just swap filter info + ctx->filter_row_ = filter_row; + if (dec->mt_method_ == 2) { // swap macroblock data + VP8MBData* const tmp = ctx->mb_data_; + ctx->mb_data_ = dec->mb_data_; + dec->mb_data_ = tmp; + } else { + // perform reconstruction directly in main thread + ReconstructRow(dec, ctx); + } + if (filter_row) { // swap filter info VP8FInfo* const tmp = ctx->f_info_; ctx->f_info_ = dec->f_info_; dec->f_info_ = tmp; } - WebPWorkerLaunch(worker); + // (reconstruct)+filter in parallel + WebPGetWorkerInterface()->Launch(worker); if (++dec->cache_id_ == dec->num_caches_) { dec->cache_id_ = 0; } @@ -289,8 +548,8 @@ int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io) { VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) { // Call setup() first. This may trigger additional decoding features on 'io'. - // Note: Afterward, we must call teardown() not matter what. - if (io->setup && !io->setup(io)) { + // Note: Afterward, we must call teardown() no matter what. + if (io->setup != NULL && !io->setup(io)) { VP8SetError(dec, VP8_STATUS_USER_ABORT, "Frame setup failed"); return dec->status_; } @@ -303,7 +562,7 @@ VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) { // Define the area where we can skip in-loop filtering, in case of cropping. // - // 'Simple' filter reads two luma samples outside of the macroblock and + // 'Simple' filter reads two luma samples outside of the macroblock // and filters one. It doesn't filter the chroma samples. Hence, we can // avoid doing the in-loop filtering before crop_top/crop_left position. // For the 'Complex' filter, 3 samples are read and up to 3 are filtered. @@ -344,11 +603,11 @@ VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) { int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io) { int ok = 1; - if (dec->use_threads_) { - ok = WebPWorkerSync(&dec->worker_); + if (dec->mt_method_ > 0) { + ok = WebPGetWorkerInterface()->Sync(&dec->worker_); } - if (io->teardown) { + if (io->teardown != NULL) { io->teardown(io); } return ok; @@ -384,9 +643,9 @@ int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io) { // Initialize multi/single-thread worker static int InitThreadContext(VP8Decoder* const dec) { dec->cache_id_ = 0; - if (dec->use_threads_) { + if (dec->mt_method_ > 0) { WebPWorker* const worker = &dec->worker_; - if (!WebPWorkerReset(worker)) { + if (!WebPGetWorkerInterface()->Reset(worker)) { return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, "thread initialization failed."); } @@ -401,6 +660,28 @@ static int InitThreadContext(VP8Decoder* const dec) { return 1; } +int VP8GetThreadMethod(const WebPDecoderOptions* const options, + const WebPHeaderStructure* const headers, + int width, int height) { + if (options == NULL || options->use_threads == 0) { + return 0; + } + (void)headers; + (void)width; + (void)height; + assert(headers == NULL || !headers->is_lossless); +#if defined(WEBP_USE_THREAD) + if (width < MIN_WIDTH_FOR_THREADS) return 0; + // TODO(skal): tune the heuristic further +#if 0 + if (height < 2 * width) return 2; +#endif + return 2; +#else // !WEBP_USE_THREAD + return 0; +#endif +} + #undef MT_CACHE_LINES #undef ST_CACHE_LINES @@ -412,14 +693,15 @@ static int AllocateMemory(VP8Decoder* const dec) { const int mb_w = dec->mb_w_; // Note: we use 'size_t' when there's no overflow risk, uint64_t otherwise. const size_t intra_pred_mode_size = 4 * mb_w * sizeof(uint8_t); - const size_t top_size = (16 + 8 + 8) * mb_w; + const size_t top_size = sizeof(VP8TopSamples) * mb_w; const size_t mb_info_size = (mb_w + 1) * sizeof(VP8MB); const size_t f_info_size = (dec->filter_type_ > 0) ? - mb_w * (dec->use_threads_ ? 2 : 1) * sizeof(VP8FInfo) + mb_w * (dec->mt_method_ > 0 ? 2 : 1) * sizeof(VP8FInfo) : 0; const size_t yuv_size = YUV_SIZE * sizeof(*dec->yuv_b_); - const size_t coeffs_size = 384 * sizeof(*dec->coeffs_); + const size_t mb_data_size = + (dec->mt_method_ == 2 ? 2 : 1) * mb_w * sizeof(*dec->mb_data_); const size_t cache_height = (16 * num_caches + kFilterExtraRows[dec->filter_type_]) * 3 / 2; const size_t cache_size = top_size * cache_height; @@ -428,20 +710,20 @@ static int AllocateMemory(VP8Decoder* const dec) { (uint64_t)dec->pic_hdr_.width_ * dec->pic_hdr_.height_ : 0ULL; const uint64_t needed = (uint64_t)intra_pred_mode_size + top_size + mb_info_size + f_info_size - + yuv_size + coeffs_size - + cache_size + alpha_size + ALIGN_MASK; + + yuv_size + mb_data_size + + cache_size + alpha_size + WEBP_ALIGN_CST; uint8_t* mem; if (needed != (size_t)needed) return 0; // check for overflow if (needed > dec->mem_size_) { - free(dec->mem_); + WebPSafeFree(dec->mem_); dec->mem_size_ = 0; dec->mem_ = WebPSafeMalloc(needed, sizeof(uint8_t)); if (dec->mem_ == NULL) { return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, "no memory during frame initialization."); } - // down-cast is ok, thanks to WebPSafeAlloc() above. + // down-cast is ok, thanks to WebPSafeMalloc() above. dec->mem_size_ = (size_t)needed; } @@ -449,12 +731,8 @@ static int AllocateMemory(VP8Decoder* const dec) { dec->intra_t_ = (uint8_t*)mem; mem += intra_pred_mode_size; - dec->y_t_ = (uint8_t*)mem; - mem += 16 * mb_w; - dec->u_t_ = (uint8_t*)mem; - mem += 8 * mb_w; - dec->v_t_ = (uint8_t*)mem; - mem += 8 * mb_w; + dec->yuv_t_ = (VP8TopSamples*)mem; + mem += top_size; dec->mb_info_ = ((VP8MB*)mem) + 1; mem += mb_info_size; @@ -463,20 +741,24 @@ static int AllocateMemory(VP8Decoder* const dec) { mem += f_info_size; dec->thread_ctx_.id_ = 0; dec->thread_ctx_.f_info_ = dec->f_info_; - if (dec->use_threads_) { + if (dec->mt_method_ > 0) { // secondary cache line. The deblocking process need to make use of the // filtering strength from previous macroblock row, while the new ones // are being decoded in parallel. We'll just swap the pointers. dec->thread_ctx_.f_info_ += mb_w; } - mem = (uint8_t*)((uintptr_t)(mem + ALIGN_MASK) & ~ALIGN_MASK); - assert((yuv_size & ALIGN_MASK) == 0); + mem = (uint8_t*)WEBP_ALIGN(mem); + assert((yuv_size & WEBP_ALIGN_CST) == 0); dec->yuv_b_ = (uint8_t*)mem; mem += yuv_size; - dec->coeffs_ = (int16_t*)mem; - mem += coeffs_size; + dec->mb_data_ = (VP8MBData*)mem; + dec->thread_ctx_.mb_data_ = (VP8MBData*)mem; + if (dec->mt_method_ == 2) { + dec->thread_ctx_.mb_data_ += mb_w; + } + mem += mb_data_size; dec->cache_y_stride_ = 16 * mb_w; dec->cache_uv_stride_ = 8 * mb_w; @@ -498,8 +780,9 @@ static int AllocateMemory(VP8Decoder* const dec) { mem += alpha_size; assert(mem <= (uint8_t*)dec->mem_ + dec->mem_size_); - // note: left-info is initialized once for all. + // note: left/top-info is initialized once for all. memset(dec->mb_info_ - 1, 0, mb_info_size); + VP8InitScanline(dec); // initialize left too. // initialize top memset(dec->intra_t_, B_DC_PRED, intra_pred_mode_size); @@ -518,7 +801,7 @@ static void InitIo(VP8Decoder* const dec, VP8Io* io) { io->a = NULL; } -int VP8InitFrame(VP8Decoder* const dec, VP8Io* io) { +int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io) { if (!InitThreadContext(dec)) return 0; // call first. Sets dec->num_caches_. if (!AllocateMemory(dec)) return 0; InitIo(dec, io); @@ -527,168 +810,3 @@ int VP8InitFrame(VP8Decoder* const dec, VP8Io* io) { } //------------------------------------------------------------------------------ -// Main reconstruction function. - -static const int kScan[16] = { - 0 + 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS, - 0 + 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS, - 0 + 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS, - 0 + 12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS -}; - -static WEBP_INLINE int CheckMode(VP8Decoder* const dec, int mode) { - if (mode == B_DC_PRED) { - if (dec->mb_x_ == 0) { - return (dec->mb_y_ == 0) ? B_DC_PRED_NOTOPLEFT : B_DC_PRED_NOLEFT; - } else { - return (dec->mb_y_ == 0) ? B_DC_PRED_NOTOP : B_DC_PRED; - } - } - return mode; -} - -static WEBP_INLINE void Copy32b(uint8_t* dst, uint8_t* src) { - *(uint32_t*)dst = *(uint32_t*)src; -} - -void VP8ReconstructBlock(VP8Decoder* const dec) { - int j; - uint8_t* const y_dst = dec->yuv_b_ + Y_OFF; - uint8_t* const u_dst = dec->yuv_b_ + U_OFF; - uint8_t* const v_dst = dec->yuv_b_ + V_OFF; - - // Rotate in the left samples from previously decoded block. We move four - // pixels at a time for alignment reason, and because of in-loop filter. - if (dec->mb_x_ > 0) { - for (j = -1; j < 16; ++j) { - Copy32b(&y_dst[j * BPS - 4], &y_dst[j * BPS + 12]); - } - for (j = -1; j < 8; ++j) { - Copy32b(&u_dst[j * BPS - 4], &u_dst[j * BPS + 4]); - Copy32b(&v_dst[j * BPS - 4], &v_dst[j * BPS + 4]); - } - } else { - for (j = 0; j < 16; ++j) { - y_dst[j * BPS - 1] = 129; - } - for (j = 0; j < 8; ++j) { - u_dst[j * BPS - 1] = 129; - v_dst[j * BPS - 1] = 129; - } - // Init top-left sample on left column too - if (dec->mb_y_ > 0) { - y_dst[-1 - BPS] = u_dst[-1 - BPS] = v_dst[-1 - BPS] = 129; - } - } - { - // bring top samples into the cache - uint8_t* const top_y = dec->y_t_ + dec->mb_x_ * 16; - uint8_t* const top_u = dec->u_t_ + dec->mb_x_ * 8; - uint8_t* const top_v = dec->v_t_ + dec->mb_x_ * 8; - const int16_t* coeffs = dec->coeffs_; - int n; - - if (dec->mb_y_ > 0) { - memcpy(y_dst - BPS, top_y, 16); - memcpy(u_dst - BPS, top_u, 8); - memcpy(v_dst - BPS, top_v, 8); - } else if (dec->mb_x_ == 0) { - // we only need to do this init once at block (0,0). - // Afterward, it remains valid for the whole topmost row. - memset(y_dst - BPS - 1, 127, 16 + 4 + 1); - memset(u_dst - BPS - 1, 127, 8 + 1); - memset(v_dst - BPS - 1, 127, 8 + 1); - } - - // predict and add residuals - - if (dec->is_i4x4_) { // 4x4 - uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16); - - if (dec->mb_y_ > 0) { - if (dec->mb_x_ >= dec->mb_w_ - 1) { // on rightmost border - top_right[0] = top_y[15] * 0x01010101u; - } else { - memcpy(top_right, top_y + 16, sizeof(*top_right)); - } - } - // replicate the top-right pixels below - top_right[BPS] = top_right[2 * BPS] = top_right[3 * BPS] = top_right[0]; - - // predict and add residues for all 4x4 blocks in turn. - for (n = 0; n < 16; n++) { - uint8_t* const dst = y_dst + kScan[n]; - VP8PredLuma4[dec->imodes_[n]](dst); - if (dec->non_zero_ac_ & (1 << n)) { - VP8Transform(coeffs + n * 16, dst, 0); - } else if (dec->non_zero_ & (1 << n)) { // only DC is present - VP8TransformDC(coeffs + n * 16, dst); - } - } - } else { // 16x16 - const int pred_func = CheckMode(dec, dec->imodes_[0]); - VP8PredLuma16[pred_func](y_dst); - if (dec->non_zero_) { - for (n = 0; n < 16; n++) { - uint8_t* const dst = y_dst + kScan[n]; - if (dec->non_zero_ac_ & (1 << n)) { - VP8Transform(coeffs + n * 16, dst, 0); - } else if (dec->non_zero_ & (1 << n)) { // only DC is present - VP8TransformDC(coeffs + n * 16, dst); - } - } - } - } - { - // Chroma - const int pred_func = CheckMode(dec, dec->uvmode_); - VP8PredChroma8[pred_func](u_dst); - VP8PredChroma8[pred_func](v_dst); - - if (dec->non_zero_ & 0x0f0000) { // chroma-U - const int16_t* const u_coeffs = dec->coeffs_ + 16 * 16; - if (dec->non_zero_ac_ & 0x0f0000) { - VP8TransformUV(u_coeffs, u_dst); - } else { - VP8TransformDCUV(u_coeffs, u_dst); - } - } - if (dec->non_zero_ & 0xf00000) { // chroma-V - const int16_t* const v_coeffs = dec->coeffs_ + 20 * 16; - if (dec->non_zero_ac_ & 0xf00000) { - VP8TransformUV(v_coeffs, v_dst); - } else { - VP8TransformDCUV(v_coeffs, v_dst); - } - } - - // stash away top samples for next block - if (dec->mb_y_ < dec->mb_h_ - 1) { - memcpy(top_y, y_dst + 15 * BPS, 16); - memcpy(top_u, u_dst + 7 * BPS, 8); - memcpy(top_v, v_dst + 7 * BPS, 8); - } - } - } - // Transfer reconstructed samples from yuv_b_ cache to final destination. - { - const int y_offset = dec->cache_id_ * 16 * dec->cache_y_stride_; - const int uv_offset = dec->cache_id_ * 8 * dec->cache_uv_stride_; - uint8_t* const y_out = dec->cache_y_ + dec->mb_x_ * 16 + y_offset; - uint8_t* const u_out = dec->cache_u_ + dec->mb_x_ * 8 + uv_offset; - uint8_t* const v_out = dec->cache_v_ + dec->mb_x_ * 8 + uv_offset; - for (j = 0; j < 16; ++j) { - memcpy(y_out + j * dec->cache_y_stride_, y_dst + j * BPS, 16); - } - for (j = 0; j < 8; ++j) { - memcpy(u_out + j * dec->cache_uv_stride_, u_dst + j * BPS, 8); - memcpy(v_out + j * dec->cache_uv_stride_, v_dst + j * BPS, 8); - } - } -} - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dec/idec.c b/3rdparty/libwebp/dec/idec_dec.c similarity index 75% rename from 3rdparty/libwebp/dec/idec.c rename to 3rdparty/libwebp/dec/idec_dec.c index 5fbf49aafe..78fb2e7186 100644 --- a/3rdparty/libwebp/dec/idec.c +++ b/3rdparty/libwebp/dec/idec_dec.c @@ -15,14 +15,11 @@ #include #include -#include "./webpi.h" -#include "./vp8i.h" +#include "./alphai_dec.h" +#include "./webpi_dec.h" +#include "./vp8i_dec.h" #include "../utils/utils.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - // In append mode, buffer allocations increase as multiples of this value. // Needs to be a power of 2. #define CHUNK_SIZE 4096 @@ -31,11 +28,13 @@ extern "C" { //------------------------------------------------------------------------------ // Data structures for memory and states -// Decoding states. State normally flows like HEADER->PARTS0->DATA->DONE. +// Decoding states. State normally flows as: +// WEBP_HEADER->VP8_HEADER->VP8_PARTS0->VP8_DATA->DONE for a lossy image, and +// WEBP_HEADER->VP8L_HEADER->VP8L_DATA->DONE for a lossless image. // If there is any error the decoder goes into state ERROR. typedef enum { - STATE_PRE_VP8, // All data before that of the first VP8 chunk. - STATE_VP8_FRAME_HEADER, // For VP8 Frame header (within VP8 chunk). + STATE_WEBP_HEADER, // All the data before that of the VP8/VP8L chunk. + STATE_VP8_HEADER, // The VP8 Frame header (within the VP8 chunk). STATE_VP8_PARTS0, STATE_VP8_DATA, STATE_VP8L_HEADER, @@ -71,30 +70,24 @@ struct WebPIDecoder { VP8Io io_; MemBuffer mem_; // input memory buffer. - WebPDecBuffer output_; // output buffer (when no external one is supplied) + WebPDecBuffer output_; // output buffer (when no external one is supplied, + // or if the external one has slow-memory) + WebPDecBuffer* final_output_; // Slow-memory output to copy to eventually. size_t chunk_size_; // Compressed VP8/VP8L size extracted from Header. + + int last_mb_y_; // last row reached for intra-mode decoding }; // MB context to restore in case VP8DecodeMB() fails typedef struct { VP8MB left_; VP8MB info_; - uint8_t intra_t_[4]; - uint8_t intra_l_[4]; - VP8BitReader br_; VP8BitReader token_br_; } MBContext; //------------------------------------------------------------------------------ // MemBuffer: incoming data handling -static void RemapBitReader(VP8BitReader* const br, ptrdiff_t offset) { - if (br->buf_ != NULL) { - br->buf_ += offset; - br->buf_end_ += offset; - } -} - static WEBP_INLINE size_t MemDataSize(const MemBuffer* mem) { return (mem->end_ - mem->start_); } @@ -102,7 +95,7 @@ static WEBP_INLINE size_t MemDataSize(const MemBuffer* mem) { // Check if we need to preserve the compressed alpha data, as it may not have // been decoded yet. static int NeedCompressedAlpha(const WebPIDecoder* const idec) { - if (idec->state_ == STATE_PRE_VP8) { + if (idec->state_ == STATE_WEBP_HEADER) { // We haven't parsed the headers yet, so we don't know whether the image is // lossy or lossless. This also means that we haven't parsed the ALPH chunk. return 0; @@ -111,7 +104,7 @@ static int NeedCompressedAlpha(const WebPIDecoder* const idec) { return 0; // ALPH chunk is not present for lossless images. } else { const VP8Decoder* const dec = (VP8Decoder*)idec->dec_; - assert(dec != NULL); // Must be true as idec->state_ != STATE_PRE_VP8. + assert(dec != NULL); // Must be true as idec->state_ != STATE_WEBP_HEADER. return (dec->alpha_data_ != NULL) && !dec->is_alpha_decoded_; } } @@ -127,21 +120,39 @@ static void DoRemap(WebPIDecoder* const idec, ptrdiff_t offset) { if (idec->dec_ != NULL) { if (!idec->is_lossless_) { VP8Decoder* const dec = (VP8Decoder*)idec->dec_; - const int last_part = dec->num_parts_ - 1; + const uint32_t last_part = dec->num_parts_minus_one_; if (offset != 0) { - int p; + uint32_t p; for (p = 0; p <= last_part; ++p) { - RemapBitReader(dec->parts_ + p, offset); + VP8RemapBitReader(dec->parts_ + p, offset); } // Remap partition #0 data pointer to new offset, but only in MAP // mode (in APPEND mode, partition #0 is copied into a fixed memory). if (mem->mode_ == MEM_MODE_MAP) { - RemapBitReader(&dec->br_, offset); + VP8RemapBitReader(&dec->br_, offset); + } + } + { + const uint8_t* const last_start = dec->parts_[last_part].buf_; + VP8BitReaderSetBuffer(&dec->parts_[last_part], last_start, + mem->buf_ + mem->end_ - last_start); + } + if (NeedCompressedAlpha(idec)) { + ALPHDecoder* const alph_dec = dec->alph_dec_; + dec->alpha_data_ += offset; + if (alph_dec != NULL) { + if (alph_dec->method_ == ALPHA_LOSSLESS_COMPRESSION) { + VP8LDecoder* const alph_vp8l_dec = alph_dec->vp8l_dec_; + assert(alph_vp8l_dec != NULL); + assert(dec->alpha_data_size_ >= ALPHA_HEADER_LEN); + VP8LBitReaderSetBuffer(&alph_vp8l_dec->br_, + dec->alpha_data_ + ALPHA_HEADER_LEN, + dec->alpha_data_size_ - ALPHA_HEADER_LEN); + } else { // alph_dec->method_ == ALPHA_NO_COMPRESSION + // Nothing special to do in this case. + } } } - assert(last_part >= 0); - dec->parts_[last_part].buf_end_ = mem->buf_ + mem->end_; - if (NeedCompressedAlpha(idec)) dec->alpha_data_ += offset; } else { // Resize lossless bitreader VP8LDecoder* const dec = (VP8LDecoder*)idec->dec_; VP8LBitReaderSetBuffer(&dec->br_, new_base, MemDataSize(mem)); @@ -175,7 +186,7 @@ static int AppendToMemBuffer(WebPIDecoder* const idec, (uint8_t*)WebPSafeMalloc(extra_size, sizeof(*new_buf)); if (new_buf == NULL) return 0; memcpy(new_buf, old_base, current_size); - free(mem->buf_); + WebPSafeFree(mem->buf_); mem->buf_ = new_buf; mem->buf_size_ = (size_t)extra_size; mem->start_ = new_mem_start; @@ -217,8 +228,8 @@ static void InitMemBuffer(MemBuffer* const mem) { static void ClearMemBuffer(MemBuffer* const mem) { assert(mem); if (mem->mode_ == MEM_MODE_APPEND) { - free(mem->buf_); - free((void*)mem->part0_buf_); + WebPSafeFree(mem->buf_); + WebPSafeFree((void*)mem->part0_buf_); } } @@ -232,35 +243,40 @@ static int CheckMemBufferMode(MemBuffer* const mem, MemBufferMode expected) { return 1; } +// To be called last. +static VP8StatusCode FinishDecoding(WebPIDecoder* const idec) { + const WebPDecoderOptions* const options = idec->params_.options; + WebPDecBuffer* const output = idec->params_.output; + + idec->state_ = STATE_DONE; + if (options != NULL && options->flip) { + const VP8StatusCode status = WebPFlipBuffer(output); + if (status != VP8_STATUS_OK) return status; + } + if (idec->final_output_ != NULL) { + WebPCopyDecBufferPixels(output, idec->final_output_); // do the slow-copy + WebPFreeDecBuffer(&idec->output_); + *output = *idec->final_output_; + idec->final_output_ = NULL; + } + return VP8_STATUS_OK; +} + //------------------------------------------------------------------------------ // Macroblock-decoding contexts static void SaveContext(const VP8Decoder* dec, const VP8BitReader* token_br, MBContext* const context) { - const VP8BitReader* const br = &dec->br_; - const VP8MB* const left = dec->mb_info_ - 1; - const VP8MB* const info = dec->mb_info_ + dec->mb_x_; - - context->left_ = *left; - context->info_ = *info; - context->br_ = *br; + context->left_ = dec->mb_info_[-1]; + context->info_ = dec->mb_info_[dec->mb_x_]; context->token_br_ = *token_br; - memcpy(context->intra_t_, dec->intra_t_ + 4 * dec->mb_x_, 4); - memcpy(context->intra_l_, dec->intra_l_, 4); } static void RestoreContext(const MBContext* context, VP8Decoder* const dec, VP8BitReader* const token_br) { - VP8BitReader* const br = &dec->br_; - VP8MB* const left = dec->mb_info_ - 1; - VP8MB* const info = dec->mb_info_ + dec->mb_x_; - - *left = context->left_; - *info = context->info_; - *br = context->br_; + dec->mb_info_[-1] = context->left_; + dec->mb_info_[dec->mb_x_] = context->info_; *token_br = context->token_br_; - memcpy(dec->intra_t_ + 4 * dec->mb_x_, context->intra_t_, 4); - memcpy(dec->intra_l_, context->intra_l_, 4); } //------------------------------------------------------------------------------ @@ -268,7 +284,7 @@ static void RestoreContext(const MBContext* context, VP8Decoder* const dec, static VP8StatusCode IDecError(WebPIDecoder* const idec, VP8StatusCode error) { if (idec->state_ == STATE_VP8_DATA) { VP8Io* const io = &idec->io_; - if (io->teardown) { + if (io->teardown != NULL) { io->teardown(io); } } @@ -296,6 +312,7 @@ static VP8StatusCode DecodeWebPHeaders(WebPIDecoder* const idec) { headers.data = data; headers.data_size = curr_size; + headers.have_all_data = 0; status = WebPParseHeaders(&headers); if (status == VP8_STATUS_NOT_ENOUGH_DATA) { return VP8_STATUS_SUSPENDED; // We haven't found a VP8 chunk yet. @@ -311,15 +328,9 @@ static VP8StatusCode DecodeWebPHeaders(WebPIDecoder* const idec) { return VP8_STATUS_OUT_OF_MEMORY; } idec->dec_ = dec; -#ifdef WEBP_USE_THREAD - dec->use_threads_ = (idec->params_.options != NULL) && - (idec->params_.options->use_threads > 0); -#else - dec->use_threads_ = 0; -#endif dec->alpha_data_ = headers.alpha_data; dec->alpha_data_size_ = headers.alpha_data_size; - ChangeState(idec, STATE_VP8_FRAME_HEADER, headers.offset); + ChangeState(idec, STATE_VP8_HEADER, headers.offset); } else { VP8LDecoder* const dec = VP8LNew(); if (dec == NULL) { @@ -334,13 +345,14 @@ static VP8StatusCode DecodeWebPHeaders(WebPIDecoder* const idec) { static VP8StatusCode DecodeVP8FrameHeader(WebPIDecoder* const idec) { const uint8_t* data = idec->mem_.buf_ + idec->mem_.start_; const size_t curr_size = MemDataSize(&idec->mem_); + int width, height; uint32_t bits; if (curr_size < VP8_FRAME_HEADER_SIZE) { // Not enough data bytes to extract VP8 Frame Header. return VP8_STATUS_SUSPENDED; } - if (!VP8GetInfo(data, curr_size, idec->chunk_size_, NULL, NULL)) { + if (!VP8GetInfo(data, curr_size, idec->chunk_size_, &width, &height)) { return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); } @@ -354,30 +366,32 @@ static VP8StatusCode DecodeVP8FrameHeader(WebPIDecoder* const idec) { } // Partition #0 -static int CopyParts0Data(WebPIDecoder* const idec) { +static VP8StatusCode CopyParts0Data(WebPIDecoder* const idec) { VP8Decoder* const dec = (VP8Decoder*)idec->dec_; VP8BitReader* const br = &dec->br_; - const size_t psize = br->buf_end_ - br->buf_; + const size_t part_size = br->buf_end_ - br->buf_; MemBuffer* const mem = &idec->mem_; assert(!idec->is_lossless_); assert(mem->part0_buf_ == NULL); - assert(psize > 0); - assert(psize <= mem->part0_size_); // Format limit: no need for runtime check + // the following is a format limitation, no need for runtime check: + assert(part_size <= mem->part0_size_); + if (part_size == 0) { // can't have zero-size partition #0 + return VP8_STATUS_BITSTREAM_ERROR; + } if (mem->mode_ == MEM_MODE_APPEND) { // We copy and grab ownership of the partition #0 data. - uint8_t* const part0_buf = (uint8_t*)malloc(psize); + uint8_t* const part0_buf = (uint8_t*)WebPSafeMalloc(1ULL, part_size); if (part0_buf == NULL) { - return 0; + return VP8_STATUS_OUT_OF_MEMORY; } - memcpy(part0_buf, br->buf_, psize); + memcpy(part0_buf, br->buf_, part_size); mem->part0_buf_ = part0_buf; - br->buf_ = part0_buf; - br->buf_end_ = part0_buf + psize; + VP8BitReaderSetBuffer(br, part0_buf, part_size); } else { // Else: just keep pointers to the partition #0's data in dec_->br_. } - mem->start_ += psize; - return 1; + mem->start_ += part_size; + return VP8_STATUS_OK; } static VP8StatusCode DecodePartition0(WebPIDecoder* const idec) { @@ -407,9 +421,14 @@ static VP8StatusCode DecodePartition0(WebPIDecoder* const idec) { if (dec->status_ != VP8_STATUS_OK) { return IDecError(idec, dec->status_); } + // This change must be done before calling VP8InitFrame() + dec->mt_method_ = VP8GetThreadMethod(params->options, NULL, + io->width, io->height); + VP8InitDithering(params->options, dec); - if (!CopyParts0Data(idec)) { - return IDecError(idec, VP8_STATUS_OUT_OF_MEMORY); + dec->status_ = CopyParts0Data(idec); + if (dec->status_ != VP8_STATUS_OK) { + return IDecError(idec, dec->status_); } // Finish setting up the decoding parameters. Will call io->setup(). @@ -433,49 +452,53 @@ static VP8StatusCode DecodeRemaining(WebPIDecoder* const idec) { VP8Io* const io = &idec->io_; assert(dec->ready_); - for (; dec->mb_y_ < dec->mb_h_; ++dec->mb_y_) { - VP8BitReader* token_br = &dec->parts_[dec->mb_y_ & (dec->num_parts_ - 1)]; - if (dec->mb_x_ == 0) { - VP8InitScanline(dec); + if (idec->last_mb_y_ != dec->mb_y_) { + if (!VP8ParseIntraModeRow(&dec->br_, dec)) { + // note: normally, error shouldn't occur since we already have the whole + // partition0 available here in DecodeRemaining(). Reaching EOF while + // reading intra modes really means a BITSTREAM_ERROR. + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + idec->last_mb_y_ = dec->mb_y_; } - for (; dec->mb_x_ < dec->mb_w_; dec->mb_x_++) { + for (; dec->mb_x_ < dec->mb_w_; ++dec->mb_x_) { + VP8BitReader* const token_br = + &dec->parts_[dec->mb_y_ & dec->num_parts_minus_one_]; MBContext context; SaveContext(dec, token_br, &context); - if (!VP8DecodeMB(dec, token_br)) { - RestoreContext(&context, dec, token_br); // We shouldn't fail when MAX_MB data was available - if (dec->num_parts_ == 1 && MemDataSize(&idec->mem_) > MAX_MB_SIZE) { + if (dec->num_parts_minus_one_ == 0 && + MemDataSize(&idec->mem_) > MAX_MB_SIZE) { return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); } + RestoreContext(&context, dec, token_br); return VP8_STATUS_SUSPENDED; } - // Reconstruct and emit samples. - VP8ReconstructBlock(dec); - // Release buffer only if there is only one partition - if (dec->num_parts_ == 1) { + if (dec->num_parts_minus_one_ == 0) { idec->mem_.start_ = token_br->buf_ - idec->mem_.buf_; assert(idec->mem_.start_ <= idec->mem_.end_); } } + VP8InitScanline(dec); // Prepare for next scanline + + // Reconstruct, filter and emit the row. if (!VP8ProcessRow(dec, io)) { return IDecError(idec, VP8_STATUS_USER_ABORT); } - dec->mb_x_ = 0; } // Synchronize the thread and check for errors. if (!VP8ExitCritical(dec, io)) { return IDecError(idec, VP8_STATUS_USER_ABORT); } dec->ready_ = 0; - idec->state_ = STATE_DONE; - - return VP8_STATUS_OK; + return FinishDecoding(idec); } -static int ErrorStatusLossless(WebPIDecoder* const idec, VP8StatusCode status) { +static VP8StatusCode ErrorStatusLossless(WebPIDecoder* const idec, + VP8StatusCode status) { if (status == VP8_STATUS_SUSPENDED || status == VP8_STATUS_NOT_ENOUGH_DATA) { return VP8_STATUS_SUSPENDED; } @@ -492,9 +515,15 @@ static VP8StatusCode DecodeVP8LHeader(WebPIDecoder* const idec) { // Wait until there's enough data for decoding header. if (curr_size < (idec->chunk_size_ >> 3)) { - return VP8_STATUS_SUSPENDED; + dec->status_ = VP8_STATUS_SUSPENDED; + return ErrorStatusLossless(idec, dec->status_); } + if (!VP8LDecodeHeader(dec, io)) { + if (dec->status_ == VP8_STATUS_BITSTREAM_ERROR && + curr_size < idec->chunk_size_) { + dec->status_ = VP8_STATUS_SUSPENDED; + } return ErrorStatusLossless(idec, dec->status_); } // Allocate/verify output buffer now. @@ -513,33 +542,29 @@ static VP8StatusCode DecodeVP8LData(WebPIDecoder* const idec) { const size_t curr_size = MemDataSize(&idec->mem_); assert(idec->is_lossless_); - // At present Lossless decoder can't decode image incrementally. So wait till - // all the image data is aggregated before image can be decoded. - if (curr_size < idec->chunk_size_) { - return VP8_STATUS_SUSPENDED; - } + // Switch to incremental decoding if we don't have all the bytes available. + dec->incremental_ = (curr_size < idec->chunk_size_); if (!VP8LDecodeImage(dec)) { return ErrorStatusLossless(idec, dec->status_); } - - idec->state_ = STATE_DONE; - - return VP8_STATUS_OK; + assert(dec->status_ == VP8_STATUS_OK || dec->status_ == VP8_STATUS_SUSPENDED); + return (dec->status_ == VP8_STATUS_SUSPENDED) ? dec->status_ + : FinishDecoding(idec); } // Main decoding loop static VP8StatusCode IDecode(WebPIDecoder* idec) { VP8StatusCode status = VP8_STATUS_SUSPENDED; - if (idec->state_ == STATE_PRE_VP8) { + if (idec->state_ == STATE_WEBP_HEADER) { status = DecodeWebPHeaders(idec); } else { if (idec->dec_ == NULL) { return VP8_STATUS_SUSPENDED; // can't continue if we have no decoder. } } - if (idec->state_ == STATE_VP8_FRAME_HEADER) { + if (idec->state_ == STATE_VP8_HEADER) { status = DecodeVP8FrameHeader(idec); } if (idec->state_ == STATE_VP8_PARTS0) { @@ -558,40 +583,65 @@ static VP8StatusCode IDecode(WebPIDecoder* idec) { } //------------------------------------------------------------------------------ -// Public functions +// Internal constructor -WebPIDecoder* WebPINewDecoder(WebPDecBuffer* output_buffer) { - WebPIDecoder* idec = (WebPIDecoder*)calloc(1, sizeof(*idec)); +static WebPIDecoder* NewDecoder(WebPDecBuffer* const output_buffer, + const WebPBitstreamFeatures* const features) { + WebPIDecoder* idec = (WebPIDecoder*)WebPSafeCalloc(1ULL, sizeof(*idec)); if (idec == NULL) { return NULL; } - idec->state_ = STATE_PRE_VP8; + idec->state_ = STATE_WEBP_HEADER; idec->chunk_size_ = 0; + idec->last_mb_y_ = -1; + InitMemBuffer(&idec->mem_); WebPInitDecBuffer(&idec->output_); VP8InitIo(&idec->io_); WebPResetDecParams(&idec->params_); - idec->params_.output = output_buffer ? output_buffer : &idec->output_; + if (output_buffer == NULL || WebPAvoidSlowMemory(output_buffer, features)) { + idec->params_.output = &idec->output_; + idec->final_output_ = output_buffer; + if (output_buffer != NULL) { + idec->params_.output->colorspace = output_buffer->colorspace; + } + } else { + idec->params_.output = output_buffer; + idec->final_output_ = NULL; + } WebPInitCustomIo(&idec->params_, &idec->io_); // Plug the I/O functions. return idec; } +//------------------------------------------------------------------------------ +// Public functions + +WebPIDecoder* WebPINewDecoder(WebPDecBuffer* output_buffer) { + return NewDecoder(output_buffer, NULL); +} + WebPIDecoder* WebPIDecode(const uint8_t* data, size_t data_size, WebPDecoderConfig* config) { WebPIDecoder* idec; + WebPBitstreamFeatures tmp_features; + WebPBitstreamFeatures* const features = + (config == NULL) ? &tmp_features : &config->input; + memset(&tmp_features, 0, sizeof(tmp_features)); // Parse the bitstream's features, if requested: - if (data != NULL && data_size > 0 && config != NULL) { - if (WebPGetFeatures(data, data_size, &config->input) != VP8_STATUS_OK) { + if (data != NULL && data_size > 0) { + if (WebPGetFeatures(data, data_size, features) != VP8_STATUS_OK) { return NULL; } } + // Create an instance of the incremental decoder - idec = WebPINewDecoder(config ? &config->output : NULL); + idec = (config != NULL) ? NewDecoder(&config->output, features) + : NewDecoder(NULL, features); if (idec == NULL) { return NULL; } @@ -606,14 +656,18 @@ void WebPIDelete(WebPIDecoder* idec) { if (idec == NULL) return; if (idec->dec_ != NULL) { if (!idec->is_lossless_) { - VP8Delete(idec->dec_); + if (idec->state_ == STATE_VP8_DATA) { + // Synchronize the thread, clean-up and check for errors. + VP8ExitCritical((VP8Decoder*)idec->dec_, &idec->io_); + } + VP8Delete((VP8Decoder*)idec->dec_); } else { - VP8LDelete(idec->dec_); + VP8LDelete((VP8LDecoder*)idec->dec_); } } ClearMemBuffer(&idec->mem_); WebPFreeDecBuffer(&idec->output_); - free(idec); + WebPSafeFree(idec); } //------------------------------------------------------------------------------ @@ -621,11 +675,11 @@ void WebPIDelete(WebPIDecoder* idec) { WebPIDecoder* WebPINewRGB(WEBP_CSP_MODE mode, uint8_t* output_buffer, size_t output_buffer_size, int output_stride) { - const int is_external_memory = (output_buffer != NULL); + const int is_external_memory = (output_buffer != NULL) ? 1 : 0; WebPIDecoder* idec; if (mode >= MODE_YUV) return NULL; - if (!is_external_memory) { // Overwrite parameters to sane values. + if (is_external_memory == 0) { // Overwrite parameters to sane values. output_buffer_size = 0; output_stride = 0; } else { // A buffer was passed. Validate the other params. @@ -647,11 +701,11 @@ WebPIDecoder* WebPINewYUVA(uint8_t* luma, size_t luma_size, int luma_stride, uint8_t* u, size_t u_size, int u_stride, uint8_t* v, size_t v_size, int v_stride, uint8_t* a, size_t a_size, int a_stride) { - const int is_external_memory = (luma != NULL); + const int is_external_memory = (luma != NULL) ? 1 : 0; WebPIDecoder* idec; WEBP_CSP_MODE colorspace; - if (!is_external_memory) { // Overwrite parameters to sane values. + if (is_external_memory == 0) { // Overwrite parameters to sane values. luma_size = u_size = v_size = a_size = 0; luma_stride = u_stride = v_stride = a_stride = 0; u = v = a = NULL; @@ -759,6 +813,9 @@ static const WebPDecBuffer* GetOutputBuffer(const WebPIDecoder* const idec) { if (idec->state_ <= STATE_VP8_PARTS0) { return NULL; } + if (idec->final_output_ != NULL) { + return NULL; // not yet slow-copied + } return idec->params_.output; } @@ -768,8 +825,7 @@ const WebPDecBuffer* WebPIDecodedArea(const WebPIDecoder* idec, const WebPDecBuffer* const src = GetOutputBuffer(idec); if (left != NULL) *left = 0; if (top != NULL) *top = 0; - // TODO(skal): later include handling of rotations. - if (src) { + if (src != NULL) { if (width != NULL) *width = src->width; if (height != NULL) *height = idec->params_.last_y; } else { @@ -823,7 +879,7 @@ int WebPISetIOHooks(WebPIDecoder* const idec, VP8IoSetupHook setup, VP8IoTeardownHook teardown, void* user_data) { - if (idec == NULL || idec->state_ > STATE_PRE_VP8) { + if (idec == NULL || idec->state_ > STATE_WEBP_HEADER) { return 0; } @@ -834,7 +890,3 @@ int WebPISetIOHooks(WebPIDecoder* const idec, return 1; } - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dec/io.c b/3rdparty/libwebp/dec/io_dec.c similarity index 66% rename from 3rdparty/libwebp/dec/io.c rename to 3rdparty/libwebp/dec/io_dec.c index 63810b4472..8bfab86959 100644 --- a/3rdparty/libwebp/dec/io.c +++ b/3rdparty/libwebp/dec/io_dec.c @@ -13,14 +13,11 @@ #include #include -#include "../dec/vp8i.h" -#include "./webpi.h" +#include "../dec/vp8i_dec.h" +#include "./webpi_dec.h" #include "../dsp/dsp.h" #include "../dsp/yuv.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "../utils/utils.h" //------------------------------------------------------------------------------ // Main YUV<->RGB conversion functions @@ -48,56 +45,16 @@ static int EmitYUV(const VP8Io* const io, WebPDecParams* const p) { // Point-sampling U/V sampler. static int EmitSampledRGB(const VP8Io* const io, WebPDecParams* const p) { - WebPDecBuffer* output = p->output; - const WebPRGBABuffer* const buf = &output->u.RGBA; - uint8_t* dst = buf->rgba + io->mb_y * buf->stride; - const uint8_t* y_src = io->y; - const uint8_t* u_src = io->u; - const uint8_t* v_src = io->v; - const WebPSampleLinePairFunc sample = WebPSamplers[output->colorspace]; - const int mb_w = io->mb_w; - const int last = io->mb_h - 1; - int j; - for (j = 0; j < last; j += 2) { - sample(y_src, y_src + io->y_stride, u_src, v_src, - dst, dst + buf->stride, mb_w); - y_src += 2 * io->y_stride; - u_src += io->uv_stride; - v_src += io->uv_stride; - dst += 2 * buf->stride; - } - if (j == last) { // Just do the last line twice - sample(y_src, y_src, u_src, v_src, dst, dst, mb_w); - } + WebPDecBuffer* const output = p->output; + WebPRGBABuffer* const buf = &output->u.RGBA; + uint8_t* const dst = buf->rgba + io->mb_y * buf->stride; + WebPSamplerProcessPlane(io->y, io->y_stride, + io->u, io->v, io->uv_stride, + dst, buf->stride, io->mb_w, io->mb_h, + WebPSamplers[output->colorspace]); return io->mb_h; } -//------------------------------------------------------------------------------ -// YUV444 -> RGB conversion - -#if 0 // TODO(skal): this is for future rescaling. -static int EmitRGB(const VP8Io* const io, WebPDecParams* const p) { - WebPDecBuffer* output = p->output; - const WebPRGBABuffer* const buf = &output->u.RGBA; - uint8_t* dst = buf->rgba + io->mb_y * buf->stride; - const uint8_t* y_src = io->y; - const uint8_t* u_src = io->u; - const uint8_t* v_src = io->v; - const WebPYUV444Converter convert = WebPYUV444Converters[output->colorspace]; - const int mb_w = io->mb_w; - const int last = io->mb_h; - int j; - for (j = 0; j < last; ++j) { - convert(y_src, u_src, v_src, dst, mb_w); - y_src += io->y_stride; - u_src += io->uv_stride; - v_src += io->uv_stride; - dst += buf->stride; - } - return io->mb_h; -} -#endif - //------------------------------------------------------------------------------ // Fancy upsampling @@ -119,7 +76,7 @@ static int EmitFancyRGB(const VP8Io* const io, WebPDecParams* const p) { if (y == 0) { // First line is special cased. We mirror the u/v samples at boundary. - upsample(NULL, cur_y, cur_u, cur_v, cur_u, cur_v, NULL, dst, mb_w); + upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, mb_w); } else { // We can finish the left-over line from previous call. upsample(p->tmp_y, cur_y, top_u, top_v, cur_u, cur_v, @@ -162,14 +119,24 @@ static int EmitFancyRGB(const VP8Io* const io, WebPDecParams* const p) { //------------------------------------------------------------------------------ -static int EmitAlphaYUV(const VP8Io* const io, WebPDecParams* const p) { +static void FillAlphaPlane(uint8_t* dst, int w, int h, int stride) { + int j; + for (j = 0; j < h; ++j) { + memset(dst, 0xff, w * sizeof(*dst)); + dst += stride; + } +} + +static int EmitAlphaYUV(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { const uint8_t* alpha = io->a; const WebPYUVABuffer* const buf = &p->output->u.YUVA; const int mb_w = io->mb_w; const int mb_h = io->mb_h; uint8_t* dst = buf->a + io->mb_y * buf->a_stride; int j; - + (void)expected_num_lines_out; + assert(expected_num_lines_out == mb_h); if (alpha != NULL) { for (j = 0; j < mb_h; ++j) { memcpy(dst, alpha, mb_w * sizeof(*dst)); @@ -178,10 +145,7 @@ static int EmitAlphaYUV(const VP8Io* const io, WebPDecParams* const p) { } } else if (buf->a != NULL) { // the user requested alpha, but there is none, set it to opaque. - for (j = 0; j < mb_h; ++j) { - memset(dst, 0xff, mb_w * sizeof(*dst)); - dst += buf->a_stride; - } + FillAlphaPlane(dst, mb_w, mb_h, buf->a_stride); } return 0; } @@ -212,7 +176,8 @@ static int GetAlphaSourceRow(const VP8Io* const io, return start_y; } -static int EmitAlphaRGB(const VP8Io* const io, WebPDecParams* const p) { +static int EmitAlphaRGB(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { const uint8_t* alpha = io->a; if (alpha != NULL) { const int mb_w = io->mb_w; @@ -223,21 +188,13 @@ static int EmitAlphaRGB(const VP8Io* const io, WebPDecParams* const p) { int num_rows; const int start_y = GetAlphaSourceRow(io, &alpha, &num_rows); uint8_t* const base_rgba = buf->rgba + start_y * buf->stride; - uint8_t* dst = base_rgba + (alpha_first ? 0 : 3); - uint32_t alpha_mask = 0xff; - int i, j; - - for (j = 0; j < num_rows; ++j) { - for (i = 0; i < mb_w; ++i) { - const uint32_t alpha_value = alpha[i]; - dst[4 * i] = alpha_value; - alpha_mask &= alpha_value; - } - alpha += io->width; - dst += buf->stride; - } - // alpha_mask is < 0xff if there's non-trivial alpha to premultiply with. - if (alpha_mask != 0xff && WebPIsPremultipliedMode(colorspace)) { + uint8_t* const dst = base_rgba + (alpha_first ? 0 : 3); + const int has_alpha = WebPDispatchAlpha(alpha, io->width, mb_w, + num_rows, dst, buf->stride); + (void)expected_num_lines_out; + assert(expected_num_lines_out == num_rows); + // has_alpha is true if there's non-trivial alpha to premultiply with. + if (has_alpha && WebPIsPremultipliedMode(colorspace)) { WebPApplyAlphaMultiply(base_rgba, alpha_first, mb_w, num_rows, buf->stride); } @@ -245,7 +202,8 @@ static int EmitAlphaRGB(const VP8Io* const io, WebPDecParams* const p) { return 0; } -static int EmitAlphaRGBA4444(const VP8Io* const io, WebPDecParams* const p) { +static int EmitAlphaRGBA4444(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { const uint8_t* alpha = io->a; if (alpha != NULL) { const int mb_w = io->mb_w; @@ -254,10 +212,13 @@ static int EmitAlphaRGBA4444(const VP8Io* const io, WebPDecParams* const p) { int num_rows; const int start_y = GetAlphaSourceRow(io, &alpha, &num_rows); uint8_t* const base_rgba = buf->rgba + start_y * buf->stride; +#ifdef WEBP_SWAP_16BIT_CSP + uint8_t* alpha_dst = base_rgba; +#else uint8_t* alpha_dst = base_rgba + 1; +#endif uint32_t alpha_mask = 0x0f; int i, j; - for (j = 0; j < num_rows; ++j) { for (i = 0; i < mb_w; ++i) { // Fill in the alpha value (converted to 4 bits). @@ -268,6 +229,8 @@ static int EmitAlphaRGBA4444(const VP8Io* const io, WebPDecParams* const p) { alpha += io->width; alpha_dst += buf->stride; } + (void)expected_num_lines_out; + assert(expected_num_lines_out == num_rows); if (alpha_mask != 0x0f && WebPIsPremultipliedMode(colorspace)) { WebPApplyAlphaMultiply4444(base_rgba, mb_w, num_rows, buf->stride); } @@ -293,15 +256,39 @@ static int Rescale(const uint8_t* src, int src_stride, static int EmitRescaledYUV(const VP8Io* const io, WebPDecParams* const p) { const int mb_h = io->mb_h; const int uv_mb_h = (mb_h + 1) >> 1; - const int num_lines_out = Rescale(io->y, io->y_stride, mb_h, &p->scaler_y); - Rescale(io->u, io->uv_stride, uv_mb_h, &p->scaler_u); - Rescale(io->v, io->uv_stride, uv_mb_h, &p->scaler_v); + WebPRescaler* const scaler = p->scaler_y; + int num_lines_out = 0; + if (WebPIsAlphaMode(p->output->colorspace) && io->a != NULL) { + // Before rescaling, we premultiply the luma directly into the io->y + // internal buffer. This is OK since these samples are not used for + // intra-prediction (the top samples are saved in cache_y_/u_/v_). + // But we need to cast the const away, though. + WebPMultRows((uint8_t*)io->y, io->y_stride, + io->a, io->width, io->mb_w, mb_h, 0); + } + num_lines_out = Rescale(io->y, io->y_stride, mb_h, scaler); + Rescale(io->u, io->uv_stride, uv_mb_h, p->scaler_u); + Rescale(io->v, io->uv_stride, uv_mb_h, p->scaler_v); return num_lines_out; } -static int EmitRescaledAlphaYUV(const VP8Io* const io, WebPDecParams* const p) { +static int EmitRescaledAlphaYUV(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { + const WebPYUVABuffer* const buf = &p->output->u.YUVA; + uint8_t* const dst_a = buf->a + p->last_y * buf->a_stride; if (io->a != NULL) { - Rescale(io->a, io->width, io->mb_h, &p->scaler_a); + uint8_t* const dst_y = buf->y + p->last_y * buf->y_stride; + const int num_lines_out = Rescale(io->a, io->width, io->mb_h, p->scaler_a); + assert(expected_num_lines_out == num_lines_out); + if (num_lines_out > 0) { // unmultiply the Y + WebPMultRows(dst_y, buf->y_stride, dst_a, buf->a_stride, + p->scaler_a->dst_width, num_lines_out, 1); + } + } else if (buf->a != NULL) { + // the user requested alpha, but there is none, set it to opaque. + assert(p->last_y + expected_num_lines_out <= io->scaled_height); + FillAlphaPlane(dst_a, io->scaled_width, expected_num_lines_out, + buf->a_stride); } return 0; } @@ -317,40 +304,46 @@ static int InitYUVRescaler(const VP8Io* const io, WebPDecParams* const p) { const int uv_in_height = (io->mb_h + 1) >> 1; const size_t work_size = 2 * out_width; // scratch memory for luma rescaler const size_t uv_work_size = 2 * uv_out_width; // and for each u/v ones - size_t tmp_size; - int32_t* work; + size_t tmp_size, rescaler_size; + rescaler_t* work; + WebPRescaler* scalers; + const int num_rescalers = has_alpha ? 4 : 3; - tmp_size = work_size + 2 * uv_work_size; + tmp_size = (work_size + 2 * uv_work_size) * sizeof(*work); if (has_alpha) { - tmp_size += work_size; + tmp_size += work_size * sizeof(*work); } - p->memory = calloc(1, tmp_size * sizeof(*work)); + rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST; + + p->memory = WebPSafeMalloc(1ULL, tmp_size + rescaler_size); if (p->memory == NULL) { return 0; // memory error } - work = (int32_t*)p->memory; - WebPRescalerInit(&p->scaler_y, io->mb_w, io->mb_h, + work = (rescaler_t*)p->memory; + + scalers = (WebPRescaler*)WEBP_ALIGN((const uint8_t*)work + tmp_size); + p->scaler_y = &scalers[0]; + p->scaler_u = &scalers[1]; + p->scaler_v = &scalers[2]; + p->scaler_a = has_alpha ? &scalers[3] : NULL; + + WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h, buf->y, out_width, out_height, buf->y_stride, 1, - io->mb_w, out_width, io->mb_h, out_height, work); - WebPRescalerInit(&p->scaler_u, uv_in_width, uv_in_height, + WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height, buf->u, uv_out_width, uv_out_height, buf->u_stride, 1, - uv_in_width, uv_out_width, - uv_in_height, uv_out_height, work + work_size); - WebPRescalerInit(&p->scaler_v, uv_in_width, uv_in_height, + WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height, buf->v, uv_out_width, uv_out_height, buf->v_stride, 1, - uv_in_width, uv_out_width, - uv_in_height, uv_out_height, work + work_size + uv_work_size); p->emit = EmitRescaledYUV; if (has_alpha) { - WebPRescalerInit(&p->scaler_a, io->mb_w, io->mb_h, + WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h, buf->a, out_width, out_height, buf->a_stride, 1, - io->mb_w, out_width, io->mb_h, out_height, work + work_size + 2 * uv_work_size); p->emit_alpha = EmitRescaledAlphaYUV; + WebPInitAlphaProcessing(); } return 1; } @@ -362,19 +355,19 @@ static int ExportRGB(WebPDecParams* const p, int y_pos) { const WebPYUV444Converter convert = WebPYUV444Converters[p->output->colorspace]; const WebPRGBABuffer* const buf = &p->output->u.RGBA; - uint8_t* dst = buf->rgba + (p->last_y + y_pos) * buf->stride; + uint8_t* dst = buf->rgba + y_pos * buf->stride; int num_lines_out = 0; // For RGB rescaling, because of the YUV420, current scan position // U/V can be +1/-1 line from the Y one. Hence the double test. - while (WebPRescalerHasPendingOutput(&p->scaler_y) && - WebPRescalerHasPendingOutput(&p->scaler_u)) { - assert(p->last_y + y_pos + num_lines_out < p->output->height); - assert(p->scaler_u.y_accum == p->scaler_v.y_accum); - WebPRescalerExportRow(&p->scaler_y); - WebPRescalerExportRow(&p->scaler_u); - WebPRescalerExportRow(&p->scaler_v); - convert(p->scaler_y.dst, p->scaler_u.dst, p->scaler_v.dst, - dst, p->scaler_y.dst_width); + while (WebPRescalerHasPendingOutput(p->scaler_y) && + WebPRescalerHasPendingOutput(p->scaler_u)) { + assert(y_pos + num_lines_out < p->output->height); + assert(p->scaler_u->y_accum == p->scaler_v->y_accum); + WebPRescalerExportRow(p->scaler_y); + WebPRescalerExportRow(p->scaler_u); + WebPRescalerExportRow(p->scaler_v); + convert(p->scaler_y->dst, p->scaler_u->dst, p->scaler_v->dst, + dst, p->scaler_y->dst_width); dst += buf->stride; ++num_lines_out; } @@ -388,71 +381,75 @@ static int EmitRescaledRGB(const VP8Io* const io, WebPDecParams* const p) { int num_lines_out = 0; while (j < mb_h) { const int y_lines_in = - WebPRescalerImport(&p->scaler_y, mb_h - j, + WebPRescalerImport(p->scaler_y, mb_h - j, io->y + j * io->y_stride, io->y_stride); - const int u_lines_in = - WebPRescalerImport(&p->scaler_u, uv_mb_h - uv_j, - io->u + uv_j * io->uv_stride, io->uv_stride); - const int v_lines_in = - WebPRescalerImport(&p->scaler_v, uv_mb_h - uv_j, - io->v + uv_j * io->uv_stride, io->uv_stride); - (void)v_lines_in; // remove a gcc warning - assert(u_lines_in == v_lines_in); j += y_lines_in; - uv_j += u_lines_in; - num_lines_out += ExportRGB(p, num_lines_out); + if (WebPRescaleNeededLines(p->scaler_u, uv_mb_h - uv_j)) { + const int u_lines_in = + WebPRescalerImport(p->scaler_u, uv_mb_h - uv_j, + io->u + uv_j * io->uv_stride, io->uv_stride); + const int v_lines_in = + WebPRescalerImport(p->scaler_v, uv_mb_h - uv_j, + io->v + uv_j * io->uv_stride, io->uv_stride); + (void)v_lines_in; // remove a gcc warning + assert(u_lines_in == v_lines_in); + uv_j += u_lines_in; + } + num_lines_out += ExportRGB(p, p->last_y + num_lines_out); } return num_lines_out; } -static int ExportAlpha(WebPDecParams* const p, int y_pos) { +static int ExportAlpha(WebPDecParams* const p, int y_pos, int max_lines_out) { const WebPRGBABuffer* const buf = &p->output->u.RGBA; - uint8_t* const base_rgba = buf->rgba + (p->last_y + y_pos) * buf->stride; + uint8_t* const base_rgba = buf->rgba + y_pos * buf->stride; const WEBP_CSP_MODE colorspace = p->output->colorspace; const int alpha_first = (colorspace == MODE_ARGB || colorspace == MODE_Argb); uint8_t* dst = base_rgba + (alpha_first ? 0 : 3); int num_lines_out = 0; const int is_premult_alpha = WebPIsPremultipliedMode(colorspace); - uint32_t alpha_mask = 0xff; - const int width = p->scaler_a.dst_width; + uint32_t non_opaque = 0; + const int width = p->scaler_a->dst_width; - while (WebPRescalerHasPendingOutput(&p->scaler_a)) { - int i; - assert(p->last_y + y_pos + num_lines_out < p->output->height); - WebPRescalerExportRow(&p->scaler_a); - for (i = 0; i < width; ++i) { - const uint32_t alpha_value = p->scaler_a.dst[i]; - dst[4 * i] = alpha_value; - alpha_mask &= alpha_value; - } + while (WebPRescalerHasPendingOutput(p->scaler_a) && + num_lines_out < max_lines_out) { + assert(y_pos + num_lines_out < p->output->height); + WebPRescalerExportRow(p->scaler_a); + non_opaque |= WebPDispatchAlpha(p->scaler_a->dst, 0, width, 1, dst, 0); dst += buf->stride; ++num_lines_out; } - if (is_premult_alpha && alpha_mask != 0xff) { + if (is_premult_alpha && non_opaque) { WebPApplyAlphaMultiply(base_rgba, alpha_first, width, num_lines_out, buf->stride); } return num_lines_out; } -static int ExportAlphaRGBA4444(WebPDecParams* const p, int y_pos) { +static int ExportAlphaRGBA4444(WebPDecParams* const p, int y_pos, + int max_lines_out) { const WebPRGBABuffer* const buf = &p->output->u.RGBA; - uint8_t* const base_rgba = buf->rgba + (p->last_y + y_pos) * buf->stride; + uint8_t* const base_rgba = buf->rgba + y_pos * buf->stride; +#ifdef WEBP_SWAP_16BIT_CSP + uint8_t* alpha_dst = base_rgba; +#else uint8_t* alpha_dst = base_rgba + 1; +#endif int num_lines_out = 0; const WEBP_CSP_MODE colorspace = p->output->colorspace; - const int width = p->scaler_a.dst_width; + const int width = p->scaler_a->dst_width; const int is_premult_alpha = WebPIsPremultipliedMode(colorspace); uint32_t alpha_mask = 0x0f; - while (WebPRescalerHasPendingOutput(&p->scaler_a)) { + while (WebPRescalerHasPendingOutput(p->scaler_a) && + num_lines_out < max_lines_out) { int i; - assert(p->last_y + y_pos + num_lines_out < p->output->height); - WebPRescalerExportRow(&p->scaler_a); + assert(y_pos + num_lines_out < p->output->height); + WebPRescalerExportRow(p->scaler_a); for (i = 0; i < width; ++i) { // Fill in the alpha value (converted to 4 bits). - const uint32_t alpha_value = p->scaler_a.dst[i] >> 4; + const uint32_t alpha_value = p->scaler_a->dst[i] >> 4; alpha_dst[2 * i] = (alpha_dst[2 * i] & 0xf0) | alpha_value; alpha_mask &= alpha_value; } @@ -465,15 +462,17 @@ static int ExportAlphaRGBA4444(WebPDecParams* const p, int y_pos) { return num_lines_out; } -static int EmitRescaledAlphaRGB(const VP8Io* const io, WebPDecParams* const p) { +static int EmitRescaledAlphaRGB(const VP8Io* const io, WebPDecParams* const p, + int expected_num_out_lines) { if (io->a != NULL) { - WebPRescaler* const scaler = &p->scaler_a; - int j = 0; - int pos = 0; - while (j < io->mb_h) { - j += WebPRescalerImport(scaler, io->mb_h - j, - io->a + j * io->width, io->width); - pos += p->emit_alpha_row(p, pos); + WebPRescaler* const scaler = p->scaler_a; + int lines_left = expected_num_out_lines; + const int y_end = p->last_y + lines_left; + while (lines_left > 0) { + const int row_offset = scaler->src_y - io->mb_y; + WebPRescalerImport(scaler, io->mb_h + io->mb_y - scaler->src_y, + io->a + row_offset * io->width, io->width); + lines_left -= p->emit_alpha_row(p, y_end - lines_left, lines_left); } } return 0; @@ -486,9 +485,11 @@ static int InitRGBRescaler(const VP8Io* const io, WebPDecParams* const p) { const int uv_in_width = (io->mb_w + 1) >> 1; const int uv_in_height = (io->mb_h + 1) >> 1; const size_t work_size = 2 * out_width; // scratch memory for one rescaler - int32_t* work; // rescalers work area + rescaler_t* work; // rescalers work area uint8_t* tmp; // tmp storage for scaled YUV444 samples before RGB conversion - size_t tmp_size1, tmp_size2; + size_t tmp_size1, tmp_size2, total_size, rescaler_size; + WebPRescaler* scalers; + const int num_rescalers = has_alpha ? 4 : 3; tmp_size1 = 3 * work_size; tmp_size2 = 3 * out_width; @@ -496,30 +497,37 @@ static int InitRGBRescaler(const VP8Io* const io, WebPDecParams* const p) { tmp_size1 += work_size; tmp_size2 += out_width; } - p->memory = calloc(1, tmp_size1 * sizeof(*work) + tmp_size2 * sizeof(*tmp)); + total_size = tmp_size1 * sizeof(*work) + tmp_size2 * sizeof(*tmp); + rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST; + + p->memory = WebPSafeMalloc(1ULL, total_size + rescaler_size); if (p->memory == NULL) { return 0; // memory error } - work = (int32_t*)p->memory; + work = (rescaler_t*)p->memory; tmp = (uint8_t*)(work + tmp_size1); - WebPRescalerInit(&p->scaler_y, io->mb_w, io->mb_h, + + scalers = (WebPRescaler*)WEBP_ALIGN((const uint8_t*)work + total_size); + p->scaler_y = &scalers[0]; + p->scaler_u = &scalers[1]; + p->scaler_v = &scalers[2]; + p->scaler_a = has_alpha ? &scalers[3] : NULL; + + WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h, tmp + 0 * out_width, out_width, out_height, 0, 1, - io->mb_w, out_width, io->mb_h, out_height, work + 0 * work_size); - WebPRescalerInit(&p->scaler_u, uv_in_width, uv_in_height, + WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height, tmp + 1 * out_width, out_width, out_height, 0, 1, - io->mb_w, 2 * out_width, io->mb_h, 2 * out_height, work + 1 * work_size); - WebPRescalerInit(&p->scaler_v, uv_in_width, uv_in_height, + WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height, tmp + 2 * out_width, out_width, out_height, 0, 1, - io->mb_w, 2 * out_width, io->mb_h, 2 * out_height, work + 2 * work_size); p->emit = EmitRescaledRGB; + WebPInitYUV444Converters(); if (has_alpha) { - WebPRescalerInit(&p->scaler_a, io->mb_w, io->mb_h, + WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h, tmp + 3 * out_width, out_width, out_height, 0, 1, - io->mb_w, out_width, io->mb_h, out_height, work + 3 * work_size); p->emit_alpha = EmitRescaledAlphaRGB; if (p->output->colorspace == MODE_RGBA_4444 || @@ -528,6 +536,7 @@ static int InitRGBRescaler(const VP8Io* const io, WebPDecParams* const p) { } else { p->emit_alpha_row = ExportAlpha; } + WebPInitAlphaProcessing(); } return 1; } @@ -548,7 +557,9 @@ static int CustomSetup(VP8Io* io) { if (!WebPIoInitFromOptions(p->options, io, is_alpha ? MODE_YUV : MODE_YUVA)) { return 0; } - + if (is_alpha && WebPIsPremultipliedMode(colorspace)) { + WebPInitUpsamplers(); + } if (io->use_scaling) { const int ok = is_rgb ? InitRGBRescaler(io, p) : InitYUVRescaler(io, p); if (!ok) { @@ -556,11 +567,12 @@ static int CustomSetup(VP8Io* io) { } } else { if (is_rgb) { + WebPInitSamplers(); p->emit = EmitSampledRGB; // default -#ifdef FANCY_UPSAMPLING if (io->fancy_upsampling) { +#ifdef FANCY_UPSAMPLING const int uv_width = (io->mb_w + 1) >> 1; - p->memory = malloc(io->mb_w + 2 * uv_width); + p->memory = WebPSafeMalloc(1ULL, (size_t)(io->mb_w + 2 * uv_width)); if (p->memory == NULL) { return 0; // memory error. } @@ -569,18 +581,20 @@ static int CustomSetup(VP8Io* io) { p->tmp_v = p->tmp_u + uv_width; p->emit = EmitFancyRGB; WebPInitUpsamplers(); - } #endif + } } else { p->emit = EmitYUV; } if (is_alpha) { // need transparency output - if (WebPIsPremultipliedMode(colorspace)) WebPInitPremultiply(); p->emit_alpha = (colorspace == MODE_RGBA_4444 || colorspace == MODE_rgbA_4444) ? EmitAlphaRGBA4444 : is_rgb ? EmitAlphaRGB : EmitAlphaYUV; + if (is_rgb) { + WebPInitAlphaProcessing(); + } } } @@ -603,8 +617,8 @@ static int CustomPut(const VP8Io* io) { return 0; } num_lines_out = p->emit(io, p); - if (p->emit_alpha) { - p->emit_alpha(io, p); + if (p->emit_alpha != NULL) { + p->emit_alpha(io, p, num_lines_out); } p->last_y += num_lines_out; return 1; @@ -614,7 +628,7 @@ static int CustomPut(const VP8Io* io) { static void CustomTeardown(const VP8Io* io) { WebPDecParams* const p = (WebPDecParams*)io->opaque; - free(p->memory); + WebPSafeFree(p->memory); p->memory = NULL; } @@ -629,7 +643,3 @@ void WebPInitCustomIo(WebPDecParams* const params, VP8Io* const io) { } //------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dec/quant.c b/3rdparty/libwebp/dec/quant_dec.c similarity index 95% rename from 3rdparty/libwebp/dec/quant.c rename to 3rdparty/libwebp/dec/quant_dec.c index a4cc693db7..14e3198946 100644 --- a/3rdparty/libwebp/dec/quant.c +++ b/3rdparty/libwebp/dec/quant_dec.c @@ -11,11 +11,7 @@ // // Author: Skal (pascal.massimino@gmail.com) -#include "./vp8i.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./vp8i_dec.h" static WEBP_INLINE int clip(int v, int M) { return v < 0 ? 0 : v > M ? M : v; @@ -104,12 +100,11 @@ void VP8ParseQuant(VP8Decoder* const dec) { m->uv_mat_[0] = kDcTable[clip(q + dquv_dc, 117)]; m->uv_mat_[1] = kAcTable[clip(q + dquv_ac, 127)]; + + m->uv_quant_ = q + dquv_ac; // for dithering strength evaluation } } } //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dec/tree.c b/3rdparty/libwebp/dec/tree_dec.c similarity index 86% rename from 3rdparty/libwebp/dec/tree.c rename to 3rdparty/libwebp/dec/tree_dec.c index 3f02efe43f..9e805f60f3 100644 --- a/3rdparty/libwebp/dec/tree.c +++ b/3rdparty/libwebp/dec/tree_dec.c @@ -11,12 +11,12 @@ // // Author: Skal (pascal.massimino@gmail.com) -#include "vp8i.h" +#include "./vp8i_dec.h" +#include "../utils/bit_reader_inl_utils.h" +#if !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__) +// using a table is ~1-2% slower on ARM. Prefer the coded-tree approach then. #define USE_GENERIC_TREE - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { #endif #ifdef USE_GENERIC_TREE @@ -33,61 +33,12 @@ static const int8_t kYModesIntra4[18] = { }; #endif -#ifndef ONLY_KEYFRAME_CODE - -// inter prediction modes -enum { - LEFT4 = 0, ABOVE4 = 1, ZERO4 = 2, NEW4 = 3, - NEARESTMV, NEARMV, ZEROMV, NEWMV, SPLITMV }; - -static const int8_t kYModesInter[8] = { - -DC_PRED, 1, - 2, 3, - -V_PRED, -H_PRED, - -TM_PRED, -B_PRED -}; - -static const int8_t kMBSplit[6] = { - -3, 1, - -2, 2, - -0, -1 -}; - -static const int8_t kMVRef[8] = { - -ZEROMV, 1, - -NEARESTMV, 2, - -NEARMV, 3, - -NEWMV, -SPLITMV -}; - -static const int8_t kMVRef4[6] = { - -LEFT4, 1, - -ABOVE4, 2, - -ZERO4, -NEW4 -}; -#endif - //------------------------------------------------------------------------------ // Default probabilities -// Inter -#ifndef ONLY_KEYFRAME_CODE -static const uint8_t kYModeProbaInter0[4] = { 112, 86, 140, 37 }; -static const uint8_t kUVModeProbaInter0[3] = { 162, 101, 204 }; -static const uint8_t kMVProba0[2][NUM_MV_PROBAS] = { - { 162, 128, 225, 146, 172, 147, 214, 39, - 156, 128, 129, 132, 75, 145, 178, 206, - 239, 254, 254 }, - { 164, 128, 204, 170, 119, 235, 140, 230, - 228, 128, 130, 130, 74, 148, 180, 203, - 236, 254, 254 } -}; -#endif - // Paragraph 13.5 static const uint8_t CoeffsProba0[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = { - // genereated using vp8_default_coef_probs() in entropy.c:129 { { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } @@ -328,28 +279,38 @@ static const uint8_t kBModesProba[NUM_BMODES][NUM_BMODES][NUM_BMODES - 1] = { void VP8ResetProba(VP8Proba* const proba) { memset(proba->segments_, 255u, sizeof(proba->segments_)); - memcpy(proba->coeffs_, CoeffsProba0, sizeof(CoeffsProba0)); -#ifndef ONLY_KEYFRAME_CODE - memcpy(proba->mv_, kMVProba0, sizeof(kMVProba0)); - memcpy(proba->ymode_, kYModeProbaInter0, sizeof(kYModeProbaInter0)); - memcpy(proba->uvmode_, kUVModeProbaInter0, sizeof(kUVModeProbaInter0)); -#endif + // proba->bands_[][] is initialized later } -void VP8ParseIntraMode(VP8BitReader* const br, VP8Decoder* const dec) { - uint8_t* const top = dec->intra_t_ + 4 * dec->mb_x_; +static void ParseIntraMode(VP8BitReader* const br, + VP8Decoder* const dec, int mb_x) { + uint8_t* const top = dec->intra_t_ + 4 * mb_x; uint8_t* const left = dec->intra_l_; - // Hardcoded 16x16 intra-mode decision tree. - dec->is_i4x4_ = !VP8GetBit(br, 145); // decide for B_PRED first - if (!dec->is_i4x4_) { + VP8MBData* const block = dec->mb_data_ + mb_x; + + // Note: we don't save segment map (yet), as we don't expect + // to decode more than 1 keyframe. + if (dec->segment_hdr_.update_map_) { + // Hardcoded tree parsing + block->segment_ = !VP8GetBit(br, dec->proba_.segments_[0]) + ? VP8GetBit(br, dec->proba_.segments_[1]) + : 2 + VP8GetBit(br, dec->proba_.segments_[2]); + } else { + block->segment_ = 0; // default for intra + } + if (dec->use_skip_proba_) block->skip_ = VP8GetBit(br, dec->skip_p_); + + block->is_i4x4_ = !VP8GetBit(br, 145); // decide for B_PRED first + if (!block->is_i4x4_) { + // Hardcoded 16x16 intra-mode decision tree. const int ymode = VP8GetBit(br, 156) ? (VP8GetBit(br, 128) ? TM_PRED : H_PRED) : (VP8GetBit(br, 163) ? V_PRED : DC_PRED); - dec->imodes_[0] = ymode; - memset(top, ymode, 4 * sizeof(top[0])); - memset(left, ymode, 4 * sizeof(left[0])); + block->imodes_[0] = ymode; + memset(top, ymode, 4 * sizeof(*top)); + memset(left, ymode, 4 * sizeof(*left)); } else { - uint8_t* modes = dec->imodes_; + uint8_t* modes = block->imodes_; int y; for (y = 0; y < 4; ++y) { int ymode = left[y]; @@ -358,10 +319,10 @@ void VP8ParseIntraMode(VP8BitReader* const br, VP8Decoder* const dec) { const uint8_t* const prob = kBModesProba[top[x]][ymode]; #ifdef USE_GENERIC_TREE // Generic tree-parsing - int i = 0; - do { + int i = kYModesIntra4[VP8GetBit(br, prob[0])]; + while (i > 0) { i = kYModesIntra4[2 * i + VP8GetBit(br, prob[i])]; - } while (i > 0); + } ymode = -i; #else // Hardcoded tree parsing @@ -376,15 +337,24 @@ void VP8ParseIntraMode(VP8BitReader* const br, VP8Decoder* const dec) { (!VP8GetBit(br, prob[8]) ? B_HD_PRED : B_HU_PRED))); #endif // USE_GENERIC_TREE top[x] = ymode; - *modes++ = ymode; } + memcpy(modes, top, 4 * sizeof(*top)); + modes += 4; left[y] = ymode; } } // Hardcoded UVMode decision tree - dec->uvmode_ = !VP8GetBit(br, 142) ? DC_PRED - : !VP8GetBit(br, 114) ? V_PRED - : VP8GetBit(br, 183) ? TM_PRED : H_PRED; + block->uvmode_ = !VP8GetBit(br, 142) ? DC_PRED + : !VP8GetBit(br, 114) ? V_PRED + : VP8GetBit(br, 183) ? TM_PRED : H_PRED; +} + +int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec) { + int mb_x; + for (mb_x = 0; mb_x < dec->mb_w_; ++mb_x) { + ParseIntraMode(br, dec, mb_x); + } + return !dec->br_.eof_; } //------------------------------------------------------------------------------ @@ -526,18 +496,13 @@ static const uint8_t } }; -#ifndef ONLY_KEYFRAME_CODE -static const uint8_t MVUpdateProba[2][NUM_MV_PROBAS] = { - { 237, 246, 253, 253, 254, 254, 254, 254, - 254, 254, 254, 254, 254, 254, 250, 250, - 252, 254, 254 }, - { 231, 243, 245, 253, 254, 254, 254, 254, - 254, 254, 254, 254, 254, 254, 251, 251, - 254, 254, 254 } -}; -#endif - // Paragraph 9.9 + +static const int kBands[16 + 1] = { + 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, + 0 // extra entry as sentinel +}; + void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec) { VP8Proba* const proba = &dec->proba_; int t, b, c, p; @@ -545,47 +510,19 @@ void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec) { for (b = 0; b < NUM_BANDS; ++b) { for (c = 0; c < NUM_CTX; ++c) { for (p = 0; p < NUM_PROBAS; ++p) { - if (VP8GetBit(br, CoeffsUpdateProba[t][b][c][p])) { - proba->coeffs_[t][b][c][p] = VP8GetValue(br, 8); - } + const int v = VP8GetBit(br, CoeffsUpdateProba[t][b][c][p]) ? + VP8GetValue(br, 8) : CoeffsProba0[t][b][c][p]; + proba->bands_[t][b].probas_[c][p] = v; } } } + for (b = 0; b < 16 + 1; ++b) { + proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]]; + } } dec->use_skip_proba_ = VP8Get(br); if (dec->use_skip_proba_) { dec->skip_p_ = VP8GetValue(br, 8); } -#ifndef ONLY_KEYFRAME_CODE - if (!dec->frm_hdr_.key_frame_) { - int i; - dec->intra_p_ = VP8GetValue(br, 8); - dec->last_p_ = VP8GetValue(br, 8); - dec->golden_p_ = VP8GetValue(br, 8); - if (VP8Get(br)) { // update y-mode - for (i = 0; i < 4; ++i) { - proba->ymode_[i] = VP8GetValue(br, 8); - } - } - if (VP8Get(br)) { // update uv-mode - for (i = 0; i < 3; ++i) { - proba->uvmode_[i] = VP8GetValue(br, 8); - } - } - // update MV - for (i = 0; i < 2; ++i) { - int k; - for (k = 0; k < NUM_MV_PROBAS; ++k) { - if (VP8GetBit(br, MVUpdateProba[i][k])) { - const int v = VP8GetValue(br, 7); - proba->mv_[i][k] = v ? v << 1 : 1; - } - } - } - } -#endif } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dec/vp8.c b/3rdparty/libwebp/dec/vp8_dec.c similarity index 60% rename from 3rdparty/libwebp/dec/vp8.c rename to 3rdparty/libwebp/dec/vp8_dec.c index 8632e48e57..fad8d9cf35 100644 --- a/3rdparty/libwebp/dec/vp8.c +++ b/3rdparty/libwebp/dec/vp8_dec.c @@ -13,14 +13,12 @@ #include -#include "./vp8i.h" -#include "./vp8li.h" -#include "./webpi.h" -#include "../utils/bit_reader.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./alphai_dec.h" +#include "./vp8i_dec.h" +#include "./vp8li_dec.h" +#include "./webpi_dec.h" +#include "../utils/bit_reader_inl_utils.h" +#include "../utils/utils.h" //------------------------------------------------------------------------------ @@ -28,6 +26,16 @@ int WebPGetDecoderVersion(void) { return (DEC_MAJ_VERSION << 16) | (DEC_MIN_VERSION << 8) | DEC_REV_VERSION; } +//------------------------------------------------------------------------------ +// Signature and pointer-to-function for GetCoeffs() variants below. + +typedef int (*GetCoeffsFunc)(VP8BitReader* const br, + const VP8BandProbas* const prob[], + int ctx, const quant_t dq, int n, int16_t* out); +static volatile GetCoeffsFunc GetCoeffs = NULL; + +static void InitGetCoeffs(void); + //------------------------------------------------------------------------------ // VP8Decoder @@ -47,12 +55,13 @@ int VP8InitIoInternal(VP8Io* const io, int version) { } VP8Decoder* VP8New(void) { - VP8Decoder* const dec = (VP8Decoder*)calloc(1, sizeof(*dec)); + VP8Decoder* const dec = (VP8Decoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); if (dec != NULL) { SetOk(dec); - WebPWorkerInit(&dec->worker_); + WebPGetWorkerInterface()->Init(&dec->worker_); dec->ready_ = 0; - dec->num_parts_ = 1; + dec->num_parts_minus_one_ = 0; + InitGetCoeffs(); } return dec; } @@ -71,16 +80,13 @@ const char* VP8StatusMessage(VP8Decoder* const dec) { void VP8Delete(VP8Decoder* const dec) { if (dec != NULL) { VP8Clear(dec); - free(dec); + WebPSafeFree(dec); } } int VP8SetError(VP8Decoder* const dec, VP8StatusCode error, const char* const msg) { - // TODO This check would be unnecessary if alpha decompression was separated - // from VP8ProcessRow/FinishRow. This avoids setting 'dec->status_' to - // something other than VP8_STATUS_BITSTREAM_ERROR on alpha decompression - // failure. + // The oldest error reported takes precedence over the new one. if (dec->status_ == VP8_STATUS_OK) { dec->status_ = error; dec->error_msg_ = msg; @@ -123,6 +129,9 @@ int VP8GetInfo(const uint8_t* data, size_t data_size, size_t chunk_size, if (((bits >> 5)) >= chunk_size) { // partition_length return 0; // inconsistent size information. } + if (w == 0 || h == 0) { + return 0; // We don't support both width and height to be zero. + } if (width) { *width = w; @@ -192,25 +201,27 @@ static VP8StatusCode ParsePartitions(VP8Decoder* const dec, const uint8_t* sz = buf; const uint8_t* buf_end = buf + size; const uint8_t* part_start; - int last_part; - int p; + size_t size_left = size; + size_t last_part; + size_t p; - dec->num_parts_ = 1 << VP8GetValue(br, 2); - last_part = dec->num_parts_ - 1; - part_start = buf + last_part * 3; - if (buf_end < part_start) { + dec->num_parts_minus_one_ = (1 << VP8GetValue(br, 2)) - 1; + last_part = dec->num_parts_minus_one_; + if (size < 3 * last_part) { // we can't even read the sizes with sz[]! That's a failure. return VP8_STATUS_NOT_ENOUGH_DATA; } + part_start = buf + last_part * 3; + size_left -= last_part * 3; for (p = 0; p < last_part; ++p) { - const uint32_t psize = sz[0] | (sz[1] << 8) | (sz[2] << 16); - const uint8_t* part_end = part_start + psize; - if (part_end > buf_end) part_end = buf_end; - VP8InitBitReader(dec->parts_ + p, part_start, part_end); - part_start = part_end; + size_t psize = sz[0] | (sz[1] << 8) | (sz[2] << 16); + if (psize > size_left) psize = size_left; + VP8InitBitReader(dec->parts_ + p, part_start, psize); + part_start += psize; + size_left -= psize; sz += 3; } - VP8InitBitReader(dec->parts_ + last_part, part_start, buf_end); + VP8InitBitReader(dec->parts_ + last_part, part_start, size_left); return (part_start < buf_end) ? VP8_STATUS_OK : VP8_STATUS_SUSPENDED; // Init is ok, but there's not enough data } @@ -249,7 +260,6 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { VP8PictureHeader* pic_hdr; VP8BitReader* br; VP8StatusCode status; - WebPHeaderStructure headers; if (dec == NULL) { return 0; @@ -259,33 +269,8 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { return VP8SetError(dec, VP8_STATUS_INVALID_PARAM, "null VP8Io passed to VP8GetHeaders()"); } - - // Process Pre-VP8 chunks. - headers.data = io->data; - headers.data_size = io->data_size; - status = WebPParseHeaders(&headers); - if (status != VP8_STATUS_OK) { - return VP8SetError(dec, status, "Incorrect/incomplete header."); - } - if (headers.is_lossless) { - return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, - "Unexpected lossless format encountered."); - } - - if (dec->alpha_data_ == NULL) { - assert(dec->alpha_data_size_ == 0); - // We have NOT set alpha data yet. Set it now. - // (This is to ensure that dec->alpha_data_ is NOT reset to NULL if - // WebPParseHeaders() is called more than once, as in incremental decoding - // case.) - dec->alpha_data_ = headers.alpha_data; - dec->alpha_data_size_ = headers.alpha_data_size; - } - - // Process the VP8 frame header. - buf = headers.data + headers.offset; - buf_size = headers.data_size - headers.offset; - assert(headers.data_size >= headers.offset); // WebPParseHeaders' guarantee + buf = io->data; + buf_size = io->data_size; if (buf_size < 4) { return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, "Truncated header."); @@ -299,12 +284,14 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { frm_hdr->profile_ = (bits >> 1) & 7; frm_hdr->show_ = (bits >> 4) & 1; frm_hdr->partition_length_ = (bits >> 5); - if (frm_hdr->profile_ > 3) + if (frm_hdr->profile_ > 3) { return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, "Incorrect keyframe parameters."); - if (!frm_hdr->show_) + } + if (!frm_hdr->show_) { return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE, "Frame not displayable."); + } buf += 3; buf_size -= 3; } @@ -329,21 +316,27 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { dec->mb_w_ = (pic_hdr->width_ + 15) >> 4; dec->mb_h_ = (pic_hdr->height_ + 15) >> 4; + // Setup default output area (can be later modified during io->setup()) io->width = pic_hdr->width_; io->height = pic_hdr->height_; - io->use_scaling = 0; + // IMPORTANT! use some sane dimensions in crop_* and scaled_* fields. + // So they can be used interchangeably without always testing for + // 'use_cropping'. io->use_cropping = 0; io->crop_top = 0; io->crop_left = 0; io->crop_right = io->width; io->crop_bottom = io->height; + io->use_scaling = 0; + io->scaled_width = io->width; + io->scaled_height = io->height; + io->mb_w = io->width; // sanity check io->mb_h = io->height; // ditto VP8ResetProba(&dec->proba_); ResetSegmentHeader(&dec->segment_hdr_); - dec->segment_ = 0; // default for intra } // Check if we have all the partition #0 available, and initialize dec->br_ @@ -354,7 +347,7 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { } br = &dec->br_; - VP8InitBitReader(br, buf, buf + frm_hdr->partition_length_); + VP8InitBitReader(br, buf, frm_hdr->partition_length_); buf += frm_hdr->partition_length_; buf_size -= frm_hdr->partition_length_; @@ -381,63 +374,14 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { // Frame buffer marking if (!frm_hdr->key_frame_) { - // Paragraph 9.7 -#ifndef ONLY_KEYFRAME_CODE - dec->buffer_flags_ = VP8Get(br) << 0; // update golden - dec->buffer_flags_ |= VP8Get(br) << 1; // update alt ref - if (!(dec->buffer_flags_ & 1)) { - dec->buffer_flags_ |= VP8GetValue(br, 2) << 2; - } - if (!(dec->buffer_flags_ & 2)) { - dec->buffer_flags_ |= VP8GetValue(br, 2) << 4; - } - dec->buffer_flags_ |= VP8Get(br) << 6; // sign bias golden - dec->buffer_flags_ |= VP8Get(br) << 7; // sign bias alt ref -#else return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE, "Not a key frame."); -#endif - } else { - dec->buffer_flags_ = 0x003 | 0x100; } - // Paragraph 9.8 -#ifndef ONLY_KEYFRAME_CODE - dec->update_proba_ = VP8Get(br); - if (!dec->update_proba_) { // save for later restore - dec->proba_saved_ = dec->proba_; - } - dec->buffer_flags_ &= 1 << 8; - dec->buffer_flags_ |= - (frm_hdr->key_frame_ || VP8Get(br)) << 8; // refresh last frame -#else - VP8Get(br); // just ignore the value of update_proba_ -#endif + VP8Get(br); // ignore the value of update_proba_ VP8ParseProba(br, dec); -#ifdef WEBP_EXPERIMENTAL_FEATURES - // Extensions - if (dec->pic_hdr_.colorspace_) { - const size_t kTrailerSize = 8; - const uint8_t kTrailerMarker = 0x01; - const uint8_t* ext_buf = buf - kTrailerSize; - size_t size; - - if (frm_hdr->partition_length_ < kTrailerSize || - ext_buf[kTrailerSize - 1] != kTrailerMarker) { - return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, - "RIFF: Inconsistent extra information."); - } - - // Layer - size = (ext_buf[0] << 0) | (ext_buf[1] << 8) | (ext_buf[2] << 16); - dec->layer_data_size_ = size; - dec->layer_data_ = NULL; // will be set later - dec->layer_colorspace_ = ext_buf[3]; - } -#endif - // sanitized state dec->ready_ = 1; return 1; @@ -446,11 +390,6 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { //------------------------------------------------------------------------------ // Residual decoding (Paragraph 13.2 / 13.3) -static const int kBands[16 + 1] = { - 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, - 0 // extra entry as sentinel -}; - static const uint8_t kCat3[] = { 173, 148, 140, 0 }; static const uint8_t kCat4[] = { 176, 155, 140, 135, 0 }; static const uint8_t kCat5[] = { 180, 157, 141, 134, 130, 0 }; @@ -461,9 +400,6 @@ static const uint8_t kZigzag[16] = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; -typedef const uint8_t (*ProbaArray)[NUM_CTX][NUM_PROBAS]; // for const-casting -typedef const uint8_t (*ProbaCtxArray)[NUM_PROBAS]; - // See section 13-2: http://tools.ietf.org/html/rfc6386#section-13.2 static int GetLargeValue(VP8BitReader* const br, const uint8_t* const p) { int v; @@ -497,19 +433,20 @@ static int GetLargeValue(VP8BitReader* const br, const uint8_t* const p) { } // Returns the position of the last non-zero coeff plus one -// (and 0 if there's no coeff at all) -static int GetCoeffs(VP8BitReader* const br, ProbaArray prob, - int ctx, const quant_t dq, int n, int16_t* out) { - // n is either 0 or 1 here. kBands[n] is not necessary for extracting '*p'. - const uint8_t* p = prob[n][ctx]; - if (!VP8GetBit(br, p[0])) { // first EOB is more a 'CBP' bit. - return 0; - } +static int GetCoeffsFast(VP8BitReader* const br, + const VP8BandProbas* const prob[], + int ctx, const quant_t dq, int n, int16_t* out) { + const uint8_t* p = prob[n]->probas_[ctx]; for (; n < 16; ++n) { - const ProbaCtxArray p_ctx = prob[kBands[n + 1]]; - if (!VP8GetBit(br, p[1])) { - p = p_ctx[0]; - } else { // non zero coeff + if (!VP8GetBit(br, p[0])) { + return n; // previous coeff was last non-zero coeff + } + while (!VP8GetBit(br, p[1])) { // sequence of zero coeffs + p = prob[++n]->probas_[0]; + if (n == 16) return 16; + } + { // non zero coeff + const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas_[0]; int v; if (!VP8GetBit(br, p[2])) { v = 1; @@ -519,205 +456,212 @@ static int GetCoeffs(VP8BitReader* const br, ProbaArray prob, p = p_ctx[2]; } out[kZigzag[n]] = VP8GetSigned(br, v) * dq[n > 0]; - if (n < 15 && !VP8GetBit(br, p[0])) { // EOB - return n + 1; - } } } return 16; } -// Alias-safe way of converting 4bytes to 32bits. -typedef union { - uint8_t i8[4]; - uint32_t i32; -} PackedNz; +// This version of GetCoeffs() uses VP8GetBitAlt() which is an alternate version +// of VP8GetBitAlt() targeting specific platforms. +static int GetCoeffsAlt(VP8BitReader* const br, + const VP8BandProbas* const prob[], + int ctx, const quant_t dq, int n, int16_t* out) { + const uint8_t* p = prob[n]->probas_[ctx]; + for (; n < 16; ++n) { + if (!VP8GetBitAlt(br, p[0])) { + return n; // previous coeff was last non-zero coeff + } + while (!VP8GetBitAlt(br, p[1])) { // sequence of zero coeffs + p = prob[++n]->probas_[0]; + if (n == 16) return 16; + } + { // non zero coeff + const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas_[0]; + int v; + if (!VP8GetBitAlt(br, p[2])) { + v = 1; + p = p_ctx[1]; + } else { + v = GetLargeValue(br, p); + p = p_ctx[2]; + } + out[kZigzag[n]] = VP8GetSigned(br, v) * dq[n > 0]; + } + } + return 16; +} -// Table to unpack four bits into four bytes -static const PackedNz kUnpackTab[16] = { - {{0, 0, 0, 0}}, {{1, 0, 0, 0}}, {{0, 1, 0, 0}}, {{1, 1, 0, 0}}, - {{0, 0, 1, 0}}, {{1, 0, 1, 0}}, {{0, 1, 1, 0}}, {{1, 1, 1, 0}}, - {{0, 0, 0, 1}}, {{1, 0, 0, 1}}, {{0, 1, 0, 1}}, {{1, 1, 0, 1}}, - {{0, 0, 1, 1}}, {{1, 0, 1, 1}}, {{0, 1, 1, 1}}, {{1, 1, 1, 1}} }; +WEBP_TSAN_IGNORE_FUNCTION static void InitGetCoeffs(void) { + if (GetCoeffs == NULL) { + if (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kSlowSSSE3)) { + GetCoeffs = GetCoeffsAlt; + } else { + GetCoeffs = GetCoeffsFast; + } + } +} -// Macro to pack four LSB of four bytes into four bits. -#if defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || \ - defined(__BIG_ENDIAN__) -#define PACK_CST 0x08040201U -#else -#define PACK_CST 0x01020408U -#endif -#define PACK(X, S) ((((X).i32 * PACK_CST) & 0xff000000) >> (S)) +static WEBP_INLINE uint32_t NzCodeBits(uint32_t nz_coeffs, int nz, int dc_nz) { + nz_coeffs <<= 2; + nz_coeffs |= (nz > 3) ? 3 : (nz > 1) ? 2 : dc_nz; + return nz_coeffs; +} -static void ParseResiduals(VP8Decoder* const dec, - VP8MB* const mb, VP8BitReader* const token_br) { - int out_t_nz, out_l_nz, first; - ProbaArray ac_prob; - const VP8QuantMatrix* q = &dec->dqm_[dec->segment_]; - int16_t* dst = dec->coeffs_; +static int ParseResiduals(VP8Decoder* const dec, + VP8MB* const mb, VP8BitReader* const token_br) { + const VP8BandProbas* (* const bands)[16 + 1] = dec->proba_.bands_ptr_; + const VP8BandProbas* const * ac_proba; + VP8MBData* const block = dec->mb_data_ + dec->mb_x_; + const VP8QuantMatrix* const q = &dec->dqm_[block->segment_]; + int16_t* dst = block->coeffs_; VP8MB* const left_mb = dec->mb_info_ - 1; - PackedNz nz_ac, nz_dc; - PackedNz tnz, lnz; - uint32_t non_zero_ac = 0; - uint32_t non_zero_dc = 0; + uint8_t tnz, lnz; + uint32_t non_zero_y = 0; + uint32_t non_zero_uv = 0; int x, y, ch; + uint32_t out_t_nz, out_l_nz; + int first; - nz_dc.i32 = nz_ac.i32 = 0; memset(dst, 0, 384 * sizeof(*dst)); - if (!dec->is_i4x4_) { // parse DC + if (!block->is_i4x4_) { // parse DC int16_t dc[16] = { 0 }; - const int ctx = mb->dc_nz_ + left_mb->dc_nz_; - mb->dc_nz_ = left_mb->dc_nz_ = - (GetCoeffs(token_br, (ProbaArray)dec->proba_.coeffs_[1], - ctx, q->y2_mat_, 0, dc) > 0); + const int ctx = mb->nz_dc_ + left_mb->nz_dc_; + const int nz = GetCoeffs(token_br, bands[1], ctx, q->y2_mat_, 0, dc); + mb->nz_dc_ = left_mb->nz_dc_ = (nz > 0); + if (nz > 1) { // more than just the DC -> perform the full transform + VP8TransformWHT(dc, dst); + } else { // only DC is non-zero -> inlined simplified transform + int i; + const int dc0 = (dc[0] + 3) >> 3; + for (i = 0; i < 16 * 16; i += 16) dst[i] = dc0; + } first = 1; - ac_prob = (ProbaArray)dec->proba_.coeffs_[0]; - VP8TransformWHT(dc, dst); + ac_proba = bands[0]; } else { first = 0; - ac_prob = (ProbaArray)dec->proba_.coeffs_[3]; + ac_proba = bands[3]; } - tnz = kUnpackTab[mb->nz_ & 0xf]; - lnz = kUnpackTab[left_mb->nz_ & 0xf]; + tnz = mb->nz_ & 0x0f; + lnz = left_mb->nz_ & 0x0f; for (y = 0; y < 4; ++y) { - int l = lnz.i8[y]; + int l = lnz & 1; + uint32_t nz_coeffs = 0; for (x = 0; x < 4; ++x) { - const int ctx = l + tnz.i8[x]; - const int nz = GetCoeffs(token_br, ac_prob, ctx, - q->y1_mat_, first, dst); - tnz.i8[x] = l = (nz > 0); - nz_dc.i8[x] = (dst[0] != 0); - nz_ac.i8[x] = (nz > 1); + const int ctx = l + (tnz & 1); + const int nz = GetCoeffs(token_br, ac_proba, ctx, q->y1_mat_, first, dst); + l = (nz > first); + tnz = (tnz >> 1) | (l << 7); + nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0); dst += 16; } - lnz.i8[y] = l; - non_zero_dc |= PACK(nz_dc, 24 - y * 4); - non_zero_ac |= PACK(nz_ac, 24 - y * 4); + tnz >>= 4; + lnz = (lnz >> 1) | (l << 7); + non_zero_y = (non_zero_y << 8) | nz_coeffs; } - out_t_nz = PACK(tnz, 24); - out_l_nz = PACK(lnz, 24); + out_t_nz = tnz; + out_l_nz = lnz >> 4; - tnz = kUnpackTab[mb->nz_ >> 4]; - lnz = kUnpackTab[left_mb->nz_ >> 4]; for (ch = 0; ch < 4; ch += 2) { + uint32_t nz_coeffs = 0; + tnz = mb->nz_ >> (4 + ch); + lnz = left_mb->nz_ >> (4 + ch); for (y = 0; y < 2; ++y) { - int l = lnz.i8[ch + y]; + int l = lnz & 1; for (x = 0; x < 2; ++x) { - const int ctx = l + tnz.i8[ch + x]; - const int nz = - GetCoeffs(token_br, (ProbaArray)dec->proba_.coeffs_[2], - ctx, q->uv_mat_, 0, dst); - tnz.i8[ch + x] = l = (nz > 0); - nz_dc.i8[y * 2 + x] = (dst[0] != 0); - nz_ac.i8[y * 2 + x] = (nz > 1); + const int ctx = l + (tnz & 1); + const int nz = GetCoeffs(token_br, bands[2], ctx, q->uv_mat_, 0, dst); + l = (nz > 0); + tnz = (tnz >> 1) | (l << 3); + nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0); dst += 16; } - lnz.i8[ch + y] = l; + tnz >>= 2; + lnz = (lnz >> 1) | (l << 5); } - non_zero_dc |= PACK(nz_dc, 8 - ch * 2); - non_zero_ac |= PACK(nz_ac, 8 - ch * 2); + // Note: we don't really need the per-4x4 details for U/V blocks. + non_zero_uv |= nz_coeffs << (4 * ch); + out_t_nz |= (tnz << 4) << ch; + out_l_nz |= (lnz & 0xf0) << ch; } - out_t_nz |= PACK(tnz, 20); - out_l_nz |= PACK(lnz, 20); mb->nz_ = out_t_nz; left_mb->nz_ = out_l_nz; - dec->non_zero_ac_ = non_zero_ac; - dec->non_zero_ = non_zero_ac | non_zero_dc; - mb->skip_ = !dec->non_zero_; + block->non_zero_y_ = non_zero_y; + block->non_zero_uv_ = non_zero_uv; + + // We look at the mode-code of each block and check if some blocks have less + // than three non-zero coeffs (code < 2). This is to avoid dithering flat and + // empty blocks. + block->dither_ = (non_zero_uv & 0xaaaa) ? 0 : q->dither_; + + return !(non_zero_y | non_zero_uv); // will be used for further optimization } -#undef PACK //------------------------------------------------------------------------------ // Main loop int VP8DecodeMB(VP8Decoder* const dec, VP8BitReader* const token_br) { - VP8BitReader* const br = &dec->br_; VP8MB* const left = dec->mb_info_ - 1; - VP8MB* const info = dec->mb_info_ + dec->mb_x_; + VP8MB* const mb = dec->mb_info_ + dec->mb_x_; + VP8MBData* const block = dec->mb_data_ + dec->mb_x_; + int skip = dec->use_skip_proba_ ? block->skip_ : 0; - // Note: we don't save segment map (yet), as we don't expect - // to decode more than 1 keyframe. - if (dec->segment_hdr_.update_map_) { - // Hardcoded tree parsing - dec->segment_ = !VP8GetBit(br, dec->proba_.segments_[0]) ? - VP8GetBit(br, dec->proba_.segments_[1]) : - 2 + VP8GetBit(br, dec->proba_.segments_[2]); - } - info->skip_ = dec->use_skip_proba_ ? VP8GetBit(br, dec->skip_p_) : 0; - - VP8ParseIntraMode(br, dec); - if (br->eof_) { - return 0; - } - - if (!info->skip_) { - ParseResiduals(dec, info, token_br); + if (!skip) { + skip = ParseResiduals(dec, mb, token_br); } else { - left->nz_ = info->nz_ = 0; - if (!dec->is_i4x4_) { - left->dc_nz_ = info->dc_nz_ = 0; + left->nz_ = mb->nz_ = 0; + if (!block->is_i4x4_) { + left->nz_dc_ = mb->nz_dc_ = 0; } - dec->non_zero_ = 0; - dec->non_zero_ac_ = 0; + block->non_zero_y_ = 0; + block->non_zero_uv_ = 0; + block->dither_ = 0; } if (dec->filter_type_ > 0) { // store filter info VP8FInfo* const finfo = dec->f_info_ + dec->mb_x_; - *finfo = dec->fstrengths_[dec->segment_][dec->is_i4x4_]; - finfo->f_inner_ = (!info->skip_ || dec->is_i4x4_); + *finfo = dec->fstrengths_[block->segment_][block->is_i4x4_]; + finfo->f_inner_ |= !skip; } - return (!token_br->eof_); + return !token_br->eof_; } void VP8InitScanline(VP8Decoder* const dec) { VP8MB* const left = dec->mb_info_ - 1; left->nz_ = 0; - left->dc_nz_ = 0; + left->nz_dc_ = 0; memset(dec->intra_l_, B_DC_PRED, sizeof(dec->intra_l_)); - dec->filter_row_ = - (dec->filter_type_ > 0) && - (dec->mb_y_ >= dec->tl_mb_y_) && (dec->mb_y_ <= dec->br_mb_y_); + dec->mb_x_ = 0; } static int ParseFrame(VP8Decoder* const dec, VP8Io* io) { for (dec->mb_y_ = 0; dec->mb_y_ < dec->br_mb_y_; ++dec->mb_y_) { + // Parse bitstream for this row. VP8BitReader* const token_br = - &dec->parts_[dec->mb_y_ & (dec->num_parts_ - 1)]; - VP8InitScanline(dec); - for (dec->mb_x_ = 0; dec->mb_x_ < dec->mb_w_; dec->mb_x_++) { + &dec->parts_[dec->mb_y_ & dec->num_parts_minus_one_]; + if (!VP8ParseIntraModeRow(&dec->br_, dec)) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "Premature end-of-partition0 encountered."); + } + for (; dec->mb_x_ < dec->mb_w_; ++dec->mb_x_) { if (!VP8DecodeMB(dec, token_br)) { return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, "Premature end-of-file encountered."); } - // Reconstruct and emit samples. - VP8ReconstructBlock(dec); } + VP8InitScanline(dec); // Prepare for next scanline + + // Reconstruct, filter and emit the row. if (!VP8ProcessRow(dec, io)) { return VP8SetError(dec, VP8_STATUS_USER_ABORT, "Output aborted."); } } - if (dec->use_threads_ && !WebPWorkerSync(&dec->worker_)) { - return 0; + if (dec->mt_method_ > 0) { + if (!WebPGetWorkerInterface()->Sync(&dec->worker_)) return 0; } - // Finish -#ifndef ONLY_KEYFRAME_CODE - if (!dec->update_proba_) { - dec->proba_ = dec->proba_saved_; - } -#endif - -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (dec->layer_data_size_ > 0) { - if (!VP8DecodeLayer(dec)) { - return 0; - } - } -#endif - return 1; } @@ -765,12 +709,9 @@ void VP8Clear(VP8Decoder* const dec) { if (dec == NULL) { return; } - if (dec->use_threads_) { - WebPWorkerEnd(&dec->worker_); - } - if (dec->mem_) { - free(dec->mem_); - } + WebPGetWorkerInterface()->End(&dec->worker_); + WebPDeallocateAlphaMemory(dec); + WebPSafeFree(dec->mem_); dec->mem_ = NULL; dec->mem_size_ = 0; memset(&dec->br_, 0, sizeof(dec->br_)); @@ -778,7 +719,3 @@ void VP8Clear(VP8Decoder* const dec) { } //------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dec/decode_vp8.h b/3rdparty/libwebp/dec/vp8_dec.h similarity index 97% rename from 3rdparty/libwebp/dec/decode_vp8.h rename to 3rdparty/libwebp/dec/vp8_dec.h index acdb15aaa9..b9337bbec0 100644 --- a/3rdparty/libwebp/dec/decode_vp8.h +++ b/3rdparty/libwebp/dec/vp8_dec.h @@ -16,7 +16,7 @@ #include "../webp/decode.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -132,7 +132,8 @@ static WEBP_INLINE int VP8InitIo(VP8Io* const io) { return VP8InitIoInternal(io, WEBP_DECODER_ABI_VERSION); } -// Start decoding a new picture. Returns true if ok. +// Decode the VP8 frame header. Returns true if ok. +// Note: 'io->data' must be pointing to the start of the VP8 frame header. int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io); // Decode a picture. Will call VP8GetHeaders() if it wasn't done already. @@ -177,7 +178,7 @@ WEBP_EXTERN(int) VP8LGetInfo( const uint8_t* data, size_t data_size, // data available so far int* const width, int* const height, int* const has_alpha); -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/dec/vp8i.h b/3rdparty/libwebp/dec/vp8i_dec.h similarity index 60% rename from 3rdparty/libwebp/dec/vp8i.h rename to 3rdparty/libwebp/dec/vp8i_dec.h index 1d0d4077eb..555853e8f8 100644 --- a/3rdparty/libwebp/dec/vp8i.h +++ b/3rdparty/libwebp/dec/vp8i_dec.h @@ -15,12 +15,14 @@ #define WEBP_DEC_VP8I_H_ #include // for memcpy() -#include "./vp8li.h" -#include "../utils/bit_reader.h" -#include "../utils/thread.h" +#include "./common_dec.h" +#include "./vp8li_dec.h" +#include "../utils/bit_reader_utils.h" +#include "../utils/random_utils.h" +#include "../utils/thread_utils.h" #include "../dsp/dsp.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -29,48 +31,10 @@ extern "C" { // version numbers #define DEC_MAJ_VERSION 0 -#define DEC_MIN_VERSION 3 -#define DEC_REV_VERSION 1 +#define DEC_MIN_VERSION 6 +#define DEC_REV_VERSION 0 -#define ONLY_KEYFRAME_CODE // to remove any code related to P-Frames - -// intra prediction modes -enum { B_DC_PRED = 0, // 4x4 modes - B_TM_PRED, - B_VE_PRED, - B_HE_PRED, - B_RD_PRED, - B_VR_PRED, - B_LD_PRED, - B_VL_PRED, - B_HD_PRED, - B_HU_PRED, - NUM_BMODES = B_HU_PRED + 1 - B_DC_PRED, // = 10 - - // Luma16 or UV modes - DC_PRED = B_DC_PRED, V_PRED = B_VE_PRED, - H_PRED = B_HE_PRED, TM_PRED = B_TM_PRED, - B_PRED = NUM_BMODES, // refined I4x4 mode - - // special modes - B_DC_PRED_NOTOP = 4, - B_DC_PRED_NOLEFT = 5, - B_DC_PRED_NOTOPLEFT = 6, - NUM_B_DC_MODES = 7 }; - -enum { MB_FEATURE_TREE_PROBS = 3, - NUM_MB_SEGMENTS = 4, - NUM_REF_LF_DELTAS = 4, - NUM_MODE_LF_DELTAS = 4, // I4x4, ZERO, *, SPLIT - MAX_NUM_PARTITIONS = 8, - // Probabilities - NUM_TYPES = 4, - NUM_BANDS = 8, - NUM_CTX = 3, - NUM_PROBAS = 11, - NUM_MV_PROBAS = 19 }; - -// YUV-cache parameters. +// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline). // Constraints are: We need to store one 16x16 block of luma samples (y), // and two 8x8 chroma blocks (u/v). These are better be 16-bytes aligned, // in order to be SIMD-friendly. We also need to store the top, left and @@ -92,14 +56,15 @@ enum { MB_FEATURE_TREE_PROBS = 3, // 'y' = y-samples 'u' = u-samples 'v' = u-samples // '|' = left sample, '-' = top sample, '+' = top-left sample // 't' = extra top-right sample for 4x4 modes -// With this layout, BPS (=Bytes Per Scan-line) is one cacheline size. -#define BPS 32 // this is the common stride used by yuv[] #define YUV_SIZE (BPS * 17 + BPS * 9) #define Y_SIZE (BPS * 17) #define Y_OFF (BPS * 1 + 8) #define U_OFF (Y_OFF + BPS * 16 + BPS) #define V_OFF (U_OFF + 16) +// minimal width under which lossy multi-threading is always disabled +#define MIN_WIDTH_FOR_THREADS 512 + //------------------------------------------------------------------------------ // Headers @@ -128,15 +93,19 @@ typedef struct { int8_t filter_strength_[NUM_MB_SEGMENTS]; // filter strength for segments } VP8SegmentHeader; +// probas associated to one of the contexts +typedef uint8_t VP8ProbaArray[NUM_PROBAS]; + +typedef struct { // all the probas associated to one band + VP8ProbaArray probas_[NUM_CTX]; +} VP8BandProbas; + // Struct collecting all frame-persistent probabilities. typedef struct { uint8_t segments_[MB_FEATURE_TREE_PROBS]; // Type: 0:Intra16-AC 1:Intra16-DC 2:Chroma 3:Intra4 - uint8_t coeffs_[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS]; -#ifndef ONLY_KEYFRAME_CODE - uint8_t ymode_[4], uvmode_[3]; - uint8_t mv_[2][NUM_MV_PROBAS]; -#endif + VP8BandProbas bands_[NUM_TYPES][NUM_BANDS]; + const VP8BandProbas* bands_ptr_[NUM_TYPES][16 + 1]; } VP8Proba; // Filter parameters @@ -153,32 +122,61 @@ typedef struct { // Informations about the macroblocks. typedef struct { // filter specs - unsigned int f_level_:6; // filter strength: 0..63 - unsigned int f_ilevel_:6; // inner limit: 1..63 - unsigned int f_inner_:1; // do inner filtering? + uint8_t f_limit_; // filter limit in [3..189], or 0 if no filtering + uint8_t f_ilevel_; // inner limit in [1..63] + uint8_t f_inner_; // do inner filtering? + uint8_t hev_thresh_; // high edge variance threshold in [0..2] } VP8FInfo; -typedef struct { // used for syntax-parsing - unsigned int nz_:24; // non-zero AC/DC coeffs (24bit) - unsigned int dc_nz_:1; // non-zero DC coeffs - unsigned int skip_:1; // block type +typedef struct { // Top/Left Contexts used for syntax-parsing + uint8_t nz_; // non-zero AC/DC coeffs (4bit for luma + 4bit for chroma) + uint8_t nz_dc_; // non-zero DC coeff (1bit) } VP8MB; // Dequantization matrices typedef int quant_t[2]; // [DC / AC]. Can be 'uint16_t[2]' too (~slower). typedef struct { quant_t y1_mat_, y2_mat_, uv_mat_; + + int uv_quant_; // U/V quantizer value + int dither_; // dithering amplitude (0 = off, max=255) } VP8QuantMatrix; +// Data needed to reconstruct a macroblock +typedef struct { + int16_t coeffs_[384]; // 384 coeffs = (16+4+4) * 4*4 + uint8_t is_i4x4_; // true if intra4x4 + uint8_t imodes_[16]; // one 16x16 mode (#0) or sixteen 4x4 modes + uint8_t uvmode_; // chroma prediction mode + // bit-wise info about the content of each sub-4x4 blocks (in decoding order). + // Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: + // code=0 -> no coefficient + // code=1 -> only DC + // code=2 -> first three coefficients are non-zero + // code=3 -> more than three coefficients are non-zero + // This allows to call specialized transform functions. + uint32_t non_zero_y_; + uint32_t non_zero_uv_; + uint8_t dither_; // local dithering strength (deduced from non_zero_*) + uint8_t skip_; + uint8_t segment_; +} VP8MBData; + // Persistent information needed by the parallel processing typedef struct { - int id_; // cache row to process (in [0..2]) - int mb_y_; // macroblock position of the row - int filter_row_; // true if row-filtering is needed - VP8FInfo* f_info_; // filter strengths - VP8Io io_; // copy of the VP8Io to pass to put() + int id_; // cache row to process (in [0..2]) + int mb_y_; // macroblock position of the row + int filter_row_; // true if row-filtering is needed + VP8FInfo* f_info_; // filter strengths (swapped with dec->f_info_) + VP8MBData* mb_data_; // reconstruction data (swapped with dec->mb_data_) + VP8Io io_; // copy of the VP8Io to pass to put() } VP8ThreadContext; +// Saved top samples, per macroblock. Fits into a cache-line. +typedef struct { + uint8_t y[16], u[8], v[8]; +} VP8TopSamples; + //------------------------------------------------------------------------------ // VP8Decoder: the main opaque structure handed over to user @@ -198,7 +196,8 @@ struct VP8Decoder { // Worker WebPWorker worker_; - int use_threads_; // use multi-thread + int mt_method_; // multi-thread method: 0=off, 1=[parse+recon][filter] + // 2=[parse][recon+filter] int cache_id_; // current cache row int num_caches_; // number of cached rows of 16 pixels (1, 2 or 3) VP8ThreadContext thread_ctx_; // Thread context @@ -210,17 +209,14 @@ struct VP8Decoder { int tl_mb_x_, tl_mb_y_; // top-left MB that must be in-loop filtered int br_mb_x_, br_mb_y_; // last bottom-right MB that must be decoded - // number of partitions. - int num_parts_; + // number of partitions minus one. + uint32_t num_parts_minus_one_; // per-partition boolean decoders. VP8BitReader parts_[MAX_NUM_PARTITIONS]; - // buffer refresh flags - // bit 0: refresh Gold, bit 1: refresh Alt - // bit 2-3: copy to Gold, bit 4-5: copy to Alt - // bit 6: Gold sign bias, bit 7: Alt sign bias - // bit 8: refresh last frame - uint32_t buffer_flags_; + // Dithering strength, deduced from decoding options + int dither_; // whether to use dithering or not + VP8Random dithering_rg_; // random generator for dithering // dequantization (one set of DC/AC dequant factor per segment) VP8QuantMatrix dqm_[NUM_MB_SEGMENTS]; @@ -229,24 +225,18 @@ struct VP8Decoder { VP8Proba proba_; int use_skip_proba_; uint8_t skip_p_; -#ifndef ONLY_KEYFRAME_CODE - uint8_t intra_p_, last_p_, golden_p_; - VP8Proba proba_saved_; - int update_proba_; -#endif // Boundary data cache and persistent buffers. - uint8_t* intra_t_; // top intra modes values: 4 * mb_w_ - uint8_t intra_l_[4]; // left intra modes values - uint8_t* y_t_; // top luma samples: 16 * mb_w_ - uint8_t* u_t_, *v_t_; // top u/v samples: 8 * mb_w_ each + uint8_t* intra_t_; // top intra modes values: 4 * mb_w_ + uint8_t intra_l_[4]; // left intra modes values - VP8MB* mb_info_; // contextual macroblock info (mb_w_ + 1) - VP8FInfo* f_info_; // filter strength info - uint8_t* yuv_b_; // main block for Y/U/V (size = YUV_SIZE) - int16_t* coeffs_; // 384 coeffs = (16+8+8) * 4*4 + VP8TopSamples* yuv_t_; // top y/u/v samples - uint8_t* cache_y_; // macroblock row for storing unfiltered samples + VP8MB* mb_info_; // contextual macroblock info (mb_w_ + 1) + VP8FInfo* f_info_; // filter strength info + uint8_t* yuv_b_; // main block for Y/U/V (size = YUV_SIZE) + + uint8_t* cache_y_; // macroblock row for storing unfiltered samples uint8_t* cache_u_; uint8_t* cache_v_; int cache_y_stride_; @@ -258,32 +248,21 @@ struct VP8Decoder { // Per macroblock non-persistent infos. int mb_x_, mb_y_; // current position, in macroblock units - uint8_t is_i4x4_; // true if intra4x4 - uint8_t imodes_[16]; // one 16x16 mode (#0) or sixteen 4x4 modes - uint8_t uvmode_; // chroma prediction mode - uint8_t segment_; // block's segment - - // bit-wise info about the content of each sub-4x4 blocks: there are 16 bits - // for luma (bits #0->#15), then 4 bits for chroma-u (#16->#19) and 4 bits for - // chroma-v (#20->#23), each corresponding to one 4x4 block in decoding order. - // If the bit is set, the 4x4 block contains some non-zero coefficients. - uint32_t non_zero_; - uint32_t non_zero_ac_; + VP8MBData* mb_data_; // parsed reconstruction data // Filtering side-info int filter_type_; // 0=off, 1=simple, 2=complex - int filter_row_; // per-row flag VP8FInfo fstrengths_[NUM_MB_SEGMENTS][2]; // precalculated per-segment/type - // extensions - const uint8_t* alpha_data_; // compressed alpha data (if present) + // Alpha + struct ALPHDecoder* alph_dec_; // alpha-plane decoder object + const uint8_t* alpha_data_; // compressed alpha data (if present) size_t alpha_data_size_; - int is_alpha_decoded_; // true if alpha_data_ is decoded in alpha_plane_ - uint8_t* alpha_plane_; // output. Persistent, contains the whole data. - - int layer_colorspace_; - const uint8_t* layer_data_; // compressed layer data (if present) - size_t layer_data_size_; + int is_alpha_decoded_; // true if alpha_data_ is decoded in alpha_plane_ + uint8_t* alpha_plane_mem_; // memory allocated for alpha_plane_ + uint8_t* alpha_plane_; // output. Persistent, contains the whole data. + const uint8_t* alpha_prev_line_; // last decoded alpha row (or NULL) + int alpha_dithering_; // derived from decoding options (0=off, 100=full) }; //------------------------------------------------------------------------------ @@ -296,15 +275,14 @@ int VP8SetError(VP8Decoder* const dec, // in tree.c void VP8ResetProba(VP8Proba* const proba); void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec); -void VP8ParseIntraMode(VP8BitReader* const br, VP8Decoder* const dec); +// parses one row of intra mode data in partition 0, returns !eof +int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec); // in quant.c void VP8ParseQuant(VP8Decoder* const dec); // in frame.c -int VP8InitFrame(VP8Decoder* const dec, VP8Io* io); -// Predict a block and add residual -void VP8ReconstructBlock(VP8Decoder* const dec); +int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io); // Call io->setup() and finish setting up scan parameters. // After this call returns, one must always call VP8ExitCritical() with the // same parameters. Both functions should be used in pair. Returns VP8_STATUS_OK @@ -313,7 +291,15 @@ VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io); // Must always be called in pair with VP8EnterCritical(). // Returns false in case of error. int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io); -// Process the last decoded row (filtering + output) +// Return the multi-threading method to use (0=off), depending +// on options and bitstream size. Only for lossy decoding. +int VP8GetThreadMethod(const WebPDecoderOptions* const options, + const WebPHeaderStructure* const headers, + int width, int height); +// Initialize dithering post-process if needed. +void VP8InitDithering(const WebPDecoderOptions* const options, + VP8Decoder* const dec); +// Process the last decoded row (filtering + output). int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io); // To be called at the start of a new scanline, to initialize predictors. void VP8InitScanline(VP8Decoder* const dec); @@ -322,14 +308,12 @@ int VP8DecodeMB(VP8Decoder* const dec, VP8BitReader* const token_br); // in alpha.c const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, + const VP8Io* const io, int row, int num_rows); -// in layer.c -int VP8DecodeLayer(VP8Decoder* const dec); - //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/dec/vp8l.c b/3rdparty/libwebp/dec/vp8l.c deleted file mode 100644 index 89b5b4bf6e..0000000000 --- a/3rdparty/libwebp/dec/vp8l.c +++ /dev/null @@ -1,1249 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// main entry for the decoder -// -// Authors: Vikas Arora (vikaas.arora@gmail.com) -// Jyrki Alakuijala (jyrki@google.com) - -#include -#include -#include "./vp8li.h" -#include "../dsp/lossless.h" -#include "../dsp/yuv.h" -#include "../utils/huffman.h" -#include "../utils/utils.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#define NUM_ARGB_CACHE_ROWS 16 - -static const int kCodeLengthLiterals = 16; -static const int kCodeLengthRepeatCode = 16; -static const int kCodeLengthExtraBits[3] = { 2, 3, 7 }; -static const int kCodeLengthRepeatOffsets[3] = { 3, 3, 11 }; - -// ----------------------------------------------------------------------------- -// Five Huffman codes are used at each meta code: -// 1. green + length prefix codes + color cache codes, -// 2. alpha, -// 3. red, -// 4. blue, and, -// 5. distance prefix codes. -typedef enum { - GREEN = 0, - RED = 1, - BLUE = 2, - ALPHA = 3, - DIST = 4 -} HuffIndex; - -static const uint16_t kAlphabetSize[HUFFMAN_CODES_PER_META_CODE] = { - NUM_LITERAL_CODES + NUM_LENGTH_CODES, - NUM_LITERAL_CODES, NUM_LITERAL_CODES, NUM_LITERAL_CODES, - NUM_DISTANCE_CODES -}; - - -#define NUM_CODE_LENGTH_CODES 19 -static const uint8_t kCodeLengthCodeOrder[NUM_CODE_LENGTH_CODES] = { - 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 -}; - -#define CODE_TO_PLANE_CODES 120 -static const uint8_t code_to_plane_lut[CODE_TO_PLANE_CODES] = { - 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, - 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, - 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, - 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, - 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, - 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, - 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, - 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, - 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, - 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, - 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, - 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 -}; - -static int DecodeImageStream(int xsize, int ysize, - int is_level0, - VP8LDecoder* const dec, - uint32_t** const decoded_data); - -//------------------------------------------------------------------------------ - -int VP8LCheckSignature(const uint8_t* const data, size_t size) { - return (size >= 1) && (data[0] == VP8L_MAGIC_BYTE); -} - -static int ReadImageInfo(VP8LBitReader* const br, - int* const width, int* const height, - int* const has_alpha) { - const uint8_t signature = VP8LReadBits(br, 8); - if (!VP8LCheckSignature(&signature, 1)) { - return 0; - } - *width = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1; - *height = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1; - *has_alpha = VP8LReadBits(br, 1); - VP8LReadBits(br, VP8L_VERSION_BITS); // Read/ignore the version number. - return 1; -} - -int VP8LGetInfo(const uint8_t* data, size_t data_size, - int* const width, int* const height, int* const has_alpha) { - if (data == NULL || data_size < VP8L_FRAME_HEADER_SIZE) { - return 0; // not enough data - } else { - int w, h, a; - VP8LBitReader br; - VP8LInitBitReader(&br, data, data_size); - if (!ReadImageInfo(&br, &w, &h, &a)) { - return 0; - } - if (width != NULL) *width = w; - if (height != NULL) *height = h; - if (has_alpha != NULL) *has_alpha = a; - return 1; - } -} - -//------------------------------------------------------------------------------ - -static WEBP_INLINE int GetCopyDistance(int distance_symbol, - VP8LBitReader* const br) { - int extra_bits, offset; - if (distance_symbol < 4) { - return distance_symbol + 1; - } - extra_bits = (distance_symbol - 2) >> 1; - offset = (2 + (distance_symbol & 1)) << extra_bits; - return offset + VP8LReadBits(br, extra_bits) + 1; -} - -static WEBP_INLINE int GetCopyLength(int length_symbol, - VP8LBitReader* const br) { - // Length and distance prefixes are encoded the same way. - return GetCopyDistance(length_symbol, br); -} - -static WEBP_INLINE int PlaneCodeToDistance(int xsize, int plane_code) { - if (plane_code > CODE_TO_PLANE_CODES) { - return plane_code - CODE_TO_PLANE_CODES; - } else { - const int dist_code = code_to_plane_lut[plane_code - 1]; - const int yoffset = dist_code >> 4; - const int xoffset = 8 - (dist_code & 0xf); - const int dist = yoffset * xsize + xoffset; - return (dist >= 1) ? dist : 1; - } -} - -//------------------------------------------------------------------------------ -// Decodes the next Huffman code from bit-stream. -// FillBitWindow(br) needs to be called at minimum every second call -// to ReadSymbol, in order to pre-fetch enough bits. -static WEBP_INLINE int ReadSymbol(const HuffmanTree* tree, - VP8LBitReader* const br) { - const HuffmanTreeNode* node = tree->root_; - int num_bits = 0; - uint32_t bits = VP8LPrefetchBits(br); - assert(node != NULL); - while (!HuffmanTreeNodeIsLeaf(node)) { - node = HuffmanTreeNextNode(node, bits & 1); - bits >>= 1; - ++num_bits; - } - VP8LDiscardBits(br, num_bits); - return node->symbol_; -} - -static int ReadHuffmanCodeLengths( - VP8LDecoder* const dec, const int* const code_length_code_lengths, - int num_symbols, int* const code_lengths) { - int ok = 0; - VP8LBitReader* const br = &dec->br_; - int symbol; - int max_symbol; - int prev_code_len = DEFAULT_CODE_LENGTH; - HuffmanTree tree; - - if (!HuffmanTreeBuildImplicit(&tree, code_length_code_lengths, - NUM_CODE_LENGTH_CODES)) { - dec->status_ = VP8_STATUS_BITSTREAM_ERROR; - return 0; - } - - if (VP8LReadBits(br, 1)) { // use length - const int length_nbits = 2 + 2 * VP8LReadBits(br, 3); - max_symbol = 2 + VP8LReadBits(br, length_nbits); - if (max_symbol > num_symbols) { - dec->status_ = VP8_STATUS_BITSTREAM_ERROR; - goto End; - } - } else { - max_symbol = num_symbols; - } - - symbol = 0; - while (symbol < num_symbols) { - int code_len; - if (max_symbol-- == 0) break; - VP8LFillBitWindow(br); - code_len = ReadSymbol(&tree, br); - if (code_len < kCodeLengthLiterals) { - code_lengths[symbol++] = code_len; - if (code_len != 0) prev_code_len = code_len; - } else { - const int use_prev = (code_len == kCodeLengthRepeatCode); - const int slot = code_len - kCodeLengthLiterals; - const int extra_bits = kCodeLengthExtraBits[slot]; - const int repeat_offset = kCodeLengthRepeatOffsets[slot]; - int repeat = VP8LReadBits(br, extra_bits) + repeat_offset; - if (symbol + repeat > num_symbols) { - dec->status_ = VP8_STATUS_BITSTREAM_ERROR; - goto End; - } else { - const int length = use_prev ? prev_code_len : 0; - while (repeat-- > 0) code_lengths[symbol++] = length; - } - } - } - ok = 1; - - End: - HuffmanTreeRelease(&tree); - return ok; -} - -static int ReadHuffmanCode(int alphabet_size, VP8LDecoder* const dec, - HuffmanTree* const tree) { - int ok = 0; - VP8LBitReader* const br = &dec->br_; - const int simple_code = VP8LReadBits(br, 1); - - if (simple_code) { // Read symbols, codes & code lengths directly. - int symbols[2]; - int codes[2]; - int code_lengths[2]; - const int num_symbols = VP8LReadBits(br, 1) + 1; - const int first_symbol_len_code = VP8LReadBits(br, 1); - // The first code is either 1 bit or 8 bit code. - symbols[0] = VP8LReadBits(br, (first_symbol_len_code == 0) ? 1 : 8); - codes[0] = 0; - code_lengths[0] = num_symbols - 1; - // The second code (if present), is always 8 bit long. - if (num_symbols == 2) { - symbols[1] = VP8LReadBits(br, 8); - codes[1] = 1; - code_lengths[1] = num_symbols - 1; - } - ok = HuffmanTreeBuildExplicit(tree, code_lengths, codes, symbols, - alphabet_size, num_symbols); - } else { // Decode Huffman-coded code lengths. - int* code_lengths = NULL; - int i; - int code_length_code_lengths[NUM_CODE_LENGTH_CODES] = { 0 }; - const int num_codes = VP8LReadBits(br, 4) + 4; - if (num_codes > NUM_CODE_LENGTH_CODES) { - dec->status_ = VP8_STATUS_BITSTREAM_ERROR; - return 0; - } - - code_lengths = - (int*)WebPSafeCalloc((uint64_t)alphabet_size, sizeof(*code_lengths)); - if (code_lengths == NULL) { - dec->status_ = VP8_STATUS_OUT_OF_MEMORY; - return 0; - } - - for (i = 0; i < num_codes; ++i) { - code_length_code_lengths[kCodeLengthCodeOrder[i]] = VP8LReadBits(br, 3); - } - ok = ReadHuffmanCodeLengths(dec, code_length_code_lengths, alphabet_size, - code_lengths); - if (ok) { - ok = HuffmanTreeBuildImplicit(tree, code_lengths, alphabet_size); - } - free(code_lengths); - } - ok = ok && !br->error_; - if (!ok) { - dec->status_ = VP8_STATUS_BITSTREAM_ERROR; - return 0; - } - return 1; -} - -static void DeleteHtreeGroups(HTreeGroup* htree_groups, int num_htree_groups) { - if (htree_groups != NULL) { - int i, j; - for (i = 0; i < num_htree_groups; ++i) { - HuffmanTree* const htrees = htree_groups[i].htrees_; - for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) { - HuffmanTreeRelease(&htrees[j]); - } - } - free(htree_groups); - } -} - -static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize, - int color_cache_bits, int allow_recursion) { - int i, j; - VP8LBitReader* const br = &dec->br_; - VP8LMetadata* const hdr = &dec->hdr_; - uint32_t* huffman_image = NULL; - HTreeGroup* htree_groups = NULL; - int num_htree_groups = 1; - - if (allow_recursion && VP8LReadBits(br, 1)) { - // use meta Huffman codes. - const int huffman_precision = VP8LReadBits(br, 3) + 2; - const int huffman_xsize = VP8LSubSampleSize(xsize, huffman_precision); - const int huffman_ysize = VP8LSubSampleSize(ysize, huffman_precision); - const int huffman_pixs = huffman_xsize * huffman_ysize; - if (!DecodeImageStream(huffman_xsize, huffman_ysize, 0, dec, - &huffman_image)) { - dec->status_ = VP8_STATUS_BITSTREAM_ERROR; - goto Error; - } - hdr->huffman_subsample_bits_ = huffman_precision; - for (i = 0; i < huffman_pixs; ++i) { - // The huffman data is stored in red and green bytes. - const int group = (huffman_image[i] >> 8) & 0xffff; - huffman_image[i] = group; - if (group >= num_htree_groups) { - num_htree_groups = group + 1; - } - } - } - - if (br->error_) goto Error; - - assert(num_htree_groups <= 0x10000); - htree_groups = - (HTreeGroup*)WebPSafeCalloc((uint64_t)num_htree_groups, - sizeof(*htree_groups)); - if (htree_groups == NULL) { - dec->status_ = VP8_STATUS_OUT_OF_MEMORY; - goto Error; - } - - for (i = 0; i < num_htree_groups; ++i) { - HuffmanTree* const htrees = htree_groups[i].htrees_; - for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) { - int alphabet_size = kAlphabetSize[j]; - if (j == 0 && color_cache_bits > 0) { - alphabet_size += 1 << color_cache_bits; - } - if (!ReadHuffmanCode(alphabet_size, dec, htrees + j)) goto Error; - } - } - - // All OK. Finalize pointers and return. - hdr->huffman_image_ = huffman_image; - hdr->num_htree_groups_ = num_htree_groups; - hdr->htree_groups_ = htree_groups; - return 1; - - Error: - free(huffman_image); - DeleteHtreeGroups(htree_groups, num_htree_groups); - return 0; -} - -//------------------------------------------------------------------------------ -// Scaling. - -static int AllocateAndInitRescaler(VP8LDecoder* const dec, VP8Io* const io) { - const int num_channels = 4; - const int in_width = io->mb_w; - const int out_width = io->scaled_width; - const int in_height = io->mb_h; - const int out_height = io->scaled_height; - const uint64_t work_size = 2 * num_channels * (uint64_t)out_width; - int32_t* work; // Rescaler work area. - const uint64_t scaled_data_size = num_channels * (uint64_t)out_width; - uint32_t* scaled_data; // Temporary storage for scaled BGRA data. - const uint64_t memory_size = sizeof(*dec->rescaler) + - work_size * sizeof(*work) + - scaled_data_size * sizeof(*scaled_data); - uint8_t* memory = (uint8_t*)WebPSafeCalloc(memory_size, sizeof(*memory)); - if (memory == NULL) { - dec->status_ = VP8_STATUS_OUT_OF_MEMORY; - return 0; - } - assert(dec->rescaler_memory == NULL); - dec->rescaler_memory = memory; - - dec->rescaler = (WebPRescaler*)memory; - memory += sizeof(*dec->rescaler); - work = (int32_t*)memory; - memory += work_size * sizeof(*work); - scaled_data = (uint32_t*)memory; - - WebPRescalerInit(dec->rescaler, in_width, in_height, (uint8_t*)scaled_data, - out_width, out_height, 0, num_channels, - in_width, out_width, in_height, out_height, work); - return 1; -} - -//------------------------------------------------------------------------------ -// Export to ARGB - -// We have special "export" function since we need to convert from BGRA -static int Export(WebPRescaler* const rescaler, WEBP_CSP_MODE colorspace, - int rgba_stride, uint8_t* const rgba) { - const uint32_t* const src = (const uint32_t*)rescaler->dst; - const int dst_width = rescaler->dst_width; - int num_lines_out = 0; - while (WebPRescalerHasPendingOutput(rescaler)) { - uint8_t* const dst = rgba + num_lines_out * rgba_stride; - WebPRescalerExportRow(rescaler); - VP8LConvertFromBGRA(src, dst_width, colorspace, dst); - ++num_lines_out; - } - return num_lines_out; -} - -// Emit scaled rows. -static int EmitRescaledRows(const VP8LDecoder* const dec, - const uint32_t* const data, int in_stride, int mb_h, - uint8_t* const out, int out_stride) { - const WEBP_CSP_MODE colorspace = dec->output_->colorspace; - const uint8_t* const in = (const uint8_t*)data; - int num_lines_in = 0; - int num_lines_out = 0; - while (num_lines_in < mb_h) { - const uint8_t* const row_in = in + num_lines_in * in_stride; - uint8_t* const row_out = out + num_lines_out * out_stride; - num_lines_in += WebPRescalerImport(dec->rescaler, mb_h - num_lines_in, - row_in, in_stride); - num_lines_out += Export(dec->rescaler, colorspace, out_stride, row_out); - } - return num_lines_out; -} - -// Emit rows without any scaling. -static int EmitRows(WEBP_CSP_MODE colorspace, - const uint32_t* const data, int in_stride, - int mb_w, int mb_h, - uint8_t* const out, int out_stride) { - int lines = mb_h; - const uint8_t* row_in = (const uint8_t*)data; - uint8_t* row_out = out; - while (lines-- > 0) { - VP8LConvertFromBGRA((const uint32_t*)row_in, mb_w, colorspace, row_out); - row_in += in_stride; - row_out += out_stride; - } - return mb_h; // Num rows out == num rows in. -} - -//------------------------------------------------------------------------------ -// Export to YUVA - -static void ConvertToYUVA(const uint32_t* const src, int width, int y_pos, - const WebPDecBuffer* const output) { - const WebPYUVABuffer* const buf = &output->u.YUVA; - // first, the luma plane - { - int i; - uint8_t* const y = buf->y + y_pos * buf->y_stride; - for (i = 0; i < width; ++i) { - const uint32_t p = src[i]; - y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff); - } - } - - // then U/V planes - { - uint8_t* const u = buf->u + (y_pos >> 1) * buf->u_stride; - uint8_t* const v = buf->v + (y_pos >> 1) * buf->v_stride; - const int uv_width = width >> 1; - int i; - for (i = 0; i < uv_width; ++i) { - const uint32_t v0 = src[2 * i + 0]; - const uint32_t v1 = src[2 * i + 1]; - // VP8RGBToU/V expects four accumulated pixels. Hence we need to - // scale r/g/b value by a factor 2. We just shift v0/v1 one bit less. - const int r = ((v0 >> 15) & 0x1fe) + ((v1 >> 15) & 0x1fe); - const int g = ((v0 >> 7) & 0x1fe) + ((v1 >> 7) & 0x1fe); - const int b = ((v0 << 1) & 0x1fe) + ((v1 << 1) & 0x1fe); - if (!(y_pos & 1)) { // even lines: store values - u[i] = VP8RGBToU(r, g, b); - v[i] = VP8RGBToV(r, g, b); - } else { // odd lines: average with previous values - const int tmp_u = VP8RGBToU(r, g, b); - const int tmp_v = VP8RGBToV(r, g, b); - // Approximated average-of-four. But it's an acceptable diff. - u[i] = (u[i] + tmp_u + 1) >> 1; - v[i] = (v[i] + tmp_v + 1) >> 1; - } - } - if (width & 1) { // last pixel - const uint32_t v0 = src[2 * i + 0]; - const int r = (v0 >> 14) & 0x3fc; - const int g = (v0 >> 6) & 0x3fc; - const int b = (v0 << 2) & 0x3fc; - if (!(y_pos & 1)) { // even lines - u[i] = VP8RGBToU(r, g, b); - v[i] = VP8RGBToV(r, g, b); - } else { // odd lines (note: we could just skip this) - const int tmp_u = VP8RGBToU(r, g, b); - const int tmp_v = VP8RGBToV(r, g, b); - u[i] = (u[i] + tmp_u + 1) >> 1; - v[i] = (v[i] + tmp_v + 1) >> 1; - } - } - } - // Lastly, store alpha if needed. - if (buf->a != NULL) { - int i; - uint8_t* const a = buf->a + y_pos * buf->a_stride; - for (i = 0; i < width; ++i) a[i] = (src[i] >> 24); - } -} - -static int ExportYUVA(const VP8LDecoder* const dec, int y_pos) { - WebPRescaler* const rescaler = dec->rescaler; - const uint32_t* const src = (const uint32_t*)rescaler->dst; - const int dst_width = rescaler->dst_width; - int num_lines_out = 0; - while (WebPRescalerHasPendingOutput(rescaler)) { - WebPRescalerExportRow(rescaler); - ConvertToYUVA(src, dst_width, y_pos, dec->output_); - ++y_pos; - ++num_lines_out; - } - return num_lines_out; -} - -static int EmitRescaledRowsYUVA(const VP8LDecoder* const dec, - const uint32_t* const data, - int in_stride, int mb_h) { - const uint8_t* const in = (const uint8_t*)data; - int num_lines_in = 0; - int y_pos = dec->last_out_row_; - while (num_lines_in < mb_h) { - const uint8_t* const row_in = in + num_lines_in * in_stride; - num_lines_in += WebPRescalerImport(dec->rescaler, mb_h - num_lines_in, - row_in, in_stride); - y_pos += ExportYUVA(dec, y_pos); - } - return y_pos; -} - -static int EmitRowsYUVA(const VP8LDecoder* const dec, - const uint32_t* const data, int in_stride, - int mb_w, int num_rows) { - int y_pos = dec->last_out_row_; - const uint8_t* row_in = (const uint8_t*)data; - while (num_rows-- > 0) { - ConvertToYUVA((const uint32_t*)row_in, mb_w, y_pos, dec->output_); - row_in += in_stride; - ++y_pos; - } - return y_pos; -} - -//------------------------------------------------------------------------------ -// Cropping. - -// Sets io->mb_y, io->mb_h & io->mb_w according to start row, end row and -// crop options. Also updates the input data pointer, so that it points to the -// start of the cropped window. -// Note that 'pixel_stride' is in units of 'uint32_t' (and not 'bytes). -// Returns true if the crop window is not empty. -static int SetCropWindow(VP8Io* const io, int y_start, int y_end, - const uint32_t** const in_data, int pixel_stride) { - assert(y_start < y_end); - assert(io->crop_left < io->crop_right); - if (y_end > io->crop_bottom) { - y_end = io->crop_bottom; // make sure we don't overflow on last row. - } - if (y_start < io->crop_top) { - const int delta = io->crop_top - y_start; - y_start = io->crop_top; - *in_data += pixel_stride * delta; - } - if (y_start >= y_end) return 0; // Crop window is empty. - - *in_data += io->crop_left; - - io->mb_y = y_start - io->crop_top; - io->mb_w = io->crop_right - io->crop_left; - io->mb_h = y_end - y_start; - return 1; // Non-empty crop window. -} - -//------------------------------------------------------------------------------ - -static WEBP_INLINE int GetMetaIndex( - const uint32_t* const image, int xsize, int bits, int x, int y) { - if (bits == 0) return 0; - return image[xsize * (y >> bits) + (x >> bits)]; -} - -static WEBP_INLINE HTreeGroup* GetHtreeGroupForPos(VP8LMetadata* const hdr, - int x, int y) { - const int meta_index = GetMetaIndex(hdr->huffman_image_, hdr->huffman_xsize_, - hdr->huffman_subsample_bits_, x, y); - assert(meta_index < hdr->num_htree_groups_); - return hdr->htree_groups_ + meta_index; -} - -//------------------------------------------------------------------------------ -// Main loop, with custom row-processing function - -typedef void (*ProcessRowsFunc)(VP8LDecoder* const dec, int row); - -static void ApplyInverseTransforms(VP8LDecoder* const dec, int num_rows, - const uint32_t* const rows) { - int n = dec->next_transform_; - const int cache_pixs = dec->width_ * num_rows; - const int start_row = dec->last_row_; - const int end_row = start_row + num_rows; - const uint32_t* rows_in = rows; - uint32_t* const rows_out = dec->argb_cache_; - - // Inverse transforms. - // TODO: most transforms only need to operate on the cropped region only. - memcpy(rows_out, rows_in, cache_pixs * sizeof(*rows_out)); - while (n-- > 0) { - VP8LTransform* const transform = &dec->transforms_[n]; - VP8LInverseTransform(transform, start_row, end_row, rows_in, rows_out); - rows_in = rows_out; - } -} - -// Special method for paletted alpha data. -static void ApplyInverseTransformsAlpha(VP8LDecoder* const dec, int num_rows, - const uint8_t* const rows) { - const int start_row = dec->last_row_; - const int end_row = start_row + num_rows; - const uint8_t* rows_in = rows; - uint8_t* rows_out = (uint8_t*)dec->io_->opaque + dec->io_->width * start_row; - VP8LTransform* const transform = &dec->transforms_[0]; - assert(dec->next_transform_ == 1); - assert(transform->type_ == COLOR_INDEXING_TRANSFORM); - VP8LColorIndexInverseTransformAlpha(transform, start_row, end_row, rows_in, - rows_out); -} - -// Processes (transforms, scales & color-converts) the rows decoded after the -// last call. -static void ProcessRows(VP8LDecoder* const dec, int row) { - const uint32_t* const rows = dec->pixels_ + dec->width_ * dec->last_row_; - const int num_rows = row - dec->last_row_; - - if (num_rows <= 0) return; // Nothing to be done. - ApplyInverseTransforms(dec, num_rows, rows); - - // Emit output. - { - VP8Io* const io = dec->io_; - const uint32_t* rows_data = dec->argb_cache_; - if (!SetCropWindow(io, dec->last_row_, row, &rows_data, io->width)) { - // Nothing to output (this time). - } else { - const WebPDecBuffer* const output = dec->output_; - const int in_stride = io->width * sizeof(*rows_data); - if (output->colorspace < MODE_YUV) { // convert to RGBA - const WebPRGBABuffer* const buf = &output->u.RGBA; - uint8_t* const rgba = buf->rgba + dec->last_out_row_ * buf->stride; - const int num_rows_out = io->use_scaling ? - EmitRescaledRows(dec, rows_data, in_stride, io->mb_h, - rgba, buf->stride) : - EmitRows(output->colorspace, rows_data, in_stride, - io->mb_w, io->mb_h, rgba, buf->stride); - // Update 'last_out_row_'. - dec->last_out_row_ += num_rows_out; - } else { // convert to YUVA - dec->last_out_row_ = io->use_scaling ? - EmitRescaledRowsYUVA(dec, rows_data, in_stride, io->mb_h) : - EmitRowsYUVA(dec, rows_data, in_stride, io->mb_w, io->mb_h); - } - assert(dec->last_out_row_ <= output->height); - } - } - - // Update 'last_row_'. - dec->last_row_ = row; - assert(dec->last_row_ <= dec->height_); -} - -#define DECODE_DATA_FUNC(FUNC_NAME, TYPE, STORE_PIXEL) \ -static int FUNC_NAME(VP8LDecoder* const dec, TYPE* const data, int width, \ - int height, ProcessRowsFunc process_func) { \ - int ok = 1; \ - int col = 0, row = 0; \ - VP8LBitReader* const br = &dec->br_; \ - VP8LMetadata* const hdr = &dec->hdr_; \ - HTreeGroup* htree_group = hdr->htree_groups_; \ - TYPE* src = data; \ - TYPE* last_cached = data; \ - TYPE* const src_end = data + width * height; \ - const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES; \ - const int color_cache_limit = len_code_limit + hdr->color_cache_size_; \ - VP8LColorCache* const color_cache = \ - (hdr->color_cache_size_ > 0) ? &hdr->color_cache_ : NULL; \ - const int mask = hdr->huffman_mask_; \ - assert(htree_group != NULL); \ - while (!br->eos_ && src < src_end) { \ - int code; \ - /* Only update when changing tile. Note we could use this test: */ \ - /* if "((((prev_col ^ col) | prev_row ^ row)) > mask)" -> tile changed */ \ - /* but that's actually slower and needs storing the previous col/row. */ \ - if ((col & mask) == 0) { \ - htree_group = GetHtreeGroupForPos(hdr, col, row); \ - } \ - VP8LFillBitWindow(br); \ - code = ReadSymbol(&htree_group->htrees_[GREEN], br); \ - if (code < NUM_LITERAL_CODES) { /* Literal*/ \ - int red, green, blue, alpha; \ - red = ReadSymbol(&htree_group->htrees_[RED], br); \ - green = code; \ - VP8LFillBitWindow(br); \ - blue = ReadSymbol(&htree_group->htrees_[BLUE], br); \ - alpha = ReadSymbol(&htree_group->htrees_[ALPHA], br); \ - *src = STORE_PIXEL(alpha, red, green, blue); \ - AdvanceByOne: \ - ++src; \ - ++col; \ - if (col >= width) { \ - col = 0; \ - ++row; \ - if ((process_func != NULL) && (row % NUM_ARGB_CACHE_ROWS == 0)) { \ - process_func(dec, row); \ - } \ - if (color_cache != NULL) { \ - while (last_cached < src) { \ - VP8LColorCacheInsert(color_cache, *last_cached++); \ - } \ - } \ - } \ - } else if (code < len_code_limit) { /* Backward reference */ \ - int dist_code, dist; \ - const int length_sym = code - NUM_LITERAL_CODES; \ - const int length = GetCopyLength(length_sym, br); \ - const int dist_symbol = ReadSymbol(&htree_group->htrees_[DIST], br); \ - VP8LFillBitWindow(br); \ - dist_code = GetCopyDistance(dist_symbol, br); \ - dist = PlaneCodeToDistance(width, dist_code); \ - if (src - data < dist || src_end - src < length) { \ - ok = 0; \ - goto End; \ - } \ - { \ - int i; \ - for (i = 0; i < length; ++i) src[i] = src[i - dist]; \ - src += length; \ - } \ - col += length; \ - while (col >= width) { \ - col -= width; \ - ++row; \ - if ((process_func != NULL) && (row % NUM_ARGB_CACHE_ROWS == 0)) { \ - process_func(dec, row); \ - } \ - } \ - if (src < src_end) { \ - htree_group = GetHtreeGroupForPos(hdr, col, row); \ - if (color_cache != NULL) { \ - while (last_cached < src) { \ - VP8LColorCacheInsert(color_cache, *last_cached++); \ - } \ - } \ - } \ - } else if (code < color_cache_limit) { /* Color cache */ \ - const int key = code - len_code_limit; \ - assert(color_cache != NULL); \ - while (last_cached < src) { \ - VP8LColorCacheInsert(color_cache, *last_cached++); \ - } \ - *src = VP8LColorCacheLookup(color_cache, key); \ - goto AdvanceByOne; \ - } else { /* Not reached */ \ - ok = 0; \ - goto End; \ - } \ - ok = !br->error_; \ - if (!ok) goto End; \ - } \ - /* Process the remaining rows corresponding to last row-block. */ \ - if (process_func != NULL) process_func(dec, row); \ -End: \ - if (br->error_ || !ok || (br->eos_ && src < src_end)) { \ - ok = 0; \ - dec->status_ = \ - (!br->eos_) ? VP8_STATUS_BITSTREAM_ERROR : VP8_STATUS_SUSPENDED; \ - } else if (src == src_end) { \ - dec->state_ = READ_DATA; \ - } \ - return ok; \ -} - -static WEBP_INLINE uint32_t GetARGBPixel(int alpha, int red, int green, - int blue) { - return (alpha << 24) | (red << 16) | (green << 8) | blue; -} - -static WEBP_INLINE uint8_t GetAlphaPixel(int alpha, int red, int green, - int blue) { - (void)alpha; - (void)red; - (void)blue; - return green; // Alpha value is stored in green channel. -} - -DECODE_DATA_FUNC(DecodeImageData, uint32_t, GetARGBPixel) -DECODE_DATA_FUNC(DecodeAlphaData, uint8_t, GetAlphaPixel) - -#undef DECODE_DATA_FUNC - -// ----------------------------------------------------------------------------- -// VP8LTransform - -static void ClearTransform(VP8LTransform* const transform) { - free(transform->data_); - transform->data_ = NULL; -} - -// For security reason, we need to remap the color map to span -// the total possible bundled values, and not just the num_colors. -static int ExpandColorMap(int num_colors, VP8LTransform* const transform) { - int i; - const int final_num_colors = 1 << (8 >> transform->bits_); - uint32_t* const new_color_map = - (uint32_t*)WebPSafeMalloc((uint64_t)final_num_colors, - sizeof(*new_color_map)); - if (new_color_map == NULL) { - return 0; - } else { - uint8_t* const data = (uint8_t*)transform->data_; - uint8_t* const new_data = (uint8_t*)new_color_map; - new_color_map[0] = transform->data_[0]; - for (i = 4; i < 4 * num_colors; ++i) { - // Equivalent to AddPixelEq(), on a byte-basis. - new_data[i] = (data[i] + new_data[i - 4]) & 0xff; - } - for (; i < 4 * final_num_colors; ++i) - new_data[i] = 0; // black tail. - free(transform->data_); - transform->data_ = new_color_map; - } - return 1; -} - -static int ReadTransform(int* const xsize, int const* ysize, - VP8LDecoder* const dec) { - int ok = 1; - VP8LBitReader* const br = &dec->br_; - VP8LTransform* transform = &dec->transforms_[dec->next_transform_]; - const VP8LImageTransformType type = - (VP8LImageTransformType)VP8LReadBits(br, 2); - - // Each transform type can only be present once in the stream. - if (dec->transforms_seen_ & (1U << type)) { - return 0; // Already there, let's not accept the second same transform. - } - dec->transforms_seen_ |= (1U << type); - - transform->type_ = type; - transform->xsize_ = *xsize; - transform->ysize_ = *ysize; - transform->data_ = NULL; - ++dec->next_transform_; - assert(dec->next_transform_ <= NUM_TRANSFORMS); - - switch (type) { - case PREDICTOR_TRANSFORM: - case CROSS_COLOR_TRANSFORM: - transform->bits_ = VP8LReadBits(br, 3) + 2; - ok = DecodeImageStream(VP8LSubSampleSize(transform->xsize_, - transform->bits_), - VP8LSubSampleSize(transform->ysize_, - transform->bits_), - 0, dec, &transform->data_); - break; - case COLOR_INDEXING_TRANSFORM: { - const int num_colors = VP8LReadBits(br, 8) + 1; - const int bits = (num_colors > 16) ? 0 - : (num_colors > 4) ? 1 - : (num_colors > 2) ? 2 - : 3; - *xsize = VP8LSubSampleSize(transform->xsize_, bits); - transform->bits_ = bits; - ok = DecodeImageStream(num_colors, 1, 0, dec, &transform->data_); - ok = ok && ExpandColorMap(num_colors, transform); - break; - } - case SUBTRACT_GREEN: - break; - default: - assert(0); // can't happen - break; - } - - return ok; -} - -// ----------------------------------------------------------------------------- -// VP8LMetadata - -static void InitMetadata(VP8LMetadata* const hdr) { - assert(hdr); - memset(hdr, 0, sizeof(*hdr)); -} - -static void ClearMetadata(VP8LMetadata* const hdr) { - assert(hdr); - - free(hdr->huffman_image_); - DeleteHtreeGroups(hdr->htree_groups_, hdr->num_htree_groups_); - VP8LColorCacheClear(&hdr->color_cache_); - InitMetadata(hdr); -} - -// ----------------------------------------------------------------------------- -// VP8LDecoder - -VP8LDecoder* VP8LNew(void) { - VP8LDecoder* const dec = (VP8LDecoder*)calloc(1, sizeof(*dec)); - if (dec == NULL) return NULL; - dec->status_ = VP8_STATUS_OK; - dec->action_ = READ_DIM; - dec->state_ = READ_DIM; - return dec; -} - -void VP8LClear(VP8LDecoder* const dec) { - int i; - if (dec == NULL) return; - ClearMetadata(&dec->hdr_); - - free(dec->pixels_); - dec->pixels_ = NULL; - for (i = 0; i < dec->next_transform_; ++i) { - ClearTransform(&dec->transforms_[i]); - } - dec->next_transform_ = 0; - dec->transforms_seen_ = 0; - - free(dec->rescaler_memory); - dec->rescaler_memory = NULL; - - dec->output_ = NULL; // leave no trace behind -} - -void VP8LDelete(VP8LDecoder* const dec) { - if (dec != NULL) { - VP8LClear(dec); - free(dec); - } -} - -static void UpdateDecoder(VP8LDecoder* const dec, int width, int height) { - VP8LMetadata* const hdr = &dec->hdr_; - const int num_bits = hdr->huffman_subsample_bits_; - dec->width_ = width; - dec->height_ = height; - - hdr->huffman_xsize_ = VP8LSubSampleSize(width, num_bits); - hdr->huffman_mask_ = (num_bits == 0) ? ~0 : (1 << num_bits) - 1; -} - -static int DecodeImageStream(int xsize, int ysize, - int is_level0, - VP8LDecoder* const dec, - uint32_t** const decoded_data) { - int ok = 1; - int transform_xsize = xsize; - int transform_ysize = ysize; - VP8LBitReader* const br = &dec->br_; - VP8LMetadata* const hdr = &dec->hdr_; - uint32_t* data = NULL; - int color_cache_bits = 0; - - // Read the transforms (may recurse). - if (is_level0) { - while (ok && VP8LReadBits(br, 1)) { - ok = ReadTransform(&transform_xsize, &transform_ysize, dec); - } - } - - // Color cache - if (ok && VP8LReadBits(br, 1)) { - color_cache_bits = VP8LReadBits(br, 4); - ok = (color_cache_bits >= 1 && color_cache_bits <= MAX_CACHE_BITS); - if (!ok) { - dec->status_ = VP8_STATUS_BITSTREAM_ERROR; - goto End; - } - } - - // Read the Huffman codes (may recurse). - ok = ok && ReadHuffmanCodes(dec, transform_xsize, transform_ysize, - color_cache_bits, is_level0); - if (!ok) { - dec->status_ = VP8_STATUS_BITSTREAM_ERROR; - goto End; - } - - // Finish setting up the color-cache - if (color_cache_bits > 0) { - hdr->color_cache_size_ = 1 << color_cache_bits; - if (!VP8LColorCacheInit(&hdr->color_cache_, color_cache_bits)) { - dec->status_ = VP8_STATUS_OUT_OF_MEMORY; - ok = 0; - goto End; - } - } else { - hdr->color_cache_size_ = 0; - } - UpdateDecoder(dec, transform_xsize, transform_ysize); - - if (is_level0) { // level 0 complete - dec->state_ = READ_HDR; - goto End; - } - - { - const uint64_t total_size = (uint64_t)transform_xsize * transform_ysize; - data = (uint32_t*)WebPSafeMalloc(total_size, sizeof(*data)); - if (data == NULL) { - dec->status_ = VP8_STATUS_OUT_OF_MEMORY; - ok = 0; - goto End; - } - } - - // Use the Huffman trees to decode the LZ77 encoded data. - ok = DecodeImageData(dec, data, transform_xsize, transform_ysize, NULL); - ok = ok && !br->error_; - - End: - - if (!ok) { - free(data); - ClearMetadata(hdr); - // If not enough data (br.eos_) resulted in BIT_STREAM_ERROR, update the - // status appropriately. - if (dec->status_ == VP8_STATUS_BITSTREAM_ERROR && dec->br_.eos_) { - dec->status_ = VP8_STATUS_SUSPENDED; - } - } else { - if (decoded_data != NULL) { - *decoded_data = data; - } else { - // We allocate image data in this function only for transforms. At level 0 - // (that is: not the transforms), we shouldn't have allocated anything. - assert(data == NULL); - assert(is_level0); - } - if (!is_level0) ClearMetadata(hdr); // Clean up temporary data behind. - } - return ok; -} - -//------------------------------------------------------------------------------ -// Allocate internal buffers dec->pixels_ and dec->argb_cache_. -static int AllocateInternalBuffers(VP8LDecoder* const dec, int final_width, - size_t bytes_per_pixel) { - const int argb_cache_needed = (bytes_per_pixel == sizeof(uint32_t)); - const uint64_t num_pixels = (uint64_t)dec->width_ * dec->height_; - // Scratch buffer corresponding to top-prediction row for transforming the - // first row in the row-blocks. Not needed for paletted alpha. - const uint64_t cache_top_pixels = - argb_cache_needed ? (uint16_t)final_width : 0ULL; - // Scratch buffer for temporary BGRA storage. Not needed for paletted alpha. - const uint64_t cache_pixels = - argb_cache_needed ? (uint64_t)final_width * NUM_ARGB_CACHE_ROWS : 0ULL; - const uint64_t total_num_pixels = - num_pixels + cache_top_pixels + cache_pixels; - - assert(dec->width_ <= final_width); - dec->pixels_ = (uint32_t*)WebPSafeMalloc(total_num_pixels, bytes_per_pixel); - if (dec->pixels_ == NULL) { - dec->argb_cache_ = NULL; // for sanity check - dec->status_ = VP8_STATUS_OUT_OF_MEMORY; - return 0; - } - dec->argb_cache_ = - argb_cache_needed ? dec->pixels_ + num_pixels + cache_top_pixels : NULL; - return 1; -} - -//------------------------------------------------------------------------------ - -// Special row-processing that only stores the alpha data. -static void ExtractAlphaRows(VP8LDecoder* const dec, int row) { - const int num_rows = row - dec->last_row_; - const uint32_t* const in = dec->pixels_ + dec->width_ * dec->last_row_; - - if (num_rows <= 0) return; // Nothing to be done. - ApplyInverseTransforms(dec, num_rows, in); - - // Extract alpha (which is stored in the green plane). - { - const int width = dec->io_->width; // the final width (!= dec->width_) - const int cache_pixs = width * num_rows; - uint8_t* const dst = (uint8_t*)dec->io_->opaque + width * dec->last_row_; - const uint32_t* const src = dec->argb_cache_; - int i; - for (i = 0; i < cache_pixs; ++i) dst[i] = (src[i] >> 8) & 0xff; - } - dec->last_row_ = dec->last_out_row_ = row; -} - -// Row-processing for the special case when alpha data contains only one -// transform: color indexing. -static void ExtractPalettedAlphaRows(VP8LDecoder* const dec, int row) { - const int num_rows = row - dec->last_row_; - const uint8_t* const in = - (uint8_t*)dec->pixels_ + dec->width_ * dec->last_row_; - if (num_rows <= 0) return; // Nothing to be done. - ApplyInverseTransformsAlpha(dec, num_rows, in); - dec->last_row_ = dec->last_out_row_ = row; -} - -int VP8LDecodeAlphaImageStream(int width, int height, const uint8_t* const data, - size_t data_size, uint8_t* const output) { - VP8Io io; - int ok = 0; - VP8LDecoder* const dec = VP8LNew(); - size_t bytes_per_pixel = sizeof(uint32_t); // Default: BGRA mode. - if (dec == NULL) return 0; - - dec->width_ = width; - dec->height_ = height; - dec->io_ = &io; - - VP8InitIo(&io); - WebPInitCustomIo(NULL, &io); // Just a sanity Init. io won't be used. - io.opaque = output; - io.width = width; - io.height = height; - - dec->status_ = VP8_STATUS_OK; - VP8LInitBitReader(&dec->br_, data, data_size); - - dec->action_ = READ_HDR; - if (!DecodeImageStream(width, height, 1, dec, NULL)) goto Err; - - // Special case: if alpha data uses only the color indexing transform and - // doesn't use color cache (a frequent case), we will use DecodeAlphaData() - // method that only needs allocation of 1 byte per pixel (alpha channel). - if (dec->next_transform_ == 1 && - dec->transforms_[0].type_ == COLOR_INDEXING_TRANSFORM && - dec->hdr_.color_cache_size_ == 0) { - bytes_per_pixel = sizeof(uint8_t); - } - - // Allocate internal buffers (note that dec->width_ may have changed here). - if (!AllocateInternalBuffers(dec, width, bytes_per_pixel)) goto Err; - - // Decode (with special row processing). - dec->action_ = READ_DATA; - ok = (bytes_per_pixel == sizeof(uint8_t)) ? - DecodeAlphaData(dec, (uint8_t*)dec->pixels_, dec->width_, dec->height_, - ExtractPalettedAlphaRows) : - DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_, - ExtractAlphaRows); - - Err: - VP8LDelete(dec); - return ok; -} - -//------------------------------------------------------------------------------ - -int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io) { - int width, height, has_alpha; - - if (dec == NULL) return 0; - if (io == NULL) { - dec->status_ = VP8_STATUS_INVALID_PARAM; - return 0; - } - - dec->io_ = io; - dec->status_ = VP8_STATUS_OK; - VP8LInitBitReader(&dec->br_, io->data, io->data_size); - if (!ReadImageInfo(&dec->br_, &width, &height, &has_alpha)) { - dec->status_ = VP8_STATUS_BITSTREAM_ERROR; - goto Error; - } - dec->state_ = READ_DIM; - io->width = width; - io->height = height; - - dec->action_ = READ_HDR; - if (!DecodeImageStream(width, height, 1, dec, NULL)) goto Error; - return 1; - - Error: - VP8LClear(dec); - assert(dec->status_ != VP8_STATUS_OK); - return 0; -} - -int VP8LDecodeImage(VP8LDecoder* const dec) { - const size_t bytes_per_pixel = sizeof(uint32_t); - VP8Io* io = NULL; - WebPDecParams* params = NULL; - - // Sanity checks. - if (dec == NULL) return 0; - - io = dec->io_; - assert(io != NULL); - params = (WebPDecParams*)io->opaque; - assert(params != NULL); - dec->output_ = params->output; - assert(dec->output_ != NULL); - - // Initialization. - if (!WebPIoInitFromOptions(params->options, io, MODE_BGRA)) { - dec->status_ = VP8_STATUS_INVALID_PARAM; - goto Err; - } - - if (!AllocateInternalBuffers(dec, io->width, bytes_per_pixel)) goto Err; - - if (io->use_scaling && !AllocateAndInitRescaler(dec, io)) goto Err; - - // Decode. - dec->action_ = READ_DATA; - if (!DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_, - ProcessRows)) { - goto Err; - } - - // Cleanup. - params->last_y = dec->last_out_row_; - VP8LClear(dec); - return 1; - - Err: - VP8LClear(dec); - assert(dec->status_ != VP8_STATUS_OK); - return 0; -} - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dec/vp8l_dec.c b/3rdparty/libwebp/dec/vp8l_dec.c new file mode 100644 index 0000000000..ef359a91f0 --- /dev/null +++ b/3rdparty/libwebp/dec/vp8l_dec.c @@ -0,0 +1,1671 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// main entry for the decoder +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) + +#include + +#include "./alphai_dec.h" +#include "./vp8li_dec.h" +#include "../dsp/dsp.h" +#include "../dsp/lossless.h" +#include "../dsp/lossless_common.h" +#include "../dsp/yuv.h" +#include "../utils/endian_inl_utils.h" +#include "../utils/huffman_utils.h" +#include "../utils/utils.h" + +#define NUM_ARGB_CACHE_ROWS 16 + +static const int kCodeLengthLiterals = 16; +static const int kCodeLengthRepeatCode = 16; +static const int kCodeLengthExtraBits[3] = { 2, 3, 7 }; +static const int kCodeLengthRepeatOffsets[3] = { 3, 3, 11 }; + +// ----------------------------------------------------------------------------- +// Five Huffman codes are used at each meta code: +// 1. green + length prefix codes + color cache codes, +// 2. alpha, +// 3. red, +// 4. blue, and, +// 5. distance prefix codes. +typedef enum { + GREEN = 0, + RED = 1, + BLUE = 2, + ALPHA = 3, + DIST = 4 +} HuffIndex; + +static const uint16_t kAlphabetSize[HUFFMAN_CODES_PER_META_CODE] = { + NUM_LITERAL_CODES + NUM_LENGTH_CODES, + NUM_LITERAL_CODES, NUM_LITERAL_CODES, NUM_LITERAL_CODES, + NUM_DISTANCE_CODES +}; + +static const uint8_t kLiteralMap[HUFFMAN_CODES_PER_META_CODE] = { + 0, 1, 1, 1, 0 +}; + +#define NUM_CODE_LENGTH_CODES 19 +static const uint8_t kCodeLengthCodeOrder[NUM_CODE_LENGTH_CODES] = { + 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 +}; + +#define CODE_TO_PLANE_CODES 120 +static const uint8_t kCodeToPlane[CODE_TO_PLANE_CODES] = { + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 +}; + +// Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha +// and distance alphabets are constant (256 for red, blue and alpha, 40 for +// distance) and lookup table sizes for them in worst case are 630 and 410 +// respectively. Size of green alphabet depends on color cache size and is equal +// to 256 (green component values) + 24 (length prefix values) +// + color_cache_size (between 0 and 2048). +// All values computed for 8-bit first level lookup with Mark Adler's tool: +// http://www.hdfgroup.org/ftp/lib-external/zlib/zlib-1.2.5/examples/enough.c +#define FIXED_TABLE_SIZE (630 * 3 + 410) +static const int kTableSize[12] = { + FIXED_TABLE_SIZE + 654, + FIXED_TABLE_SIZE + 656, + FIXED_TABLE_SIZE + 658, + FIXED_TABLE_SIZE + 662, + FIXED_TABLE_SIZE + 670, + FIXED_TABLE_SIZE + 686, + FIXED_TABLE_SIZE + 718, + FIXED_TABLE_SIZE + 782, + FIXED_TABLE_SIZE + 912, + FIXED_TABLE_SIZE + 1168, + FIXED_TABLE_SIZE + 1680, + FIXED_TABLE_SIZE + 2704 +}; + +static int DecodeImageStream(int xsize, int ysize, + int is_level0, + VP8LDecoder* const dec, + uint32_t** const decoded_data); + +//------------------------------------------------------------------------------ + +int VP8LCheckSignature(const uint8_t* const data, size_t size) { + return (size >= VP8L_FRAME_HEADER_SIZE && + data[0] == VP8L_MAGIC_BYTE && + (data[4] >> 5) == 0); // version +} + +static int ReadImageInfo(VP8LBitReader* const br, + int* const width, int* const height, + int* const has_alpha) { + if (VP8LReadBits(br, 8) != VP8L_MAGIC_BYTE) return 0; + *width = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1; + *height = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1; + *has_alpha = VP8LReadBits(br, 1); + if (VP8LReadBits(br, VP8L_VERSION_BITS) != 0) return 0; + return !br->eos_; +} + +int VP8LGetInfo(const uint8_t* data, size_t data_size, + int* const width, int* const height, int* const has_alpha) { + if (data == NULL || data_size < VP8L_FRAME_HEADER_SIZE) { + return 0; // not enough data + } else if (!VP8LCheckSignature(data, data_size)) { + return 0; // bad signature + } else { + int w, h, a; + VP8LBitReader br; + VP8LInitBitReader(&br, data, data_size); + if (!ReadImageInfo(&br, &w, &h, &a)) { + return 0; + } + if (width != NULL) *width = w; + if (height != NULL) *height = h; + if (has_alpha != NULL) *has_alpha = a; + return 1; + } +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE int GetCopyDistance(int distance_symbol, + VP8LBitReader* const br) { + int extra_bits, offset; + if (distance_symbol < 4) { + return distance_symbol + 1; + } + extra_bits = (distance_symbol - 2) >> 1; + offset = (2 + (distance_symbol & 1)) << extra_bits; + return offset + VP8LReadBits(br, extra_bits) + 1; +} + +static WEBP_INLINE int GetCopyLength(int length_symbol, + VP8LBitReader* const br) { + // Length and distance prefixes are encoded the same way. + return GetCopyDistance(length_symbol, br); +} + +static WEBP_INLINE int PlaneCodeToDistance(int xsize, int plane_code) { + if (plane_code > CODE_TO_PLANE_CODES) { + return plane_code - CODE_TO_PLANE_CODES; + } else { + const int dist_code = kCodeToPlane[plane_code - 1]; + const int yoffset = dist_code >> 4; + const int xoffset = 8 - (dist_code & 0xf); + const int dist = yoffset * xsize + xoffset; + return (dist >= 1) ? dist : 1; // dist<1 can happen if xsize is very small + } +} + +//------------------------------------------------------------------------------ +// Decodes the next Huffman code from bit-stream. +// FillBitWindow(br) needs to be called at minimum every second call +// to ReadSymbol, in order to pre-fetch enough bits. +static WEBP_INLINE int ReadSymbol(const HuffmanCode* table, + VP8LBitReader* const br) { + int nbits; + uint32_t val = VP8LPrefetchBits(br); + table += val & HUFFMAN_TABLE_MASK; + nbits = table->bits - HUFFMAN_TABLE_BITS; + if (nbits > 0) { + VP8LSetBitPos(br, br->bit_pos_ + HUFFMAN_TABLE_BITS); + val = VP8LPrefetchBits(br); + table += table->value; + table += val & ((1 << nbits) - 1); + } + VP8LSetBitPos(br, br->bit_pos_ + table->bits); + return table->value; +} + +// Reads packed symbol depending on GREEN channel +#define BITS_SPECIAL_MARKER 0x100 // something large enough (and a bit-mask) +#define PACKED_NON_LITERAL_CODE 0 // must be < NUM_LITERAL_CODES +static WEBP_INLINE int ReadPackedSymbols(const HTreeGroup* group, + VP8LBitReader* const br, + uint32_t* const dst) { + const uint32_t val = VP8LPrefetchBits(br) & (HUFFMAN_PACKED_TABLE_SIZE - 1); + const HuffmanCode32 code = group->packed_table[val]; + assert(group->use_packed_table); + if (code.bits < BITS_SPECIAL_MARKER) { + VP8LSetBitPos(br, br->bit_pos_ + code.bits); + *dst = code.value; + return PACKED_NON_LITERAL_CODE; + } else { + VP8LSetBitPos(br, br->bit_pos_ + code.bits - BITS_SPECIAL_MARKER); + assert(code.value >= NUM_LITERAL_CODES); + return code.value; + } +} + +static int AccumulateHCode(HuffmanCode hcode, int shift, + HuffmanCode32* const huff) { + huff->bits += hcode.bits; + huff->value |= (uint32_t)hcode.value << shift; + assert(huff->bits <= HUFFMAN_TABLE_BITS); + return hcode.bits; +} + +static void BuildPackedTable(HTreeGroup* const htree_group) { + uint32_t code; + for (code = 0; code < HUFFMAN_PACKED_TABLE_SIZE; ++code) { + uint32_t bits = code; + HuffmanCode32* const huff = &htree_group->packed_table[bits]; + HuffmanCode hcode = htree_group->htrees[GREEN][bits]; + if (hcode.value >= NUM_LITERAL_CODES) { + huff->bits = hcode.bits + BITS_SPECIAL_MARKER; + huff->value = hcode.value; + } else { + huff->bits = 0; + huff->value = 0; + bits >>= AccumulateHCode(hcode, 8, huff); + bits >>= AccumulateHCode(htree_group->htrees[RED][bits], 16, huff); + bits >>= AccumulateHCode(htree_group->htrees[BLUE][bits], 0, huff); + bits >>= AccumulateHCode(htree_group->htrees[ALPHA][bits], 24, huff); + (void)bits; + } + } +} + +static int ReadHuffmanCodeLengths( + VP8LDecoder* const dec, const int* const code_length_code_lengths, + int num_symbols, int* const code_lengths) { + int ok = 0; + VP8LBitReader* const br = &dec->br_; + int symbol; + int max_symbol; + int prev_code_len = DEFAULT_CODE_LENGTH; + HuffmanCode table[1 << LENGTHS_TABLE_BITS]; + + if (!VP8LBuildHuffmanTable(table, LENGTHS_TABLE_BITS, + code_length_code_lengths, + NUM_CODE_LENGTH_CODES)) { + goto End; + } + + if (VP8LReadBits(br, 1)) { // use length + const int length_nbits = 2 + 2 * VP8LReadBits(br, 3); + max_symbol = 2 + VP8LReadBits(br, length_nbits); + if (max_symbol > num_symbols) { + goto End; + } + } else { + max_symbol = num_symbols; + } + + symbol = 0; + while (symbol < num_symbols) { + const HuffmanCode* p; + int code_len; + if (max_symbol-- == 0) break; + VP8LFillBitWindow(br); + p = &table[VP8LPrefetchBits(br) & LENGTHS_TABLE_MASK]; + VP8LSetBitPos(br, br->bit_pos_ + p->bits); + code_len = p->value; + if (code_len < kCodeLengthLiterals) { + code_lengths[symbol++] = code_len; + if (code_len != 0) prev_code_len = code_len; + } else { + const int use_prev = (code_len == kCodeLengthRepeatCode); + const int slot = code_len - kCodeLengthLiterals; + const int extra_bits = kCodeLengthExtraBits[slot]; + const int repeat_offset = kCodeLengthRepeatOffsets[slot]; + int repeat = VP8LReadBits(br, extra_bits) + repeat_offset; + if (symbol + repeat > num_symbols) { + goto End; + } else { + const int length = use_prev ? prev_code_len : 0; + while (repeat-- > 0) code_lengths[symbol++] = length; + } + } + } + ok = 1; + + End: + if (!ok) dec->status_ = VP8_STATUS_BITSTREAM_ERROR; + return ok; +} + +// 'code_lengths' is pre-allocated temporary buffer, used for creating Huffman +// tree. +static int ReadHuffmanCode(int alphabet_size, VP8LDecoder* const dec, + int* const code_lengths, HuffmanCode* const table) { + int ok = 0; + int size = 0; + VP8LBitReader* const br = &dec->br_; + const int simple_code = VP8LReadBits(br, 1); + + memset(code_lengths, 0, alphabet_size * sizeof(*code_lengths)); + + if (simple_code) { // Read symbols, codes & code lengths directly. + const int num_symbols = VP8LReadBits(br, 1) + 1; + const int first_symbol_len_code = VP8LReadBits(br, 1); + // The first code is either 1 bit or 8 bit code. + int symbol = VP8LReadBits(br, (first_symbol_len_code == 0) ? 1 : 8); + code_lengths[symbol] = 1; + // The second code (if present), is always 8 bit long. + if (num_symbols == 2) { + symbol = VP8LReadBits(br, 8); + code_lengths[symbol] = 1; + } + ok = 1; + } else { // Decode Huffman-coded code lengths. + int i; + int code_length_code_lengths[NUM_CODE_LENGTH_CODES] = { 0 }; + const int num_codes = VP8LReadBits(br, 4) + 4; + if (num_codes > NUM_CODE_LENGTH_CODES) { + dec->status_ = VP8_STATUS_BITSTREAM_ERROR; + return 0; + } + + for (i = 0; i < num_codes; ++i) { + code_length_code_lengths[kCodeLengthCodeOrder[i]] = VP8LReadBits(br, 3); + } + ok = ReadHuffmanCodeLengths(dec, code_length_code_lengths, alphabet_size, + code_lengths); + } + + ok = ok && !br->eos_; + if (ok) { + size = VP8LBuildHuffmanTable(table, HUFFMAN_TABLE_BITS, + code_lengths, alphabet_size); + } + if (!ok || size == 0) { + dec->status_ = VP8_STATUS_BITSTREAM_ERROR; + return 0; + } + return size; +} + +static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize, + int color_cache_bits, int allow_recursion) { + int i, j; + VP8LBitReader* const br = &dec->br_; + VP8LMetadata* const hdr = &dec->hdr_; + uint32_t* huffman_image = NULL; + HTreeGroup* htree_groups = NULL; + HuffmanCode* huffman_tables = NULL; + HuffmanCode* next = NULL; + int num_htree_groups = 1; + int max_alphabet_size = 0; + int* code_lengths = NULL; + const int table_size = kTableSize[color_cache_bits]; + + if (allow_recursion && VP8LReadBits(br, 1)) { + // use meta Huffman codes. + const int huffman_precision = VP8LReadBits(br, 3) + 2; + const int huffman_xsize = VP8LSubSampleSize(xsize, huffman_precision); + const int huffman_ysize = VP8LSubSampleSize(ysize, huffman_precision); + const int huffman_pixs = huffman_xsize * huffman_ysize; + if (!DecodeImageStream(huffman_xsize, huffman_ysize, 0, dec, + &huffman_image)) { + goto Error; + } + hdr->huffman_subsample_bits_ = huffman_precision; + for (i = 0; i < huffman_pixs; ++i) { + // The huffman data is stored in red and green bytes. + const int group = (huffman_image[i] >> 8) & 0xffff; + huffman_image[i] = group; + if (group >= num_htree_groups) { + num_htree_groups = group + 1; + } + } + } + + if (br->eos_) goto Error; + + // Find maximum alphabet size for the htree group. + for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) { + int alphabet_size = kAlphabetSize[j]; + if (j == 0 && color_cache_bits > 0) { + alphabet_size += 1 << color_cache_bits; + } + if (max_alphabet_size < alphabet_size) { + max_alphabet_size = alphabet_size; + } + } + + huffman_tables = (HuffmanCode*)WebPSafeMalloc(num_htree_groups * table_size, + sizeof(*huffman_tables)); + htree_groups = VP8LHtreeGroupsNew(num_htree_groups); + code_lengths = (int*)WebPSafeCalloc((uint64_t)max_alphabet_size, + sizeof(*code_lengths)); + + if (htree_groups == NULL || code_lengths == NULL || huffman_tables == NULL) { + dec->status_ = VP8_STATUS_OUT_OF_MEMORY; + goto Error; + } + + next = huffman_tables; + for (i = 0; i < num_htree_groups; ++i) { + HTreeGroup* const htree_group = &htree_groups[i]; + HuffmanCode** const htrees = htree_group->htrees; + int size; + int total_size = 0; + int is_trivial_literal = 1; + int max_bits = 0; + for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) { + int alphabet_size = kAlphabetSize[j]; + htrees[j] = next; + if (j == 0 && color_cache_bits > 0) { + alphabet_size += 1 << color_cache_bits; + } + size = ReadHuffmanCode(alphabet_size, dec, code_lengths, next); + if (size == 0) { + goto Error; + } + if (is_trivial_literal && kLiteralMap[j] == 1) { + is_trivial_literal = (next->bits == 0); + } + total_size += next->bits; + next += size; + if (j <= ALPHA) { + int local_max_bits = code_lengths[0]; + int k; + for (k = 1; k < alphabet_size; ++k) { + if (code_lengths[k] > local_max_bits) { + local_max_bits = code_lengths[k]; + } + } + max_bits += local_max_bits; + } + } + htree_group->is_trivial_literal = is_trivial_literal; + htree_group->is_trivial_code = 0; + if (is_trivial_literal) { + const int red = htrees[RED][0].value; + const int blue = htrees[BLUE][0].value; + const int alpha = htrees[ALPHA][0].value; + htree_group->literal_arb = + ((uint32_t)alpha << 24) | (red << 16) | blue; + if (total_size == 0 && htrees[GREEN][0].value < NUM_LITERAL_CODES) { + htree_group->is_trivial_code = 1; + htree_group->literal_arb |= htrees[GREEN][0].value << 8; + } + } + htree_group->use_packed_table = !htree_group->is_trivial_code && + (max_bits < HUFFMAN_PACKED_BITS); + if (htree_group->use_packed_table) BuildPackedTable(htree_group); + } + WebPSafeFree(code_lengths); + + // All OK. Finalize pointers and return. + hdr->huffman_image_ = huffman_image; + hdr->num_htree_groups_ = num_htree_groups; + hdr->htree_groups_ = htree_groups; + hdr->huffman_tables_ = huffman_tables; + return 1; + + Error: + WebPSafeFree(code_lengths); + WebPSafeFree(huffman_image); + WebPSafeFree(huffman_tables); + VP8LHtreeGroupsFree(htree_groups); + return 0; +} + +//------------------------------------------------------------------------------ +// Scaling. + +static int AllocateAndInitRescaler(VP8LDecoder* const dec, VP8Io* const io) { + const int num_channels = 4; + const int in_width = io->mb_w; + const int out_width = io->scaled_width; + const int in_height = io->mb_h; + const int out_height = io->scaled_height; + const uint64_t work_size = 2 * num_channels * (uint64_t)out_width; + rescaler_t* work; // Rescaler work area. + const uint64_t scaled_data_size = (uint64_t)out_width; + uint32_t* scaled_data; // Temporary storage for scaled BGRA data. + const uint64_t memory_size = sizeof(*dec->rescaler) + + work_size * sizeof(*work) + + scaled_data_size * sizeof(*scaled_data); + uint8_t* memory = (uint8_t*)WebPSafeMalloc(memory_size, sizeof(*memory)); + if (memory == NULL) { + dec->status_ = VP8_STATUS_OUT_OF_MEMORY; + return 0; + } + assert(dec->rescaler_memory == NULL); + dec->rescaler_memory = memory; + + dec->rescaler = (WebPRescaler*)memory; + memory += sizeof(*dec->rescaler); + work = (rescaler_t*)memory; + memory += work_size * sizeof(*work); + scaled_data = (uint32_t*)memory; + + WebPRescalerInit(dec->rescaler, in_width, in_height, (uint8_t*)scaled_data, + out_width, out_height, 0, num_channels, work); + return 1; +} + +//------------------------------------------------------------------------------ +// Export to ARGB + +// We have special "export" function since we need to convert from BGRA +static int Export(WebPRescaler* const rescaler, WEBP_CSP_MODE colorspace, + int rgba_stride, uint8_t* const rgba) { + uint32_t* const src = (uint32_t*)rescaler->dst; + const int dst_width = rescaler->dst_width; + int num_lines_out = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + uint8_t* const dst = rgba + num_lines_out * rgba_stride; + WebPRescalerExportRow(rescaler); + WebPMultARGBRow(src, dst_width, 1); + VP8LConvertFromBGRA(src, dst_width, colorspace, dst); + ++num_lines_out; + } + return num_lines_out; +} + +// Emit scaled rows. +static int EmitRescaledRowsRGBA(const VP8LDecoder* const dec, + uint8_t* in, int in_stride, int mb_h, + uint8_t* const out, int out_stride) { + const WEBP_CSP_MODE colorspace = dec->output_->colorspace; + int num_lines_in = 0; + int num_lines_out = 0; + while (num_lines_in < mb_h) { + uint8_t* const row_in = in + num_lines_in * in_stride; + uint8_t* const row_out = out + num_lines_out * out_stride; + const int lines_left = mb_h - num_lines_in; + const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left); + int lines_imported; + assert(needed_lines > 0 && needed_lines <= lines_left); + WebPMultARGBRows(row_in, in_stride, + dec->rescaler->src_width, needed_lines, 0); + lines_imported = + WebPRescalerImport(dec->rescaler, lines_left, row_in, in_stride); + assert(lines_imported == needed_lines); + num_lines_in += lines_imported; + num_lines_out += Export(dec->rescaler, colorspace, out_stride, row_out); + } + return num_lines_out; +} + +// Emit rows without any scaling. +static int EmitRows(WEBP_CSP_MODE colorspace, + const uint8_t* row_in, int in_stride, + int mb_w, int mb_h, + uint8_t* const out, int out_stride) { + int lines = mb_h; + uint8_t* row_out = out; + while (lines-- > 0) { + VP8LConvertFromBGRA((const uint32_t*)row_in, mb_w, colorspace, row_out); + row_in += in_stride; + row_out += out_stride; + } + return mb_h; // Num rows out == num rows in. +} + +//------------------------------------------------------------------------------ +// Export to YUVA + +static void ConvertToYUVA(const uint32_t* const src, int width, int y_pos, + const WebPDecBuffer* const output) { + const WebPYUVABuffer* const buf = &output->u.YUVA; + + // first, the luma plane + WebPConvertARGBToY(src, buf->y + y_pos * buf->y_stride, width); + + // then U/V planes + { + uint8_t* const u = buf->u + (y_pos >> 1) * buf->u_stride; + uint8_t* const v = buf->v + (y_pos >> 1) * buf->v_stride; + // even lines: store values + // odd lines: average with previous values + WebPConvertARGBToUV(src, u, v, width, !(y_pos & 1)); + } + // Lastly, store alpha if needed. + if (buf->a != NULL) { + uint8_t* const a = buf->a + y_pos * buf->a_stride; +#if defined(WORDS_BIGENDIAN) + WebPExtractAlpha((uint8_t*)src + 0, 0, width, 1, a, 0); +#else + WebPExtractAlpha((uint8_t*)src + 3, 0, width, 1, a, 0); +#endif + } +} + +static int ExportYUVA(const VP8LDecoder* const dec, int y_pos) { + WebPRescaler* const rescaler = dec->rescaler; + uint32_t* const src = (uint32_t*)rescaler->dst; + const int dst_width = rescaler->dst_width; + int num_lines_out = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + WebPRescalerExportRow(rescaler); + WebPMultARGBRow(src, dst_width, 1); + ConvertToYUVA(src, dst_width, y_pos, dec->output_); + ++y_pos; + ++num_lines_out; + } + return num_lines_out; +} + +static int EmitRescaledRowsYUVA(const VP8LDecoder* const dec, + uint8_t* in, int in_stride, int mb_h) { + int num_lines_in = 0; + int y_pos = dec->last_out_row_; + while (num_lines_in < mb_h) { + const int lines_left = mb_h - num_lines_in; + const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left); + int lines_imported; + WebPMultARGBRows(in, in_stride, dec->rescaler->src_width, needed_lines, 0); + lines_imported = + WebPRescalerImport(dec->rescaler, lines_left, in, in_stride); + assert(lines_imported == needed_lines); + num_lines_in += lines_imported; + in += needed_lines * in_stride; + y_pos += ExportYUVA(dec, y_pos); + } + return y_pos; +} + +static int EmitRowsYUVA(const VP8LDecoder* const dec, + const uint8_t* in, int in_stride, + int mb_w, int num_rows) { + int y_pos = dec->last_out_row_; + while (num_rows-- > 0) { + ConvertToYUVA((const uint32_t*)in, mb_w, y_pos, dec->output_); + in += in_stride; + ++y_pos; + } + return y_pos; +} + +//------------------------------------------------------------------------------ +// Cropping. + +// Sets io->mb_y, io->mb_h & io->mb_w according to start row, end row and +// crop options. Also updates the input data pointer, so that it points to the +// start of the cropped window. Note that pixels are in ARGB format even if +// 'in_data' is uint8_t*. +// Returns true if the crop window is not empty. +static int SetCropWindow(VP8Io* const io, int y_start, int y_end, + uint8_t** const in_data, int pixel_stride) { + assert(y_start < y_end); + assert(io->crop_left < io->crop_right); + if (y_end > io->crop_bottom) { + y_end = io->crop_bottom; // make sure we don't overflow on last row. + } + if (y_start < io->crop_top) { + const int delta = io->crop_top - y_start; + y_start = io->crop_top; + *in_data += delta * pixel_stride; + } + if (y_start >= y_end) return 0; // Crop window is empty. + + *in_data += io->crop_left * sizeof(uint32_t); + + io->mb_y = y_start - io->crop_top; + io->mb_w = io->crop_right - io->crop_left; + io->mb_h = y_end - y_start; + return 1; // Non-empty crop window. +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE int GetMetaIndex( + const uint32_t* const image, int xsize, int bits, int x, int y) { + if (bits == 0) return 0; + return image[xsize * (y >> bits) + (x >> bits)]; +} + +static WEBP_INLINE HTreeGroup* GetHtreeGroupForPos(VP8LMetadata* const hdr, + int x, int y) { + const int meta_index = GetMetaIndex(hdr->huffman_image_, hdr->huffman_xsize_, + hdr->huffman_subsample_bits_, x, y); + assert(meta_index < hdr->num_htree_groups_); + return hdr->htree_groups_ + meta_index; +} + +//------------------------------------------------------------------------------ +// Main loop, with custom row-processing function + +typedef void (*ProcessRowsFunc)(VP8LDecoder* const dec, int row); + +static void ApplyInverseTransforms(VP8LDecoder* const dec, int num_rows, + const uint32_t* const rows) { + int n = dec->next_transform_; + const int cache_pixs = dec->width_ * num_rows; + const int start_row = dec->last_row_; + const int end_row = start_row + num_rows; + const uint32_t* rows_in = rows; + uint32_t* const rows_out = dec->argb_cache_; + + // Inverse transforms. + while (n-- > 0) { + VP8LTransform* const transform = &dec->transforms_[n]; + VP8LInverseTransform(transform, start_row, end_row, rows_in, rows_out); + rows_in = rows_out; + } + if (rows_in != rows_out) { + // No transform called, hence just copy. + memcpy(rows_out, rows_in, cache_pixs * sizeof(*rows_out)); + } +} + +// Processes (transforms, scales & color-converts) the rows decoded after the +// last call. +static void ProcessRows(VP8LDecoder* const dec, int row) { + const uint32_t* const rows = dec->pixels_ + dec->width_ * dec->last_row_; + const int num_rows = row - dec->last_row_; + + assert(row <= dec->io_->crop_bottom); + // We can't process more than NUM_ARGB_CACHE_ROWS at a time (that's the size + // of argb_cache_), but we currently don't need more than that. + assert(num_rows <= NUM_ARGB_CACHE_ROWS); + if (num_rows > 0) { // Emit output. + VP8Io* const io = dec->io_; + uint8_t* rows_data = (uint8_t*)dec->argb_cache_; + const int in_stride = io->width * sizeof(uint32_t); // in unit of RGBA + + ApplyInverseTransforms(dec, num_rows, rows); + if (!SetCropWindow(io, dec->last_row_, row, &rows_data, in_stride)) { + // Nothing to output (this time). + } else { + const WebPDecBuffer* const output = dec->output_; + if (WebPIsRGBMode(output->colorspace)) { // convert to RGBA + const WebPRGBABuffer* const buf = &output->u.RGBA; + uint8_t* const rgba = buf->rgba + dec->last_out_row_ * buf->stride; + const int num_rows_out = io->use_scaling ? + EmitRescaledRowsRGBA(dec, rows_data, in_stride, io->mb_h, + rgba, buf->stride) : + EmitRows(output->colorspace, rows_data, in_stride, + io->mb_w, io->mb_h, rgba, buf->stride); + // Update 'last_out_row_'. + dec->last_out_row_ += num_rows_out; + } else { // convert to YUVA + dec->last_out_row_ = io->use_scaling ? + EmitRescaledRowsYUVA(dec, rows_data, in_stride, io->mb_h) : + EmitRowsYUVA(dec, rows_data, in_stride, io->mb_w, io->mb_h); + } + assert(dec->last_out_row_ <= output->height); + } + } + + // Update 'last_row_'. + dec->last_row_ = row; + assert(dec->last_row_ <= dec->height_); +} + +// Row-processing for the special case when alpha data contains only one +// transform (color indexing), and trivial non-green literals. +static int Is8bOptimizable(const VP8LMetadata* const hdr) { + int i; + if (hdr->color_cache_size_ > 0) return 0; + // When the Huffman tree contains only one symbol, we can skip the + // call to ReadSymbol() for red/blue/alpha channels. + for (i = 0; i < hdr->num_htree_groups_; ++i) { + HuffmanCode** const htrees = hdr->htree_groups_[i].htrees; + if (htrees[RED][0].bits > 0) return 0; + if (htrees[BLUE][0].bits > 0) return 0; + if (htrees[ALPHA][0].bits > 0) return 0; + } + return 1; +} + +static void AlphaApplyFilter(ALPHDecoder* const alph_dec, + int first_row, int last_row, + uint8_t* out, int stride) { + if (alph_dec->filter_ != WEBP_FILTER_NONE) { + int y; + const uint8_t* prev_line = alph_dec->prev_line_; + assert(WebPUnfilters[alph_dec->filter_] != NULL); + for (y = first_row; y < last_row; ++y) { + WebPUnfilters[alph_dec->filter_](prev_line, out, out, stride); + prev_line = out; + out += stride; + } + alph_dec->prev_line_ = prev_line; + } +} + +static void ExtractPalettedAlphaRows(VP8LDecoder* const dec, int last_row) { + // For vertical and gradient filtering, we need to decode the part above the + // crop_top row, in order to have the correct spatial predictors. + ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io_->opaque; + const int top_row = + (alph_dec->filter_ == WEBP_FILTER_NONE || + alph_dec->filter_ == WEBP_FILTER_HORIZONTAL) ? dec->io_->crop_top + : dec->last_row_; + const int first_row = (dec->last_row_ < top_row) ? top_row : dec->last_row_; + assert(last_row <= dec->io_->crop_bottom); + if (last_row > first_row) { + // Special method for paletted alpha data. We only process the cropped area. + const int width = dec->io_->width; + uint8_t* out = alph_dec->output_ + width * first_row; + const uint8_t* const in = + (uint8_t*)dec->pixels_ + dec->width_ * first_row; + VP8LTransform* const transform = &dec->transforms_[0]; + assert(dec->next_transform_ == 1); + assert(transform->type_ == COLOR_INDEXING_TRANSFORM); + VP8LColorIndexInverseTransformAlpha(transform, first_row, last_row, + in, out); + AlphaApplyFilter(alph_dec, first_row, last_row, out, width); + } + dec->last_row_ = dec->last_out_row_ = last_row; +} + +//------------------------------------------------------------------------------ +// Helper functions for fast pattern copy (8b and 32b) + +// cyclic rotation of pattern word +static WEBP_INLINE uint32_t Rotate8b(uint32_t V) { +#if defined(WORDS_BIGENDIAN) + return ((V & 0xff000000u) >> 24) | (V << 8); +#else + return ((V & 0xffu) << 24) | (V >> 8); +#endif +} + +// copy 1, 2 or 4-bytes pattern +static WEBP_INLINE void CopySmallPattern8b(const uint8_t* src, uint8_t* dst, + int length, uint32_t pattern) { + int i; + // align 'dst' to 4-bytes boundary. Adjust the pattern along the way. + while ((uintptr_t)dst & 3) { + *dst++ = *src++; + pattern = Rotate8b(pattern); + --length; + } + // Copy the pattern 4 bytes at a time. + for (i = 0; i < (length >> 2); ++i) { + ((uint32_t*)dst)[i] = pattern; + } + // Finish with left-overs. 'pattern' is still correctly positioned, + // so no Rotate8b() call is needed. + for (i <<= 2; i < length; ++i) { + dst[i] = src[i]; + } +} + +static WEBP_INLINE void CopyBlock8b(uint8_t* const dst, int dist, int length) { + const uint8_t* src = dst - dist; + if (length >= 8) { + uint32_t pattern = 0; + switch (dist) { + case 1: + pattern = src[0]; +#if defined(__arm__) || defined(_M_ARM) // arm doesn't like multiply that much + pattern |= pattern << 8; + pattern |= pattern << 16; +#elif defined(WEBP_USE_MIPS_DSP_R2) + __asm__ volatile ("replv.qb %0, %0" : "+r"(pattern)); +#else + pattern = 0x01010101u * pattern; +#endif + break; + case 2: + memcpy(&pattern, src, sizeof(uint16_t)); +#if defined(__arm__) || defined(_M_ARM) + pattern |= pattern << 16; +#elif defined(WEBP_USE_MIPS_DSP_R2) + __asm__ volatile ("replv.ph %0, %0" : "+r"(pattern)); +#else + pattern = 0x00010001u * pattern; +#endif + break; + case 4: + memcpy(&pattern, src, sizeof(uint32_t)); + break; + default: + goto Copy; + break; + } + CopySmallPattern8b(src, dst, length, pattern); + return; + } + Copy: + if (dist >= length) { // no overlap -> use memcpy() + memcpy(dst, src, length * sizeof(*dst)); + } else { + int i; + for (i = 0; i < length; ++i) dst[i] = src[i]; + } +} + +// copy pattern of 1 or 2 uint32_t's +static WEBP_INLINE void CopySmallPattern32b(const uint32_t* src, + uint32_t* dst, + int length, uint64_t pattern) { + int i; + if ((uintptr_t)dst & 4) { // Align 'dst' to 8-bytes boundary. + *dst++ = *src++; + pattern = (pattern >> 32) | (pattern << 32); + --length; + } + assert(0 == ((uintptr_t)dst & 7)); + for (i = 0; i < (length >> 1); ++i) { + ((uint64_t*)dst)[i] = pattern; // Copy the pattern 8 bytes at a time. + } + if (length & 1) { // Finish with left-over. + dst[i << 1] = src[i << 1]; + } +} + +static WEBP_INLINE void CopyBlock32b(uint32_t* const dst, + int dist, int length) { + const uint32_t* const src = dst - dist; + if (dist <= 2 && length >= 4 && ((uintptr_t)dst & 3) == 0) { + uint64_t pattern; + if (dist == 1) { + pattern = (uint64_t)src[0]; + pattern |= pattern << 32; + } else { + memcpy(&pattern, src, sizeof(pattern)); + } + CopySmallPattern32b(src, dst, length, pattern); + } else if (dist >= length) { // no overlap + memcpy(dst, src, length * sizeof(*dst)); + } else { + int i; + for (i = 0; i < length; ++i) dst[i] = src[i]; + } +} + +//------------------------------------------------------------------------------ + +static int DecodeAlphaData(VP8LDecoder* const dec, uint8_t* const data, + int width, int height, int last_row) { + int ok = 1; + int row = dec->last_pixel_ / width; + int col = dec->last_pixel_ % width; + VP8LBitReader* const br = &dec->br_; + VP8LMetadata* const hdr = &dec->hdr_; + int pos = dec->last_pixel_; // current position + const int end = width * height; // End of data + const int last = width * last_row; // Last pixel to decode + const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES; + const int mask = hdr->huffman_mask_; + const HTreeGroup* htree_group = + (pos < last) ? GetHtreeGroupForPos(hdr, col, row) : NULL; + assert(pos <= end); + assert(last_row <= height); + assert(Is8bOptimizable(hdr)); + + while (!br->eos_ && pos < last) { + int code; + // Only update when changing tile. + if ((col & mask) == 0) { + htree_group = GetHtreeGroupForPos(hdr, col, row); + } + assert(htree_group != NULL); + VP8LFillBitWindow(br); + code = ReadSymbol(htree_group->htrees[GREEN], br); + if (code < NUM_LITERAL_CODES) { // Literal + data[pos] = code; + ++pos; + ++col; + if (col >= width) { + col = 0; + ++row; + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + ExtractPalettedAlphaRows(dec, row); + } + } + } else if (code < len_code_limit) { // Backward reference + int dist_code, dist; + const int length_sym = code - NUM_LITERAL_CODES; + const int length = GetCopyLength(length_sym, br); + const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br); + VP8LFillBitWindow(br); + dist_code = GetCopyDistance(dist_symbol, br); + dist = PlaneCodeToDistance(width, dist_code); + if (pos >= dist && end - pos >= length) { + CopyBlock8b(data + pos, dist, length); + } else { + ok = 0; + goto End; + } + pos += length; + col += length; + while (col >= width) { + col -= width; + ++row; + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + ExtractPalettedAlphaRows(dec, row); + } + } + if (pos < last && (col & mask)) { + htree_group = GetHtreeGroupForPos(hdr, col, row); + } + } else { // Not reached + ok = 0; + goto End; + } + assert(br->eos_ == VP8LIsEndOfStream(br)); + } + // Process the remaining rows corresponding to last row-block. + ExtractPalettedAlphaRows(dec, row > last_row ? last_row : row); + + End: + if (!ok || (br->eos_ && pos < end)) { + ok = 0; + dec->status_ = br->eos_ ? VP8_STATUS_SUSPENDED + : VP8_STATUS_BITSTREAM_ERROR; + } else { + dec->last_pixel_ = pos; + } + return ok; +} + +static void SaveState(VP8LDecoder* const dec, int last_pixel) { + assert(dec->incremental_); + dec->saved_br_ = dec->br_; + dec->saved_last_pixel_ = last_pixel; + if (dec->hdr_.color_cache_size_ > 0) { + VP8LColorCacheCopy(&dec->hdr_.color_cache_, &dec->hdr_.saved_color_cache_); + } +} + +static void RestoreState(VP8LDecoder* const dec) { + assert(dec->br_.eos_); + dec->status_ = VP8_STATUS_SUSPENDED; + dec->br_ = dec->saved_br_; + dec->last_pixel_ = dec->saved_last_pixel_; + if (dec->hdr_.color_cache_size_ > 0) { + VP8LColorCacheCopy(&dec->hdr_.saved_color_cache_, &dec->hdr_.color_cache_); + } +} + +#define SYNC_EVERY_N_ROWS 8 // minimum number of rows between check-points +static int DecodeImageData(VP8LDecoder* const dec, uint32_t* const data, + int width, int height, int last_row, + ProcessRowsFunc process_func) { + int row = dec->last_pixel_ / width; + int col = dec->last_pixel_ % width; + VP8LBitReader* const br = &dec->br_; + VP8LMetadata* const hdr = &dec->hdr_; + uint32_t* src = data + dec->last_pixel_; + uint32_t* last_cached = src; + uint32_t* const src_end = data + width * height; // End of data + uint32_t* const src_last = data + width * last_row; // Last pixel to decode + const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES; + const int color_cache_limit = len_code_limit + hdr->color_cache_size_; + int next_sync_row = dec->incremental_ ? row : 1 << 24; + VP8LColorCache* const color_cache = + (hdr->color_cache_size_ > 0) ? &hdr->color_cache_ : NULL; + const int mask = hdr->huffman_mask_; + const HTreeGroup* htree_group = + (src < src_last) ? GetHtreeGroupForPos(hdr, col, row) : NULL; + assert(dec->last_row_ < last_row); + assert(src_last <= src_end); + + while (src < src_last) { + int code; + if (row >= next_sync_row) { + SaveState(dec, (int)(src - data)); + next_sync_row = row + SYNC_EVERY_N_ROWS; + } + // Only update when changing tile. Note we could use this test: + // if "((((prev_col ^ col) | prev_row ^ row)) > mask)" -> tile changed + // but that's actually slower and needs storing the previous col/row. + if ((col & mask) == 0) { + htree_group = GetHtreeGroupForPos(hdr, col, row); + } + assert(htree_group != NULL); + if (htree_group->is_trivial_code) { + *src = htree_group->literal_arb; + goto AdvanceByOne; + } + VP8LFillBitWindow(br); + if (htree_group->use_packed_table) { + code = ReadPackedSymbols(htree_group, br, src); + if (code == PACKED_NON_LITERAL_CODE) goto AdvanceByOne; + } else { + code = ReadSymbol(htree_group->htrees[GREEN], br); + } + if (br->eos_) break; // early out + if (code < NUM_LITERAL_CODES) { // Literal + if (htree_group->is_trivial_literal) { + *src = htree_group->literal_arb | (code << 8); + } else { + int red, blue, alpha; + red = ReadSymbol(htree_group->htrees[RED], br); + VP8LFillBitWindow(br); + blue = ReadSymbol(htree_group->htrees[BLUE], br); + alpha = ReadSymbol(htree_group->htrees[ALPHA], br); + if (br->eos_) break; + *src = ((uint32_t)alpha << 24) | (red << 16) | (code << 8) | blue; + } + AdvanceByOne: + ++src; + ++col; + if (col >= width) { + col = 0; + ++row; + if (process_func != NULL) { + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + process_func(dec, row); + } + } + if (color_cache != NULL) { + while (last_cached < src) { + VP8LColorCacheInsert(color_cache, *last_cached++); + } + } + } + } else if (code < len_code_limit) { // Backward reference + int dist_code, dist; + const int length_sym = code - NUM_LITERAL_CODES; + const int length = GetCopyLength(length_sym, br); + const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br); + VP8LFillBitWindow(br); + dist_code = GetCopyDistance(dist_symbol, br); + dist = PlaneCodeToDistance(width, dist_code); + if (br->eos_) break; + if (src - data < (ptrdiff_t)dist || src_end - src < (ptrdiff_t)length) { + goto Error; + } else { + CopyBlock32b(src, dist, length); + } + src += length; + col += length; + while (col >= width) { + col -= width; + ++row; + if (process_func != NULL) { + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + process_func(dec, row); + } + } + } + // Because of the check done above (before 'src' was incremented by + // 'length'), the following holds true. + assert(src <= src_end); + if (col & mask) htree_group = GetHtreeGroupForPos(hdr, col, row); + if (color_cache != NULL) { + while (last_cached < src) { + VP8LColorCacheInsert(color_cache, *last_cached++); + } + } + } else if (code < color_cache_limit) { // Color cache + const int key = code - len_code_limit; + assert(color_cache != NULL); + while (last_cached < src) { + VP8LColorCacheInsert(color_cache, *last_cached++); + } + *src = VP8LColorCacheLookup(color_cache, key); + goto AdvanceByOne; + } else { // Not reached + goto Error; + } + assert(br->eos_ == VP8LIsEndOfStream(br)); + } + + if (dec->incremental_ && br->eos_ && src < src_end) { + RestoreState(dec); + } else if (!br->eos_) { + // Process the remaining rows corresponding to last row-block. + if (process_func != NULL) { + process_func(dec, row > last_row ? last_row : row); + } + dec->status_ = VP8_STATUS_OK; + dec->last_pixel_ = (int)(src - data); // end-of-scan marker + } else { + // if not incremental, and we are past the end of buffer (eos_=1), then this + // is a real bitstream error. + goto Error; + } + return 1; + + Error: + dec->status_ = VP8_STATUS_BITSTREAM_ERROR; + return 0; +} + +// ----------------------------------------------------------------------------- +// VP8LTransform + +static void ClearTransform(VP8LTransform* const transform) { + WebPSafeFree(transform->data_); + transform->data_ = NULL; +} + +// For security reason, we need to remap the color map to span +// the total possible bundled values, and not just the num_colors. +static int ExpandColorMap(int num_colors, VP8LTransform* const transform) { + int i; + const int final_num_colors = 1 << (8 >> transform->bits_); + uint32_t* const new_color_map = + (uint32_t*)WebPSafeMalloc((uint64_t)final_num_colors, + sizeof(*new_color_map)); + if (new_color_map == NULL) { + return 0; + } else { + uint8_t* const data = (uint8_t*)transform->data_; + uint8_t* const new_data = (uint8_t*)new_color_map; + new_color_map[0] = transform->data_[0]; + for (i = 4; i < 4 * num_colors; ++i) { + // Equivalent to AddPixelEq(), on a byte-basis. + new_data[i] = (data[i] + new_data[i - 4]) & 0xff; + } + for (; i < 4 * final_num_colors; ++i) { + new_data[i] = 0; // black tail. + } + WebPSafeFree(transform->data_); + transform->data_ = new_color_map; + } + return 1; +} + +static int ReadTransform(int* const xsize, int const* ysize, + VP8LDecoder* const dec) { + int ok = 1; + VP8LBitReader* const br = &dec->br_; + VP8LTransform* transform = &dec->transforms_[dec->next_transform_]; + const VP8LImageTransformType type = + (VP8LImageTransformType)VP8LReadBits(br, 2); + + // Each transform type can only be present once in the stream. + if (dec->transforms_seen_ & (1U << type)) { + return 0; // Already there, let's not accept the second same transform. + } + dec->transforms_seen_ |= (1U << type); + + transform->type_ = type; + transform->xsize_ = *xsize; + transform->ysize_ = *ysize; + transform->data_ = NULL; + ++dec->next_transform_; + assert(dec->next_transform_ <= NUM_TRANSFORMS); + + switch (type) { + case PREDICTOR_TRANSFORM: + case CROSS_COLOR_TRANSFORM: + transform->bits_ = VP8LReadBits(br, 3) + 2; + ok = DecodeImageStream(VP8LSubSampleSize(transform->xsize_, + transform->bits_), + VP8LSubSampleSize(transform->ysize_, + transform->bits_), + 0, dec, &transform->data_); + break; + case COLOR_INDEXING_TRANSFORM: { + const int num_colors = VP8LReadBits(br, 8) + 1; + const int bits = (num_colors > 16) ? 0 + : (num_colors > 4) ? 1 + : (num_colors > 2) ? 2 + : 3; + *xsize = VP8LSubSampleSize(transform->xsize_, bits); + transform->bits_ = bits; + ok = DecodeImageStream(num_colors, 1, 0, dec, &transform->data_); + ok = ok && ExpandColorMap(num_colors, transform); + break; + } + case SUBTRACT_GREEN: + break; + default: + assert(0); // can't happen + break; + } + + return ok; +} + +// ----------------------------------------------------------------------------- +// VP8LMetadata + +static void InitMetadata(VP8LMetadata* const hdr) { + assert(hdr != NULL); + memset(hdr, 0, sizeof(*hdr)); +} + +static void ClearMetadata(VP8LMetadata* const hdr) { + assert(hdr != NULL); + + WebPSafeFree(hdr->huffman_image_); + WebPSafeFree(hdr->huffman_tables_); + VP8LHtreeGroupsFree(hdr->htree_groups_); + VP8LColorCacheClear(&hdr->color_cache_); + VP8LColorCacheClear(&hdr->saved_color_cache_); + InitMetadata(hdr); +} + +// ----------------------------------------------------------------------------- +// VP8LDecoder + +VP8LDecoder* VP8LNew(void) { + VP8LDecoder* const dec = (VP8LDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + if (dec == NULL) return NULL; + dec->status_ = VP8_STATUS_OK; + dec->state_ = READ_DIM; + + VP8LDspInit(); // Init critical function pointers. + + return dec; +} + +void VP8LClear(VP8LDecoder* const dec) { + int i; + if (dec == NULL) return; + ClearMetadata(&dec->hdr_); + + WebPSafeFree(dec->pixels_); + dec->pixels_ = NULL; + for (i = 0; i < dec->next_transform_; ++i) { + ClearTransform(&dec->transforms_[i]); + } + dec->next_transform_ = 0; + dec->transforms_seen_ = 0; + + WebPSafeFree(dec->rescaler_memory); + dec->rescaler_memory = NULL; + + dec->output_ = NULL; // leave no trace behind +} + +void VP8LDelete(VP8LDecoder* const dec) { + if (dec != NULL) { + VP8LClear(dec); + WebPSafeFree(dec); + } +} + +static void UpdateDecoder(VP8LDecoder* const dec, int width, int height) { + VP8LMetadata* const hdr = &dec->hdr_; + const int num_bits = hdr->huffman_subsample_bits_; + dec->width_ = width; + dec->height_ = height; + + hdr->huffman_xsize_ = VP8LSubSampleSize(width, num_bits); + hdr->huffman_mask_ = (num_bits == 0) ? ~0 : (1 << num_bits) - 1; +} + +static int DecodeImageStream(int xsize, int ysize, + int is_level0, + VP8LDecoder* const dec, + uint32_t** const decoded_data) { + int ok = 1; + int transform_xsize = xsize; + int transform_ysize = ysize; + VP8LBitReader* const br = &dec->br_; + VP8LMetadata* const hdr = &dec->hdr_; + uint32_t* data = NULL; + int color_cache_bits = 0; + + // Read the transforms (may recurse). + if (is_level0) { + while (ok && VP8LReadBits(br, 1)) { + ok = ReadTransform(&transform_xsize, &transform_ysize, dec); + } + } + + // Color cache + if (ok && VP8LReadBits(br, 1)) { + color_cache_bits = VP8LReadBits(br, 4); + ok = (color_cache_bits >= 1 && color_cache_bits <= MAX_CACHE_BITS); + if (!ok) { + dec->status_ = VP8_STATUS_BITSTREAM_ERROR; + goto End; + } + } + + // Read the Huffman codes (may recurse). + ok = ok && ReadHuffmanCodes(dec, transform_xsize, transform_ysize, + color_cache_bits, is_level0); + if (!ok) { + dec->status_ = VP8_STATUS_BITSTREAM_ERROR; + goto End; + } + + // Finish setting up the color-cache + if (color_cache_bits > 0) { + hdr->color_cache_size_ = 1 << color_cache_bits; + if (!VP8LColorCacheInit(&hdr->color_cache_, color_cache_bits)) { + dec->status_ = VP8_STATUS_OUT_OF_MEMORY; + ok = 0; + goto End; + } + } else { + hdr->color_cache_size_ = 0; + } + UpdateDecoder(dec, transform_xsize, transform_ysize); + + if (is_level0) { // level 0 complete + dec->state_ = READ_HDR; + goto End; + } + + { + const uint64_t total_size = (uint64_t)transform_xsize * transform_ysize; + data = (uint32_t*)WebPSafeMalloc(total_size, sizeof(*data)); + if (data == NULL) { + dec->status_ = VP8_STATUS_OUT_OF_MEMORY; + ok = 0; + goto End; + } + } + + // Use the Huffman trees to decode the LZ77 encoded data. + ok = DecodeImageData(dec, data, transform_xsize, transform_ysize, + transform_ysize, NULL); + ok = ok && !br->eos_; + + End: + if (!ok) { + WebPSafeFree(data); + ClearMetadata(hdr); + } else { + if (decoded_data != NULL) { + *decoded_data = data; + } else { + // We allocate image data in this function only for transforms. At level 0 + // (that is: not the transforms), we shouldn't have allocated anything. + assert(data == NULL); + assert(is_level0); + } + dec->last_pixel_ = 0; // Reset for future DECODE_DATA_FUNC() calls. + if (!is_level0) ClearMetadata(hdr); // Clean up temporary data behind. + } + return ok; +} + +//------------------------------------------------------------------------------ +// Allocate internal buffers dec->pixels_ and dec->argb_cache_. +static int AllocateInternalBuffers32b(VP8LDecoder* const dec, int final_width) { + const uint64_t num_pixels = (uint64_t)dec->width_ * dec->height_; + // Scratch buffer corresponding to top-prediction row for transforming the + // first row in the row-blocks. Not needed for paletted alpha. + const uint64_t cache_top_pixels = (uint16_t)final_width; + // Scratch buffer for temporary BGRA storage. Not needed for paletted alpha. + const uint64_t cache_pixels = (uint64_t)final_width * NUM_ARGB_CACHE_ROWS; + const uint64_t total_num_pixels = + num_pixels + cache_top_pixels + cache_pixels; + + assert(dec->width_ <= final_width); + dec->pixels_ = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint32_t)); + if (dec->pixels_ == NULL) { + dec->argb_cache_ = NULL; // for sanity check + dec->status_ = VP8_STATUS_OUT_OF_MEMORY; + return 0; + } + dec->argb_cache_ = dec->pixels_ + num_pixels + cache_top_pixels; + return 1; +} + +static int AllocateInternalBuffers8b(VP8LDecoder* const dec) { + const uint64_t total_num_pixels = (uint64_t)dec->width_ * dec->height_; + dec->argb_cache_ = NULL; // for sanity check + dec->pixels_ = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint8_t)); + if (dec->pixels_ == NULL) { + dec->status_ = VP8_STATUS_OUT_OF_MEMORY; + return 0; + } + return 1; +} + +//------------------------------------------------------------------------------ + +// Special row-processing that only stores the alpha data. +static void ExtractAlphaRows(VP8LDecoder* const dec, int last_row) { + int cur_row = dec->last_row_; + int num_rows = last_row - cur_row; + const uint32_t* in = dec->pixels_ + dec->width_ * cur_row; + + assert(last_row <= dec->io_->crop_bottom); + while (num_rows > 0) { + const int num_rows_to_process = + (num_rows > NUM_ARGB_CACHE_ROWS) ? NUM_ARGB_CACHE_ROWS : num_rows; + // Extract alpha (which is stored in the green plane). + ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io_->opaque; + uint8_t* const output = alph_dec->output_; + const int width = dec->io_->width; // the final width (!= dec->width_) + const int cache_pixs = width * num_rows_to_process; + uint8_t* const dst = output + width * cur_row; + const uint32_t* const src = dec->argb_cache_; + ApplyInverseTransforms(dec, num_rows_to_process, in); + WebPExtractGreen(src, dst, cache_pixs); + AlphaApplyFilter(alph_dec, + cur_row, cur_row + num_rows_to_process, dst, width); + num_rows -= num_rows_to_process; + in += num_rows_to_process * dec->width_; + cur_row += num_rows_to_process; + } + assert(cur_row == last_row); + dec->last_row_ = dec->last_out_row_ = last_row; +} + +int VP8LDecodeAlphaHeader(ALPHDecoder* const alph_dec, + const uint8_t* const data, size_t data_size) { + int ok = 0; + VP8LDecoder* dec = VP8LNew(); + + if (dec == NULL) return 0; + + assert(alph_dec != NULL); + alph_dec->vp8l_dec_ = dec; + + dec->width_ = alph_dec->width_; + dec->height_ = alph_dec->height_; + dec->io_ = &alph_dec->io_; + dec->io_->opaque = alph_dec; + dec->io_->width = alph_dec->width_; + dec->io_->height = alph_dec->height_; + + dec->status_ = VP8_STATUS_OK; + VP8LInitBitReader(&dec->br_, data, data_size); + + if (!DecodeImageStream(alph_dec->width_, alph_dec->height_, 1, dec, NULL)) { + goto Err; + } + + // Special case: if alpha data uses only the color indexing transform and + // doesn't use color cache (a frequent case), we will use DecodeAlphaData() + // method that only needs allocation of 1 byte per pixel (alpha channel). + if (dec->next_transform_ == 1 && + dec->transforms_[0].type_ == COLOR_INDEXING_TRANSFORM && + Is8bOptimizable(&dec->hdr_)) { + alph_dec->use_8b_decode_ = 1; + ok = AllocateInternalBuffers8b(dec); + } else { + // Allocate internal buffers (note that dec->width_ may have changed here). + alph_dec->use_8b_decode_ = 0; + ok = AllocateInternalBuffers32b(dec, alph_dec->width_); + } + + if (!ok) goto Err; + + return 1; + + Err: + VP8LDelete(alph_dec->vp8l_dec_); + alph_dec->vp8l_dec_ = NULL; + return 0; +} + +int VP8LDecodeAlphaImageStream(ALPHDecoder* const alph_dec, int last_row) { + VP8LDecoder* const dec = alph_dec->vp8l_dec_; + assert(dec != NULL); + assert(last_row <= dec->height_); + + if (dec->last_row_ >= last_row) { + return 1; // done + } + + if (!alph_dec->use_8b_decode_) WebPInitAlphaProcessing(); + + // Decode (with special row processing). + return alph_dec->use_8b_decode_ ? + DecodeAlphaData(dec, (uint8_t*)dec->pixels_, dec->width_, dec->height_, + last_row) : + DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_, + last_row, ExtractAlphaRows); +} + +//------------------------------------------------------------------------------ + +int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io) { + int width, height, has_alpha; + + if (dec == NULL) return 0; + if (io == NULL) { + dec->status_ = VP8_STATUS_INVALID_PARAM; + return 0; + } + + dec->io_ = io; + dec->status_ = VP8_STATUS_OK; + VP8LInitBitReader(&dec->br_, io->data, io->data_size); + if (!ReadImageInfo(&dec->br_, &width, &height, &has_alpha)) { + dec->status_ = VP8_STATUS_BITSTREAM_ERROR; + goto Error; + } + dec->state_ = READ_DIM; + io->width = width; + io->height = height; + + if (!DecodeImageStream(width, height, 1, dec, NULL)) goto Error; + return 1; + + Error: + VP8LClear(dec); + assert(dec->status_ != VP8_STATUS_OK); + return 0; +} + +int VP8LDecodeImage(VP8LDecoder* const dec) { + VP8Io* io = NULL; + WebPDecParams* params = NULL; + + // Sanity checks. + if (dec == NULL) return 0; + + assert(dec->hdr_.huffman_tables_ != NULL); + assert(dec->hdr_.htree_groups_ != NULL); + assert(dec->hdr_.num_htree_groups_ > 0); + + io = dec->io_; + assert(io != NULL); + params = (WebPDecParams*)io->opaque; + assert(params != NULL); + + // Initialization. + if (dec->state_ != READ_DATA) { + dec->output_ = params->output; + assert(dec->output_ != NULL); + + if (!WebPIoInitFromOptions(params->options, io, MODE_BGRA)) { + dec->status_ = VP8_STATUS_INVALID_PARAM; + goto Err; + } + + if (!AllocateInternalBuffers32b(dec, io->width)) goto Err; + + if (io->use_scaling && !AllocateAndInitRescaler(dec, io)) goto Err; + + if (io->use_scaling || WebPIsPremultipliedMode(dec->output_->colorspace)) { + // need the alpha-multiply functions for premultiplied output or rescaling + WebPInitAlphaProcessing(); + } + if (!WebPIsRGBMode(dec->output_->colorspace)) { + WebPInitConvertARGBToYUV(); + if (dec->output_->u.YUVA.a != NULL) WebPInitAlphaProcessing(); + } + if (dec->incremental_) { + if (dec->hdr_.color_cache_size_ > 0 && + dec->hdr_.saved_color_cache_.colors_ == NULL) { + if (!VP8LColorCacheInit(&dec->hdr_.saved_color_cache_, + dec->hdr_.color_cache_.hash_bits_)) { + dec->status_ = VP8_STATUS_OUT_OF_MEMORY; + goto Err; + } + } + } + dec->state_ = READ_DATA; + } + + // Decode. + if (!DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_, + io->crop_bottom, ProcessRows)) { + goto Err; + } + + params->last_y = dec->last_out_row_; + return 1; + + Err: + VP8LClear(dec); + assert(dec->status_ != VP8_STATUS_OK); + return 0; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/dec/vp8li.h b/3rdparty/libwebp/dec/vp8li_dec.h similarity index 71% rename from 3rdparty/libwebp/dec/vp8li.h rename to 3rdparty/libwebp/dec/vp8li_dec.h index 543a767590..097a9d0589 100644 --- a/3rdparty/libwebp/dec/vp8li.h +++ b/3rdparty/libwebp/dec/vp8li_dec.h @@ -16,13 +16,12 @@ #define WEBP_DEC_VP8LI_H_ #include // for memcpy() -#include "./webpi.h" -#include "../utils/bit_reader.h" -#include "../utils/color_cache.h" -#include "../utils/huffman.h" -#include "../webp/format_constants.h" +#include "./webpi_dec.h" +#include "../utils/bit_reader_utils.h" +#include "../utils/color_cache_utils.h" +#include "../utils/huffman_utils.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -41,13 +40,10 @@ struct VP8LTransform { uint32_t *data_; // transform data. }; -typedef struct { - HuffmanTree htrees_[HUFFMAN_CODES_PER_META_CODE]; -} HTreeGroup; - typedef struct { int color_cache_size_; VP8LColorCache color_cache_; + VP8LColorCache saved_color_cache_; // for incremental int huffman_mask_; int huffman_subsample_bits_; @@ -55,11 +51,12 @@ typedef struct { uint32_t *huffman_image_; int num_htree_groups_; HTreeGroup *htree_groups_; + HuffmanCode *huffman_tables_; } VP8LMetadata; -typedef struct { +typedef struct VP8LDecoder VP8LDecoder; +struct VP8LDecoder { VP8StatusCode status_; - VP8LDecodeState action_; VP8LDecodeState state_; VP8Io *io_; @@ -70,10 +67,16 @@ typedef struct { uint32_t *argb_cache_; // Scratch buffer for temporary BGRA storage. VP8LBitReader br_; + int incremental_; // if true, incremental decoding is expected + VP8LBitReader saved_br_; // note: could be local variables too + int saved_last_pixel_; int width_; int height_; int last_row_; // last input row decoded so far. + int last_pixel_; // last pixel decoded so far. However, it may + // not be transformed, scaled and + // color-converted yet. int last_out_row_; // last row output so far. VP8LMetadata hdr_; @@ -85,18 +88,26 @@ typedef struct { uint8_t *rescaler_memory; // Working memory for rescaling work. WebPRescaler *rescaler; // Common rescaler for all channels. -} VP8LDecoder; +}; //------------------------------------------------------------------------------ // internal functions. Not public. +struct ALPHDecoder; // Defined in dec/alphai.h. + // in vp8l.c -// Decodes a raw image stream (without header) and store the alpha data -// into *output, which must be of size width x height. Returns false in case -// of error. -int VP8LDecodeAlphaImageStream(int width, int height, const uint8_t* const data, - size_t data_size, uint8_t* const output); +// Decodes image header for alpha data stored using lossless compression. +// Returns false in case of error. +int VP8LDecodeAlphaHeader(struct ALPHDecoder* const alph_dec, + const uint8_t* const data, size_t data_size); + +// Decodes *at least* 'last_row' rows of alpha. If some of the initial rows are +// already decoded in previous call(s), it will resume decoding from where it +// was paused. +// Returns false in case of bitstream error. +int VP8LDecodeAlphaImageStream(struct ALPHDecoder* const alph_dec, + int last_row); // Allocates and initialize a new lossless decoder instance. VP8LDecoder* VP8LNew(void); @@ -117,7 +128,7 @@ void VP8LDelete(VP8LDecoder* const dec); //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/dec/webp.c b/3rdparty/libwebp/dec/webp_dec.c similarity index 82% rename from 3rdparty/libwebp/dec/webp.c rename to 3rdparty/libwebp/dec/webp_dec.c index 97e79b64da..a8e9c2c510 100644 --- a/3rdparty/libwebp/dec/webp.c +++ b/3rdparty/libwebp/dec/webp_dec.c @@ -13,15 +13,12 @@ #include -#include "./vp8i.h" -#include "./vp8li.h" -#include "./webpi.h" +#include "./vp8i_dec.h" +#include "./vp8li_dec.h" +#include "./webpi_dec.h" +#include "../utils/utils.h" #include "../webp/mux_types.h" // ALPHA_FLAG -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - //------------------------------------------------------------------------------ // RIFF layout is: // Offset tag @@ -42,27 +39,20 @@ extern "C" { // 20..23 VP8X flags bit-map corresponding to the chunk-types present. // 24..26 Width of the Canvas Image. // 27..29 Height of the Canvas Image. -// There can be extra chunks after the "VP8X" chunk (ICCP, FRGM, ANMF, VP8, -// VP8L, XMP, EXIF ...) +// There can be extra chunks after the "VP8X" chunk (ICCP, ANMF, VP8, VP8L, +// XMP, EXIF ...) // All sizes are in little-endian order. // Note: chunk data size must be padded to multiple of 2 when written. -static WEBP_INLINE uint32_t get_le24(const uint8_t* const data) { - return data[0] | (data[1] << 8) | (data[2] << 16); -} - -static WEBP_INLINE uint32_t get_le32(const uint8_t* const data) { - return (uint32_t)get_le24(data) | (data[3] << 24); -} - // Validates the RIFF container (if detected) and skips over it. -// If a RIFF container is detected, -// Returns VP8_STATUS_BITSTREAM_ERROR for invalid header, and -// VP8_STATUS_OK otherwise. +// If a RIFF container is detected, returns: +// VP8_STATUS_BITSTREAM_ERROR for invalid header, +// VP8_STATUS_NOT_ENOUGH_DATA for truncated data if have_all_data is true, +// and VP8_STATUS_OK otherwise. // In case there are not enough bytes (partial RIFF container), return 0 for // *riff_size. Else return the RIFF size extracted from the header. static VP8StatusCode ParseRIFF(const uint8_t** const data, - size_t* const data_size, + size_t* const data_size, int have_all_data, size_t* const riff_size) { assert(data != NULL); assert(data_size != NULL); @@ -73,7 +63,7 @@ static VP8StatusCode ParseRIFF(const uint8_t** const data, if (memcmp(*data + 8, "WEBP", TAG_SIZE)) { return VP8_STATUS_BITSTREAM_ERROR; // Wrong image file signature. } else { - const uint32_t size = get_le32(*data + TAG_SIZE); + const uint32_t size = GetLE32(*data + TAG_SIZE); // Check that we have at least one chunk (i.e "WEBP" + "VP8?nnnn"). if (size < TAG_SIZE + CHUNK_HEADER_SIZE) { return VP8_STATUS_BITSTREAM_ERROR; @@ -81,6 +71,9 @@ static VP8StatusCode ParseRIFF(const uint8_t** const data, if (size > MAX_CHUNK_PAYLOAD) { return VP8_STATUS_BITSTREAM_ERROR; } + if (have_all_data && (size > *data_size - CHUNK_HEADER_SIZE)) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Truncated bitstream. + } // We have a RIFF container. Skip it. *riff_size = size; *data += RIFF_HEADER_SIZE; @@ -116,7 +109,7 @@ static VP8StatusCode ParseVP8X(const uint8_t** const data, if (!memcmp(*data, "VP8X", TAG_SIZE)) { int width, height; uint32_t flags; - const uint32_t chunk_size = get_le32(*data + TAG_SIZE); + const uint32_t chunk_size = GetLE32(*data + TAG_SIZE); if (chunk_size != VP8X_CHUNK_SIZE) { return VP8_STATUS_BITSTREAM_ERROR; // Wrong chunk size. } @@ -125,9 +118,9 @@ static VP8StatusCode ParseVP8X(const uint8_t** const data, if (*data_size < vp8x_size) { return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. } - flags = get_le32(*data + 8); - width = 1 + get_le24(*data + 12); - height = 1 + get_le24(*data + 15); + flags = GetLE32(*data + 8); + width = 1 + GetLE24(*data + 12); + height = 1 + GetLE24(*data + 15); if (width * (uint64_t)height >= MAX_IMAGE_AREA) { return VP8_STATUS_BITSTREAM_ERROR; // image is too large } @@ -181,7 +174,7 @@ static VP8StatusCode ParseOptionalChunks(const uint8_t** const data, return VP8_STATUS_NOT_ENOUGH_DATA; } - chunk_size = get_le32(buf + TAG_SIZE); + chunk_size = GetLE32(buf + TAG_SIZE); if (chunk_size > MAX_CHUNK_PAYLOAD) { return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size. } @@ -227,9 +220,8 @@ static VP8StatusCode ParseOptionalChunks(const uint8_t** const data, // extracted from the VP8/VP8L chunk header. // The flag '*is_lossless' is set to 1 in case of VP8L chunk / raw VP8L data. static VP8StatusCode ParseVP8Header(const uint8_t** const data_ptr, - size_t* const data_size, - size_t riff_size, - size_t* const chunk_size, + size_t* const data_size, int have_all_data, + size_t riff_size, size_t* const chunk_size, int* const is_lossless) { const uint8_t* const data = *data_ptr; const int is_vp8 = !memcmp(data, "VP8 ", TAG_SIZE); @@ -248,10 +240,13 @@ static VP8StatusCode ParseVP8Header(const uint8_t** const data_ptr, if (is_vp8 || is_vp8l) { // Bitstream contains VP8/VP8L header. - const uint32_t size = get_le32(data + TAG_SIZE); + const uint32_t size = GetLE32(data + TAG_SIZE); if ((riff_size >= minimal_size) && (size > riff_size - minimal_size)) { return VP8_STATUS_BITSTREAM_ERROR; // Inconsistent size information. } + if (have_all_data && (size > *data_size - CHUNK_HEADER_SIZE)) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Truncated bitstream. + } // Skip over CHUNK_HEADER_SIZE bytes from VP8/VP8L Header. *chunk_size = size; *data_ptr += CHUNK_HEADER_SIZE; @@ -285,9 +280,17 @@ static VP8StatusCode ParseHeadersInternal(const uint8_t* data, int* const height, int* const has_alpha, int* const has_animation, + int* const format, WebPHeaderStructure* const headers) { + int canvas_width = 0; + int canvas_height = 0; + int image_width = 0; + int image_height = 0; int found_riff = 0; int found_vp8x = 0; + int animation_present = 0; + const int have_all_data = (headers != NULL) ? headers->have_all_data : 0; + VP8StatusCode status; WebPHeaderStructure hdrs; @@ -299,7 +302,7 @@ static VP8StatusCode ParseHeadersInternal(const uint8_t* data, hdrs.data_size = data_size; // Skip over RIFF header. - status = ParseRIFF(&data, &data_size, &hdrs.riff_size); + status = ParseRIFF(&data, &data_size, have_all_data, &hdrs.riff_size); if (status != VP8_STATUS_OK) { return status; // Wrong RIFF header / insufficient data. } @@ -308,23 +311,33 @@ static VP8StatusCode ParseHeadersInternal(const uint8_t* data, // Skip over VP8X. { uint32_t flags = 0; - status = ParseVP8X(&data, &data_size, &found_vp8x, width, height, &flags); + status = ParseVP8X(&data, &data_size, &found_vp8x, + &canvas_width, &canvas_height, &flags); if (status != VP8_STATUS_OK) { return status; // Wrong VP8X / insufficient data. } + animation_present = !!(flags & ANIMATION_FLAG); if (!found_riff && found_vp8x) { // Note: This restriction may be removed in the future, if it becomes // necessary to send VP8X chunk to the decoder. return VP8_STATUS_BITSTREAM_ERROR; } if (has_alpha != NULL) *has_alpha = !!(flags & ALPHA_FLAG); - if (has_animation != NULL) *has_animation = !!(flags & ANIMATION_FLAG); - if (found_vp8x && headers == NULL) { - return VP8_STATUS_OK; // Return features from VP8X header. + if (has_animation != NULL) *has_animation = animation_present; + if (format != NULL) *format = 0; // default = undefined + + image_width = canvas_width; + image_height = canvas_height; + if (found_vp8x && animation_present && headers == NULL) { + status = VP8_STATUS_OK; + goto ReturnWidthHeight; // Just return features from VP8X header. } } - if (data_size < TAG_SIZE) return VP8_STATUS_NOT_ENOUGH_DATA; + if (data_size < TAG_SIZE) { + status = VP8_STATUS_NOT_ENOUGH_DATA; + goto ReturnWidthHeight; + } // Skip over optional chunks if data started with "RIFF + VP8X" or "ALPH". if ((found_riff && found_vp8x) || @@ -332,43 +345,49 @@ static VP8StatusCode ParseHeadersInternal(const uint8_t* data, status = ParseOptionalChunks(&data, &data_size, hdrs.riff_size, &hdrs.alpha_data, &hdrs.alpha_data_size); if (status != VP8_STATUS_OK) { - return status; // Found an invalid chunk size / insufficient data. + goto ReturnWidthHeight; // Invalid chunk size / insufficient data. } } // Skip over VP8/VP8L header. - status = ParseVP8Header(&data, &data_size, hdrs.riff_size, + status = ParseVP8Header(&data, &data_size, have_all_data, hdrs.riff_size, &hdrs.compressed_size, &hdrs.is_lossless); if (status != VP8_STATUS_OK) { - return status; // Wrong VP8/VP8L chunk-header / insufficient data. + goto ReturnWidthHeight; // Wrong VP8/VP8L chunk-header / insufficient data. } if (hdrs.compressed_size > MAX_CHUNK_PAYLOAD) { return VP8_STATUS_BITSTREAM_ERROR; } + if (format != NULL && !animation_present) { + *format = hdrs.is_lossless ? 2 : 1; + } + if (!hdrs.is_lossless) { if (data_size < VP8_FRAME_HEADER_SIZE) { - return VP8_STATUS_NOT_ENOUGH_DATA; + status = VP8_STATUS_NOT_ENOUGH_DATA; + goto ReturnWidthHeight; } // Validates raw VP8 data. - if (!VP8GetInfo(data, data_size, - (uint32_t)hdrs.compressed_size, width, height)) { + if (!VP8GetInfo(data, data_size, (uint32_t)hdrs.compressed_size, + &image_width, &image_height)) { return VP8_STATUS_BITSTREAM_ERROR; } } else { if (data_size < VP8L_FRAME_HEADER_SIZE) { - return VP8_STATUS_NOT_ENOUGH_DATA; + status = VP8_STATUS_NOT_ENOUGH_DATA; + goto ReturnWidthHeight; } // Validates raw VP8L data. - if (!VP8LGetInfo(data, data_size, width, height, has_alpha)) { + if (!VP8LGetInfo(data, data_size, &image_width, &image_height, has_alpha)) { return VP8_STATUS_BITSTREAM_ERROR; } } - - if (has_alpha != NULL) { - // If the data did not contain a VP8X/VP8L chunk the only definitive way - // to set this is by looking for alpha data (from an ALPH chunk). - *has_alpha |= (hdrs.alpha_data != NULL); + // Validates image size coherency. + if (found_vp8x) { + if (canvas_width != image_width || canvas_height != image_height) { + return VP8_STATUS_BITSTREAM_ERROR; + } } if (headers != NULL) { *headers = hdrs; @@ -376,16 +395,31 @@ static VP8StatusCode ParseHeadersInternal(const uint8_t* data, assert((uint64_t)(data - headers->data) < MAX_CHUNK_PAYLOAD); assert(headers->offset == headers->data_size - data_size); } - return VP8_STATUS_OK; // Return features from VP8 header. + ReturnWidthHeight: + if (status == VP8_STATUS_OK || + (status == VP8_STATUS_NOT_ENOUGH_DATA && found_vp8x && headers == NULL)) { + if (has_alpha != NULL) { + // If the data did not contain a VP8X/VP8L chunk the only definitive way + // to set this is by looking for alpha data (from an ALPH chunk). + *has_alpha |= (hdrs.alpha_data != NULL); + } + if (width != NULL) *width = image_width; + if (height != NULL) *height = image_height; + return VP8_STATUS_OK; + } else { + return status; + } } VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers) { - VP8StatusCode status; + // status is marked volatile as a workaround for a clang-3.8 (aarch64) bug + volatile VP8StatusCode status; int has_animation = 0; assert(headers != NULL); // fill out headers, ignore width/height/has_alpha. status = ParseHeadersInternal(headers->data, headers->data_size, - NULL, NULL, NULL, &has_animation, headers); + NULL, NULL, NULL, &has_animation, + NULL, headers); if (status == VP8_STATUS_OK || status == VP8_STATUS_NOT_ENOUGH_DATA) { // TODO(jzern): full support of animation frames will require API additions. if (has_animation) { @@ -399,7 +433,7 @@ VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers) { // WebPDecParams void WebPResetDecParams(WebPDecParams* const params) { - if (params) { + if (params != NULL) { memset(params, 0, sizeof(*params)); } } @@ -416,6 +450,7 @@ static VP8StatusCode DecodeInto(const uint8_t* const data, size_t data_size, headers.data = data; headers.data_size = data_size; + headers.have_all_data = 1; status = WebPParseHeaders(&headers); // Process Pre-VP8 chunks. if (status != VP8_STATUS_OK) { return status; @@ -432,11 +467,6 @@ static VP8StatusCode DecodeInto(const uint8_t* const data, size_t data_size, if (dec == NULL) { return VP8_STATUS_OUT_OF_MEMORY; } -#ifdef WEBP_USE_THREAD - dec->use_threads_ = params->options && (params->options->use_threads > 0); -#else - dec->use_threads_ = 0; -#endif dec->alpha_data_ = headers.alpha_data; dec->alpha_data_size_ = headers.alpha_data_size; @@ -448,6 +478,10 @@ static VP8StatusCode DecodeInto(const uint8_t* const data, size_t data_size, status = WebPAllocateDecBuffer(io.width, io.height, params->options, params->output); if (status == VP8_STATUS_OK) { // Decode + // This change must be done before calling VP8Decode() + dec->mt_method_ = VP8GetThreadMethod(params->options, &headers, + io.width, io.height); + VP8InitDithering(params->options, dec); if (!VP8Decode(dec, &io)) { status = dec->status_; } @@ -476,6 +510,12 @@ static VP8StatusCode DecodeInto(const uint8_t* const data, size_t data_size, if (status != VP8_STATUS_OK) { WebPFreeDecBuffer(params->output); + } else { + if (params->options != NULL && params->options->flip) { + // This restores the original stride values if options->flip was used + // during the call to WebPAllocateDecBuffer above. + status = WebPFlipBuffer(params->output); + } } return status; } @@ -634,7 +674,6 @@ uint8_t* WebPDecodeYUV(const uint8_t* data, size_t data_size, static void DefaultFeatures(WebPBitstreamFeatures* const features) { assert(features != NULL); memset(features, 0, sizeof(*features)); - features->bitstream_version = 0; } static VP8StatusCode GetFeatures(const uint8_t* const data, size_t data_size, @@ -648,7 +687,7 @@ static VP8StatusCode GetFeatures(const uint8_t* const data, size_t data_size, return ParseHeadersInternal(data, data_size, &features->width, &features->height, &features->has_alpha, &features->has_animation, - NULL); + &features->format, NULL); } //------------------------------------------------------------------------------ @@ -719,9 +758,24 @@ VP8StatusCode WebPDecode(const uint8_t* data, size_t data_size, } WebPResetDecParams(¶ms); - params.output = &config->output; params.options = &config->options; - status = DecodeInto(data, data_size, ¶ms); + params.output = &config->output; + if (WebPAvoidSlowMemory(params.output, &config->input)) { + // decoding to slow memory: use a temporary in-mem buffer to decode into. + WebPDecBuffer in_mem_buffer; + WebPInitDecBuffer(&in_mem_buffer); + in_mem_buffer.colorspace = config->output.colorspace; + in_mem_buffer.width = config->input.width; + in_mem_buffer.height = config->input.height; + params.output = &in_mem_buffer; + status = DecodeInto(data, data_size, ¶ms); + if (status == VP8_STATUS_OK) { // do the slow-copy + status = WebPCopyDecBufferPixels(&in_mem_buffer, &config->output); + } + WebPFreeDecBuffer(&in_mem_buffer); + } else { + status = DecodeInto(data, data_size, ¶ms); + } return status; } @@ -742,9 +796,9 @@ int WebPIoInitFromOptions(const WebPDecoderOptions* const options, h = options->crop_height; x = options->crop_left; y = options->crop_top; - if (!WebPIsRGBMode(src_colorspace)) { // only snap for YUV420 or YUV422 + if (!WebPIsRGBMode(src_colorspace)) { // only snap for YUV420 x &= ~1; - y &= ~1; // TODO(later): only for YUV420, not YUV422. + y &= ~1; } if (x < 0 || y < 0 || w <= 0 || h <= 0 || x + w > W || y + h > H) { return 0; // out of frame boundary error @@ -760,15 +814,17 @@ int WebPIoInitFromOptions(const WebPDecoderOptions* const options, // Scaling io->use_scaling = (options != NULL) && (options->use_scaling > 0); if (io->use_scaling) { - if (options->scaled_width <= 0 || options->scaled_height <= 0) { + int scaled_width = options->scaled_width; + int scaled_height = options->scaled_height; + if (!WebPRescalerGetScaledDimensions(w, h, &scaled_width, &scaled_height)) { return 0; } - io->scaled_width = options->scaled_width; - io->scaled_height = options->scaled_height; + io->scaled_width = scaled_width; + io->scaled_height = scaled_height; } // Filter - io->bypass_filtering = options && options->bypass_filtering; + io->bypass_filtering = (options != NULL) && options->bypass_filtering; // Fancy upsampler #ifdef FANCY_UPSAMPLING @@ -785,7 +841,3 @@ int WebPIoInitFromOptions(const WebPDecoderOptions* const options, } //------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dec/webpi.h b/3rdparty/libwebp/dec/webpi_dec.h similarity index 78% rename from 3rdparty/libwebp/dec/webpi.h rename to 3rdparty/libwebp/dec/webpi_dec.h index 4ae0bfc5a0..696abc1958 100644 --- a/3rdparty/libwebp/dec/webpi.h +++ b/3rdparty/libwebp/dec/webpi_dec.h @@ -14,19 +14,22 @@ #ifndef WEBP_DEC_WEBPI_H_ #define WEBP_DEC_WEBPI_H_ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -#include "../utils/rescaler.h" -#include "./decode_vp8.h" +#include "../utils/rescaler_utils.h" +#include "./vp8_dec.h" //------------------------------------------------------------------------------ // WebPDecParams: Decoding output parameters. Transient internal object. typedef struct WebPDecParams WebPDecParams; typedef int (*OutputFunc)(const VP8Io* const io, WebPDecParams* const p); -typedef int (*OutputRowFunc)(WebPDecParams* const p, int y_pos); +typedef int (*OutputAlphaFunc)(const VP8Io* const io, WebPDecParams* const p, + int expected_num_out_lines); +typedef int (*OutputRowFunc)(WebPDecParams* const p, int y_pos, + int max_out_lines); struct WebPDecParams { WebPDecBuffer* output; // output buffer. @@ -35,12 +38,12 @@ struct WebPDecParams { int last_y; // coordinate of the line that was last output const WebPDecoderOptions* options; // if not NULL, use alt decoding features - // rescalers - WebPRescaler scaler_y, scaler_u, scaler_v, scaler_a; + + WebPRescaler* scaler_y, *scaler_u, *scaler_v, *scaler_a; // rescalers void* memory; // overall scratch memory for the output work. OutputFunc emit; // output RGB or YUV samples - OutputFunc emit_alpha; // output alpha channel + OutputAlphaFunc emit_alpha; // output alpha channel OutputRowFunc emit_alpha_row; // output one line of rescaled alpha values }; @@ -54,6 +57,7 @@ void WebPResetDecParams(WebPDecParams* const params); typedef struct { const uint8_t* data; // input buffer size_t data_size; // input buffer size + int have_all_data; // true if all data is known to be available size_t offset; // offset to main data chunk (VP8 or VP8L) const uint8_t* alpha_data; // points to alpha chunk (if present) size_t alpha_data_size; // alpha chunk size @@ -93,23 +97,36 @@ int WebPIoInitFromOptions(const WebPDecoderOptions* const options, // dimension / etc.). If *options is not NULL, also verify that the options' // parameters are valid and apply them to the width/height dimensions of the // output buffer. This takes cropping / scaling / rotation into account. +// Also incorporates the options->flip flag to flip the buffer parameters if +// needed. VP8StatusCode WebPAllocateDecBuffer(int width, int height, const WebPDecoderOptions* const options, WebPDecBuffer* const buffer); +// Flip buffer vertically by negating the various strides. +VP8StatusCode WebPFlipBuffer(WebPDecBuffer* const buffer); + // Copy 'src' into 'dst' buffer, making sure 'dst' is not marked as owner of the -// memory (still held by 'src'). +// memory (still held by 'src'). No pixels are copied. void WebPCopyDecBuffer(const WebPDecBuffer* const src, WebPDecBuffer* const dst); // Copy and transfer ownership from src to dst (beware of parameter order!) void WebPGrabDecBuffer(WebPDecBuffer* const src, WebPDecBuffer* const dst); +// Copy pixels from 'src' into a *preallocated* 'dst' buffer. Returns +// VP8_STATUS_INVALID_PARAM if the 'dst' is not set up correctly for the copy. +VP8StatusCode WebPCopyDecBufferPixels(const WebPDecBuffer* const src, + WebPDecBuffer* const dst); +// Returns true if decoding will be slow with the current configuration +// and bitstream features. +int WebPAvoidSlowMemory(const WebPDecBuffer* const output, + const WebPBitstreamFeatures* const features); //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/demux/Makefile.am b/3rdparty/libwebp/demux/Makefile.am new file mode 100644 index 0000000000..c47536bb55 --- /dev/null +++ b/3rdparty/libwebp/demux/Makefile.am @@ -0,0 +1,14 @@ +lib_LTLIBRARIES = libwebpdemux.la + +libwebpdemux_la_SOURCES = +libwebpdemux_la_SOURCES += anim_decode.c demux.c + +libwebpdemuxinclude_HEADERS = +libwebpdemuxinclude_HEADERS += ../webp/demux.h +libwebpdemuxinclude_HEADERS += ../webp/mux_types.h +libwebpdemuxinclude_HEADERS += ../webp/types.h + +libwebpdemux_la_LIBADD = ../libwebp.la +libwebpdemux_la_LDFLAGS = -no-undefined -version-info 2:2:0 +libwebpdemuxincludedir = $(includedir)/webp +pkgconfig_DATA = libwebpdemux.pc diff --git a/3rdparty/libwebp/demux/anim_decode.c b/3rdparty/libwebp/demux/anim_decode.c new file mode 100644 index 0000000000..f1cf176e72 --- /dev/null +++ b/3rdparty/libwebp/demux/anim_decode.c @@ -0,0 +1,454 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// AnimDecoder implementation. +// + +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" +#endif + +#include +#include + +#include "../utils/utils.h" +#include "../webp/decode.h" +#include "../webp/demux.h" + +#define NUM_CHANNELS 4 + +typedef void (*BlendRowFunc)(uint32_t* const, const uint32_t* const, int); +static void BlendPixelRowNonPremult(uint32_t* const src, + const uint32_t* const dst, int num_pixels); +static void BlendPixelRowPremult(uint32_t* const src, const uint32_t* const dst, + int num_pixels); + +struct WebPAnimDecoder { + WebPDemuxer* demux_; // Demuxer created from given WebP bitstream. + WebPDecoderConfig config_; // Decoder config. + // Note: we use a pointer to a function blending multiple pixels at a time to + // allow possible inlining of per-pixel blending function. + BlendRowFunc blend_func_; // Pointer to the chose blend row function. + WebPAnimInfo info_; // Global info about the animation. + uint8_t* curr_frame_; // Current canvas (not disposed). + uint8_t* prev_frame_disposed_; // Previous canvas (properly disposed). + int prev_frame_timestamp_; // Previous frame timestamp (milliseconds). + WebPIterator prev_iter_; // Iterator object for previous frame. + int prev_frame_was_keyframe_; // True if previous frame was a keyframe. + int next_frame_; // Index of the next frame to be decoded + // (starting from 1). +}; + +static void DefaultDecoderOptions(WebPAnimDecoderOptions* const dec_options) { + dec_options->color_mode = MODE_RGBA; + dec_options->use_threads = 0; +} + +int WebPAnimDecoderOptionsInitInternal(WebPAnimDecoderOptions* dec_options, + int abi_version) { + if (dec_options == NULL || + WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_DEMUX_ABI_VERSION)) { + return 0; + } + DefaultDecoderOptions(dec_options); + return 1; +} + +static int ApplyDecoderOptions(const WebPAnimDecoderOptions* const dec_options, + WebPAnimDecoder* const dec) { + WEBP_CSP_MODE mode; + WebPDecoderConfig* config = &dec->config_; + assert(dec_options != NULL); + + mode = dec_options->color_mode; + if (mode != MODE_RGBA && mode != MODE_BGRA && + mode != MODE_rgbA && mode != MODE_bgrA) { + return 0; + } + dec->blend_func_ = (mode == MODE_RGBA || mode == MODE_BGRA) + ? &BlendPixelRowNonPremult + : &BlendPixelRowPremult; + WebPInitDecoderConfig(config); + config->output.colorspace = mode; + config->output.is_external_memory = 1; + config->options.use_threads = dec_options->use_threads; + // Note: config->output.u.RGBA is set at the time of decoding each frame. + return 1; +} + +WebPAnimDecoder* WebPAnimDecoderNewInternal( + const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options, + int abi_version) { + WebPAnimDecoderOptions options; + WebPAnimDecoder* dec = NULL; + if (webp_data == NULL || + WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_DEMUX_ABI_VERSION)) { + return NULL; + } + + // Note: calloc() so that the pointer members are initialized to NULL. + dec = (WebPAnimDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + if (dec == NULL) goto Error; + + if (dec_options != NULL) { + options = *dec_options; + } else { + DefaultDecoderOptions(&options); + } + if (!ApplyDecoderOptions(&options, dec)) goto Error; + + dec->demux_ = WebPDemux(webp_data); + if (dec->demux_ == NULL) goto Error; + + dec->info_.canvas_width = WebPDemuxGetI(dec->demux_, WEBP_FF_CANVAS_WIDTH); + dec->info_.canvas_height = WebPDemuxGetI(dec->demux_, WEBP_FF_CANVAS_HEIGHT); + dec->info_.loop_count = WebPDemuxGetI(dec->demux_, WEBP_FF_LOOP_COUNT); + dec->info_.bgcolor = WebPDemuxGetI(dec->demux_, WEBP_FF_BACKGROUND_COLOR); + dec->info_.frame_count = WebPDemuxGetI(dec->demux_, WEBP_FF_FRAME_COUNT); + + // Note: calloc() because we fill frame with zeroes as well. + dec->curr_frame_ = (uint8_t*)WebPSafeCalloc( + dec->info_.canvas_width * NUM_CHANNELS, dec->info_.canvas_height); + if (dec->curr_frame_ == NULL) goto Error; + dec->prev_frame_disposed_ = (uint8_t*)WebPSafeCalloc( + dec->info_.canvas_width * NUM_CHANNELS, dec->info_.canvas_height); + if (dec->prev_frame_disposed_ == NULL) goto Error; + + WebPAnimDecoderReset(dec); + return dec; + + Error: + WebPAnimDecoderDelete(dec); + return NULL; +} + +int WebPAnimDecoderGetInfo(const WebPAnimDecoder* dec, WebPAnimInfo* info) { + if (dec == NULL || info == NULL) return 0; + *info = dec->info_; + return 1; +} + +// Returns true if the frame covers the full canvas. +static int IsFullFrame(int width, int height, int canvas_width, + int canvas_height) { + return (width == canvas_width && height == canvas_height); +} + +// Clear the canvas to transparent. +static int ZeroFillCanvas(uint8_t* buf, uint32_t canvas_width, + uint32_t canvas_height) { + const uint64_t size = + (uint64_t)canvas_width * canvas_height * NUM_CHANNELS * sizeof(*buf); + if (size != (size_t)size) return 0; + memset(buf, 0, (size_t)size); + return 1; +} + +// Clear given frame rectangle to transparent. +static void ZeroFillFrameRect(uint8_t* buf, int buf_stride, int x_offset, + int y_offset, int width, int height) { + int j; + assert(width * NUM_CHANNELS <= buf_stride); + buf += y_offset * buf_stride + x_offset * NUM_CHANNELS; + for (j = 0; j < height; ++j) { + memset(buf, 0, width * NUM_CHANNELS); + buf += buf_stride; + } +} + +// Copy width * height pixels from 'src' to 'dst'. +static int CopyCanvas(const uint8_t* src, uint8_t* dst, + uint32_t width, uint32_t height) { + const uint64_t size = (uint64_t)width * height * NUM_CHANNELS; + if (size != (size_t)size) return 0; + assert(src != NULL && dst != NULL); + memcpy(dst, src, (size_t)size); + return 1; +} + +// Returns true if the current frame is a key-frame. +static int IsKeyFrame(const WebPIterator* const curr, + const WebPIterator* const prev, + int prev_frame_was_key_frame, + int canvas_width, int canvas_height) { + if (curr->frame_num == 1) { + return 1; + } else if ((!curr->has_alpha || curr->blend_method == WEBP_MUX_NO_BLEND) && + IsFullFrame(curr->width, curr->height, + canvas_width, canvas_height)) { + return 1; + } else { + return (prev->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) && + (IsFullFrame(prev->width, prev->height, canvas_width, + canvas_height) || + prev_frame_was_key_frame); + } +} + + +// Blend a single channel of 'src' over 'dst', given their alpha channel values. +// 'src' and 'dst' are assumed to be NOT pre-multiplied by alpha. +static uint8_t BlendChannelNonPremult(uint32_t src, uint8_t src_a, + uint32_t dst, uint8_t dst_a, + uint32_t scale, int shift) { + const uint8_t src_channel = (src >> shift) & 0xff; + const uint8_t dst_channel = (dst >> shift) & 0xff; + const uint32_t blend_unscaled = src_channel * src_a + dst_channel * dst_a; + assert(blend_unscaled < (1ULL << 32) / scale); + return (blend_unscaled * scale) >> 24; +} + +// Blend 'src' over 'dst' assuming they are NOT pre-multiplied by alpha. +static uint32_t BlendPixelNonPremult(uint32_t src, uint32_t dst) { + const uint8_t src_a = (src >> 24) & 0xff; + + if (src_a == 0) { + return dst; + } else { + const uint8_t dst_a = (dst >> 24) & 0xff; + // This is the approximate integer arithmetic for the actual formula: + // dst_factor_a = (dst_a * (255 - src_a)) / 255. + const uint8_t dst_factor_a = (dst_a * (256 - src_a)) >> 8; + const uint8_t blend_a = src_a + dst_factor_a; + const uint32_t scale = (1UL << 24) / blend_a; + + const uint8_t blend_r = + BlendChannelNonPremult(src, src_a, dst, dst_factor_a, scale, 0); + const uint8_t blend_g = + BlendChannelNonPremult(src, src_a, dst, dst_factor_a, scale, 8); + const uint8_t blend_b = + BlendChannelNonPremult(src, src_a, dst, dst_factor_a, scale, 16); + assert(src_a + dst_factor_a < 256); + + return (blend_r << 0) | + (blend_g << 8) | + (blend_b << 16) | + ((uint32_t)blend_a << 24); + } +} + +// Blend 'num_pixels' in 'src' over 'dst' assuming they are NOT pre-multiplied +// by alpha. +static void BlendPixelRowNonPremult(uint32_t* const src, + const uint32_t* const dst, int num_pixels) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint8_t src_alpha = (src[i] >> 24) & 0xff; + if (src_alpha != 0xff) { + src[i] = BlendPixelNonPremult(src[i], dst[i]); + } + } +} + +// Individually multiply each channel in 'pix' by 'scale'. +static WEBP_INLINE uint32_t ChannelwiseMultiply(uint32_t pix, uint32_t scale) { + uint32_t mask = 0x00FF00FF; + uint32_t rb = ((pix & mask) * scale) >> 8; + uint32_t ag = ((pix >> 8) & mask) * scale; + return (rb & mask) | (ag & ~mask); +} + +// Blend 'src' over 'dst' assuming they are pre-multiplied by alpha. +static uint32_t BlendPixelPremult(uint32_t src, uint32_t dst) { + const uint8_t src_a = (src >> 24) & 0xff; + return src + ChannelwiseMultiply(dst, 256 - src_a); +} + +// Blend 'num_pixels' in 'src' over 'dst' assuming they are pre-multiplied by +// alpha. +static void BlendPixelRowPremult(uint32_t* const src, const uint32_t* const dst, + int num_pixels) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint8_t src_alpha = (src[i] >> 24) & 0xff; + if (src_alpha != 0xff) { + src[i] = BlendPixelPremult(src[i], dst[i]); + } + } +} + +// Returns two ranges ( pairs) at row 'canvas_y', that belong to +// 'src' but not 'dst'. A point range is empty if the corresponding width is 0. +static void FindBlendRangeAtRow(const WebPIterator* const src, + const WebPIterator* const dst, int canvas_y, + int* const left1, int* const width1, + int* const left2, int* const width2) { + const int src_max_x = src->x_offset + src->width; + const int dst_max_x = dst->x_offset + dst->width; + const int dst_max_y = dst->y_offset + dst->height; + assert(canvas_y >= src->y_offset && canvas_y < (src->y_offset + src->height)); + *left1 = -1; + *width1 = 0; + *left2 = -1; + *width2 = 0; + + if (canvas_y < dst->y_offset || canvas_y >= dst_max_y || + src->x_offset >= dst_max_x || src_max_x <= dst->x_offset) { + *left1 = src->x_offset; + *width1 = src->width; + return; + } + + if (src->x_offset < dst->x_offset) { + *left1 = src->x_offset; + *width1 = dst->x_offset - src->x_offset; + } + + if (src_max_x > dst_max_x) { + *left2 = dst_max_x; + *width2 = src_max_x - dst_max_x; + } +} + +int WebPAnimDecoderGetNext(WebPAnimDecoder* dec, + uint8_t** buf_ptr, int* timestamp_ptr) { + WebPIterator iter; + uint32_t width; + uint32_t height; + int is_key_frame; + int timestamp; + BlendRowFunc blend_row; + + if (dec == NULL || buf_ptr == NULL || timestamp_ptr == NULL) return 0; + if (!WebPAnimDecoderHasMoreFrames(dec)) return 0; + + width = dec->info_.canvas_width; + height = dec->info_.canvas_height; + blend_row = dec->blend_func_; + + // Get compressed frame. + if (!WebPDemuxGetFrame(dec->demux_, dec->next_frame_, &iter)) { + return 0; + } + timestamp = dec->prev_frame_timestamp_ + iter.duration; + + // Initialize. + is_key_frame = IsKeyFrame(&iter, &dec->prev_iter_, + dec->prev_frame_was_keyframe_, width, height); + if (is_key_frame) { + if (!ZeroFillCanvas(dec->curr_frame_, width, height)) { + goto Error; + } + } else { + if (!CopyCanvas(dec->prev_frame_disposed_, dec->curr_frame_, + width, height)) { + goto Error; + } + } + + // Decode. + { + const uint8_t* in = iter.fragment.bytes; + const size_t in_size = iter.fragment.size; + const size_t out_offset = + (iter.y_offset * width + iter.x_offset) * NUM_CHANNELS; + WebPDecoderConfig* const config = &dec->config_; + WebPRGBABuffer* const buf = &config->output.u.RGBA; + buf->stride = NUM_CHANNELS * width; + buf->size = buf->stride * iter.height; + buf->rgba = dec->curr_frame_ + out_offset; + + if (WebPDecode(in, in_size, config) != VP8_STATUS_OK) { + goto Error; + } + } + + // During the decoding of current frame, we may have set some pixels to be + // transparent (i.e. alpha < 255). However, the value of each of these + // pixels should have been determined by blending it against the value of + // that pixel in the previous frame if blending method of is WEBP_MUX_BLEND. + if (iter.frame_num > 1 && iter.blend_method == WEBP_MUX_BLEND && + !is_key_frame) { + if (dec->prev_iter_.dispose_method == WEBP_MUX_DISPOSE_NONE) { + int y; + // Blend transparent pixels with pixels in previous canvas. + for (y = 0; y < iter.height; ++y) { + const size_t offset = + (iter.y_offset + y) * width + iter.x_offset; + blend_row((uint32_t*)dec->curr_frame_ + offset, + (uint32_t*)dec->prev_frame_disposed_ + offset, iter.width); + } + } else { + int y; + assert(dec->prev_iter_.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND); + // We need to blend a transparent pixel with its value just after + // initialization. That is, blend it with: + // * Fully transparent pixel if it belongs to prevRect <-- No-op. + // * The pixel in the previous canvas otherwise <-- Need alpha-blending. + for (y = 0; y < iter.height; ++y) { + const int canvas_y = iter.y_offset + y; + int left1, width1, left2, width2; + FindBlendRangeAtRow(&iter, &dec->prev_iter_, canvas_y, &left1, &width1, + &left2, &width2); + if (width1 > 0) { + const size_t offset1 = canvas_y * width + left1; + blend_row((uint32_t*)dec->curr_frame_ + offset1, + (uint32_t*)dec->prev_frame_disposed_ + offset1, width1); + } + if (width2 > 0) { + const size_t offset2 = canvas_y * width + left2; + blend_row((uint32_t*)dec->curr_frame_ + offset2, + (uint32_t*)dec->prev_frame_disposed_ + offset2, width2); + } + } + } + } + + // Update info of the previous frame and dispose it for the next iteration. + dec->prev_frame_timestamp_ = timestamp; + WebPDemuxReleaseIterator(&dec->prev_iter_); + dec->prev_iter_ = iter; + dec->prev_frame_was_keyframe_ = is_key_frame; + CopyCanvas(dec->curr_frame_, dec->prev_frame_disposed_, width, height); + if (dec->prev_iter_.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { + ZeroFillFrameRect(dec->prev_frame_disposed_, width * NUM_CHANNELS, + dec->prev_iter_.x_offset, dec->prev_iter_.y_offset, + dec->prev_iter_.width, dec->prev_iter_.height); + } + ++dec->next_frame_; + + // All OK, fill in the values. + *buf_ptr = dec->curr_frame_; + *timestamp_ptr = timestamp; + return 1; + + Error: + WebPDemuxReleaseIterator(&iter); + return 0; +} + +int WebPAnimDecoderHasMoreFrames(const WebPAnimDecoder* dec) { + if (dec == NULL) return 0; + return (dec->next_frame_ <= (int)dec->info_.frame_count); +} + +void WebPAnimDecoderReset(WebPAnimDecoder* dec) { + if (dec != NULL) { + dec->prev_frame_timestamp_ = 0; + WebPDemuxReleaseIterator(&dec->prev_iter_); + memset(&dec->prev_iter_, 0, sizeof(dec->prev_iter_)); + dec->prev_frame_was_keyframe_ = 0; + dec->next_frame_ = 1; + } +} + +const WebPDemuxer* WebPAnimDecoderGetDemuxer(const WebPAnimDecoder* dec) { + if (dec == NULL) return NULL; + return dec->demux_; +} + +void WebPAnimDecoderDelete(WebPAnimDecoder* dec) { + if (dec != NULL) { + WebPDemuxReleaseIterator(&dec->prev_iter_); + WebPDemuxDelete(dec->demux_); + WebPSafeFree(dec->curr_frame_); + WebPSafeFree(dec->prev_frame_disposed_); + WebPSafeFree(dec); + } +} diff --git a/3rdparty/libwebp/demux/demux.c b/3rdparty/libwebp/demux/demux.c index bd17ff7f69..100eab8c01 100644 --- a/3rdparty/libwebp/demux/demux.c +++ b/3rdparty/libwebp/demux/demux.c @@ -11,7 +11,7 @@ // #ifdef HAVE_CONFIG_H -#include "config.h" +#include "../webp/config.h" #endif #include @@ -23,13 +23,9 @@ #include "../webp/demux.h" #include "../webp/format_constants.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - #define DMUX_MAJ_VERSION 0 -#define DMUX_MIN_VERSION 1 -#define DMUX_REV_VERSION 1 +#define DMUX_MIN_VERSION 3 +#define DMUX_REV_VERSION 2 typedef struct { size_t start_; // start location of the data @@ -47,10 +43,11 @@ typedef struct { typedef struct Frame { int x_offset_, y_offset_; int width_, height_; + int has_alpha_; int duration_; WebPMuxAnimDispose dispose_method_; - int is_fragment_; // this is a frame fragment (and not a full frame). - int frame_num_; // the referent frame number for use in assembling fragments. + WebPMuxAnimBlend blend_method_; + int frame_num_; int complete_; // img_components_ contains a full image. ChunkData img_components_[2]; // 0=VP8{,L} 1=ALPH struct Frame* next_; @@ -73,6 +70,7 @@ struct WebPDemuxer { Frame* frames_; Frame** frames_tail_; Chunk* chunks_; // non-image chunks + Chunk** chunks_tail_; }; typedef enum { @@ -177,10 +175,9 @@ static WEBP_INLINE uint32_t ReadLE32(MemBuffer* const mem) { // Secondary chunk parsing static void AddChunk(WebPDemuxer* const dmux, Chunk* const chunk) { - Chunk** c = &dmux->chunks_; - while (*c != NULL) c = &(*c)->next_; - *c = chunk; + *dmux->chunks_tail_ = chunk; chunk->next_ = NULL; + dmux->chunks_tail_ = &chunk->next_; } // Add a frame to the end of the list, ensuring the last frame is complete. @@ -195,19 +192,27 @@ static int AddFrame(WebPDemuxer* const dmux, Frame* const frame) { return 1; } +static void SetFrameInfo(size_t start_offset, size_t size, + int frame_num, int complete, + const WebPBitstreamFeatures* const features, + Frame* const frame) { + frame->img_components_[0].offset_ = start_offset; + frame->img_components_[0].size_ = size; + frame->width_ = features->width; + frame->height_ = features->height; + frame->has_alpha_ |= features->has_alpha; + frame->frame_num_ = frame_num; + frame->complete_ = complete; +} + // Store image bearing chunks to 'frame'. -// If 'has_vp8l_alpha' is not NULL, it will be set to true if the frame is a -// lossless image with alpha. static ParseStatus StoreFrame(int frame_num, uint32_t min_size, - MemBuffer* const mem, Frame* const frame, - int* const has_vp8l_alpha) { + MemBuffer* const mem, Frame* const frame) { int alpha_chunks = 0; int image_chunks = 0; int done = (MemDataSize(mem) < min_size); ParseStatus status = PARSE_OK; - if (has_vp8l_alpha != NULL) *has_vp8l_alpha = 0; // Default. - if (done) return PARSE_NEED_MORE_DATA; do { @@ -229,6 +234,7 @@ static ParseStatus StoreFrame(int frame_num, uint32_t min_size, ++alpha_chunks; frame->img_components_[1].offset_ = chunk_start_offset; frame->img_components_[1].size_ = chunk_size; + frame->has_alpha_ = 1; frame->frame_num_ = frame_num; Skip(mem, payload_available); } else { @@ -254,13 +260,8 @@ static ParseStatus StoreFrame(int frame_num, uint32_t min_size, return PARSE_ERROR; } ++image_chunks; - frame->img_components_[0].offset_ = chunk_start_offset; - frame->img_components_[0].size_ = chunk_size; - frame->width_ = features.width; - frame->height_ = features.height; - if (has_vp8l_alpha != NULL) *has_vp8l_alpha = features.has_alpha; - frame->frame_num_ = frame_num; - frame->complete_ = (status == PARSE_OK); + SetFrameInfo(chunk_start_offset, chunk_size, frame_num, + status == PARSE_OK, &features, frame); Skip(mem, payload_available); } else { goto Done; @@ -295,7 +296,7 @@ static ParseStatus NewFrame(const MemBuffer* const mem, if (actual_size < min_size) return PARSE_ERROR; if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; - *frame = (Frame*)calloc(1, sizeof(**frame)); + *frame = (Frame*)WebPSafeCalloc(1ULL, sizeof(**frame)); return (*frame == NULL) ? PARSE_ERROR : PARSE_OK; } @@ -303,9 +304,10 @@ static ParseStatus NewFrame(const MemBuffer* const mem, // 'frame_chunk_size' is the previously validated, padded chunk size. static ParseStatus ParseAnimationFrame( WebPDemuxer* const dmux, uint32_t frame_chunk_size) { - const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG); + const int is_animation = !!(dmux->feature_flags_ & ANIMATION_FLAG); const uint32_t anmf_payload_size = frame_chunk_size - ANMF_CHUNK_SIZE; int added_frame = 0; + int bits; MemBuffer* const mem = &dmux->mem_; Frame* frame; ParseStatus status = @@ -317,17 +319,19 @@ static ParseStatus ParseAnimationFrame( frame->width_ = 1 + ReadLE24s(mem); frame->height_ = 1 + ReadLE24s(mem); frame->duration_ = ReadLE24s(mem); - frame->dispose_method_ = (WebPMuxAnimDispose)(ReadByte(mem) & 1); + bits = ReadByte(mem); + frame->dispose_method_ = + (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE; + frame->blend_method_ = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND; if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) { - free(frame); + WebPSafeFree(frame); return PARSE_ERROR; } // Store a frame only if the animation flag is set there is some data for // this frame is available. - status = StoreFrame(dmux->num_frames_ + 1, anmf_payload_size, mem, frame, - NULL); - if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) { + status = StoreFrame(dmux->num_frames_ + 1, anmf_payload_size, mem, frame); + if (status != PARSE_ERROR && is_animation && frame->frame_num_ > 0) { added_frame = AddFrame(dmux, frame); if (added_frame) { ++dmux->num_frames_; @@ -336,53 +340,17 @@ static ParseStatus ParseAnimationFrame( } } - if (!added_frame) free(frame); + if (!added_frame) WebPSafeFree(frame); return status; } -#ifdef WEBP_EXPERIMENTAL_FEATURES -// Parse a 'FRGM' chunk and any image bearing chunks that immediately follow. -// 'fragment_chunk_size' is the previously validated, padded chunk size. -static ParseStatus ParseFragment(WebPDemuxer* const dmux, - uint32_t fragment_chunk_size) { - const int frame_num = 1; // All fragments belong to the 1st (and only) frame. - const int has_fragments = !!(dmux->feature_flags_ & FRAGMENTS_FLAG); - const uint32_t frgm_payload_size = fragment_chunk_size - FRGM_CHUNK_SIZE; - int added_fragment = 0; - MemBuffer* const mem = &dmux->mem_; - Frame* frame; - ParseStatus status = - NewFrame(mem, FRGM_CHUNK_SIZE, fragment_chunk_size, &frame); - if (status != PARSE_OK) return status; - - frame->is_fragment_ = 1; - frame->x_offset_ = 2 * ReadLE24s(mem); - frame->y_offset_ = 2 * ReadLE24s(mem); - - // Store a fragment only if the fragments flag is set there is some data for - // this fragment is available. - status = StoreFrame(frame_num, frgm_payload_size, mem, frame, NULL); - if (status != PARSE_ERROR && has_fragments && frame->frame_num_ > 0) { - added_fragment = AddFrame(dmux, frame); - if (!added_fragment) { - status = PARSE_ERROR; - } else { - dmux->num_frames_ = 1; - } - } - - if (!added_fragment) free(frame); - return status; -} -#endif // WEBP_EXPERIMENTAL_FEATURES - // General chunk storage, starting with the header at 'start_offset', allowing // the user to request the payload via a fourcc string. 'size' includes the // header and the unpadded payload size. // Returns true on success, false otherwise. static int StoreChunk(WebPDemuxer* const dmux, size_t start_offset, uint32_t size) { - Chunk* const chunk = (Chunk*)calloc(1, sizeof(*chunk)); + Chunk* const chunk = (Chunk*)WebPSafeCalloc(1ULL, sizeof(*chunk)); if (chunk == NULL) return 0; chunk->data_.offset_ = start_offset; @@ -394,20 +362,20 @@ static int StoreChunk(WebPDemuxer* const dmux, // ----------------------------------------------------------------------------- // Primary chunk parsing -static int ReadHeader(MemBuffer* const mem) { +static ParseStatus ReadHeader(MemBuffer* const mem) { const size_t min_size = RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE; uint32_t riff_size; // Basic file level validation. - if (MemDataSize(mem) < min_size) return 0; + if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; if (memcmp(GetBuffer(mem), "RIFF", CHUNK_SIZE_BYTES) || memcmp(GetBuffer(mem) + CHUNK_HEADER_SIZE, "WEBP", CHUNK_SIZE_BYTES)) { - return 0; + return PARSE_ERROR; } riff_size = GetLE32(GetBuffer(mem) + TAG_SIZE); - if (riff_size < CHUNK_HEADER_SIZE) return 0; - if (riff_size > MAX_CHUNK_PAYLOAD) return 0; + if (riff_size < CHUNK_HEADER_SIZE) return PARSE_ERROR; + if (riff_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; // There's no point in reading past the end of the RIFF chunk mem->riff_end_ = riff_size + CHUNK_HEADER_SIZE; @@ -416,7 +384,7 @@ static int ReadHeader(MemBuffer* const mem) { } Skip(mem, RIFF_HEADER_SIZE); - return 1; + return PARSE_OK; } static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) { @@ -424,25 +392,25 @@ static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) { MemBuffer* const mem = &dmux->mem_; Frame* frame; ParseStatus status; - int has_vp8l_alpha = 0; // Frame contains a lossless image with alpha. + int image_added = 0; if (dmux->frames_ != NULL) return PARSE_ERROR; if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR; if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; - frame = (Frame*)calloc(1, sizeof(*frame)); + frame = (Frame*)WebPSafeCalloc(1ULL, sizeof(*frame)); if (frame == NULL) return PARSE_ERROR; // For the single image case we allow parsing of a partial frame, but we need // at least CHUNK_HEADER_SIZE for parsing. - status = StoreFrame(1, CHUNK_HEADER_SIZE, &dmux->mem_, frame, - &has_vp8l_alpha); + status = StoreFrame(1, CHUNK_HEADER_SIZE, &dmux->mem_, frame); if (status != PARSE_ERROR) { const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG); // Clear any alpha when the alpha flag is missing. if (!has_alpha && frame->img_components_[1].size_ > 0) { frame->img_components_[1].offset_ = 0; frame->img_components_[1].size_ = 0; + frame->has_alpha_ = 0; } // Use the frame width/height as the canvas values for non-vp8x files. @@ -451,47 +419,26 @@ static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) { dmux->state_ = WEBP_DEMUX_PARSED_HEADER; dmux->canvas_width_ = frame->width_; dmux->canvas_height_ = frame->height_; - dmux->feature_flags_ |= has_vp8l_alpha ? ALPHA_FLAG : 0; + dmux->feature_flags_ |= frame->has_alpha_ ? ALPHA_FLAG : 0; + } + if (!AddFrame(dmux, frame)) { + status = PARSE_ERROR; // last frame was left incomplete + } else { + image_added = 1; + dmux->num_frames_ = 1; } - AddFrame(dmux, frame); - dmux->num_frames_ = 1; - } else { - free(frame); } + if (!image_added) WebPSafeFree(frame); return status; } -static ParseStatus ParseVP8X(WebPDemuxer* const dmux) { +static ParseStatus ParseVP8XChunks(WebPDemuxer* const dmux) { + const int is_animation = !!(dmux->feature_flags_ & ANIMATION_FLAG); MemBuffer* const mem = &dmux->mem_; int anim_chunks = 0; - uint32_t vp8x_size; ParseStatus status = PARSE_OK; - if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; - - dmux->is_ext_format_ = 1; - Skip(mem, TAG_SIZE); // VP8X - vp8x_size = ReadLE32(mem); - if (vp8x_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; - if (vp8x_size < VP8X_CHUNK_SIZE) return PARSE_ERROR; - vp8x_size += vp8x_size & 1; - if (SizeIsInvalid(mem, vp8x_size)) return PARSE_ERROR; - if (MemDataSize(mem) < vp8x_size) return PARSE_NEED_MORE_DATA; - - dmux->feature_flags_ = ReadByte(mem); - Skip(mem, 3); // Reserved. - dmux->canvas_width_ = 1 + ReadLE24s(mem); - dmux->canvas_height_ = 1 + ReadLE24s(mem); - if (dmux->canvas_width_ * (uint64_t)dmux->canvas_height_ >= MAX_IMAGE_AREA) { - return PARSE_ERROR; // image final dimension is too large - } - Skip(mem, vp8x_size - VP8X_CHUNK_SIZE); // skip any trailing data. - dmux->state_ = WEBP_DEMUX_PARSED_HEADER; - - if (SizeIsInvalid(mem, CHUNK_HEADER_SIZE)) return PARSE_ERROR; - if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; - do { int store_chunk = 1; const size_t chunk_start_offset = mem->start_; @@ -510,7 +457,7 @@ static ParseStatus ParseVP8X(WebPDemuxer* const dmux) { case MKFOURCC('V', 'P', '8', ' '): case MKFOURCC('V', 'P', '8', 'L'): { // check that this isn't an animation (all frames should be in an ANMF). - if (anim_chunks > 0) return PARSE_ERROR; + if (anim_chunks > 0 || is_animation) return PARSE_ERROR; Rewind(mem, CHUNK_HEADER_SIZE); status = ParseSingleImage(dmux); @@ -537,24 +484,18 @@ static ParseStatus ParseVP8X(WebPDemuxer* const dmux) { status = ParseAnimationFrame(dmux, chunk_size_padded); break; } -#ifdef WEBP_EXPERIMENTAL_FEATURES - case MKFOURCC('F', 'R', 'G', 'M'): { - status = ParseFragment(dmux, chunk_size_padded); - break; - } -#endif case MKFOURCC('I', 'C', 'C', 'P'): { store_chunk = !!(dmux->feature_flags_ & ICCP_FLAG); goto Skip; } - case MKFOURCC('X', 'M', 'P', ' '): { - store_chunk = !!(dmux->feature_flags_ & XMP_FLAG); - goto Skip; - } case MKFOURCC('E', 'X', 'I', 'F'): { store_chunk = !!(dmux->feature_flags_ & EXIF_FLAG); goto Skip; } + case MKFOURCC('X', 'M', 'P', ' '): { + store_chunk = !!(dmux->feature_flags_ & XMP_FLAG); + goto Skip; + } Skip: default: { if (chunk_size_padded <= MemDataSize(mem)) { @@ -583,6 +524,37 @@ static ParseStatus ParseVP8X(WebPDemuxer* const dmux) { return status; } +static ParseStatus ParseVP8X(WebPDemuxer* const dmux) { + MemBuffer* const mem = &dmux->mem_; + uint32_t vp8x_size; + + if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; + + dmux->is_ext_format_ = 1; + Skip(mem, TAG_SIZE); // VP8X + vp8x_size = ReadLE32(mem); + if (vp8x_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; + if (vp8x_size < VP8X_CHUNK_SIZE) return PARSE_ERROR; + vp8x_size += vp8x_size & 1; + if (SizeIsInvalid(mem, vp8x_size)) return PARSE_ERROR; + if (MemDataSize(mem) < vp8x_size) return PARSE_NEED_MORE_DATA; + + dmux->feature_flags_ = ReadByte(mem); + Skip(mem, 3); // Reserved. + dmux->canvas_width_ = 1 + ReadLE24s(mem); + dmux->canvas_height_ = 1 + ReadLE24s(mem); + if (dmux->canvas_width_ * (uint64_t)dmux->canvas_height_ >= MAX_IMAGE_AREA) { + return PARSE_ERROR; // image final dimension is too large + } + Skip(mem, vp8x_size - VP8X_CHUNK_SIZE); // skip any trailing data. + dmux->state_ = WEBP_DEMUX_PARSED_HEADER; + + if (SizeIsInvalid(mem, CHUNK_HEADER_SIZE)) return PARSE_ERROR; + if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; + + return ParseVP8XChunks(dmux); +} + // ----------------------------------------------------------------------------- // Format validation @@ -597,30 +569,47 @@ static int IsValidSimpleFormat(const WebPDemuxer* const dmux) { return 1; } +// If 'exact' is true, check that the image resolution matches the canvas. +// If 'exact' is false, check that the x/y offsets do not exceed the canvas. +static int CheckFrameBounds(const Frame* const frame, int exact, + int canvas_width, int canvas_height) { + if (exact) { + if (frame->x_offset_ != 0 || frame->y_offset_ != 0) { + return 0; + } + if (frame->width_ != canvas_width || frame->height_ != canvas_height) { + return 0; + } + } else { + if (frame->x_offset_ < 0 || frame->y_offset_ < 0) return 0; + if (frame->width_ + frame->x_offset_ > canvas_width) return 0; + if (frame->height_ + frame->y_offset_ > canvas_height) return 0; + } + return 1; +} + static int IsValidExtendedFormat(const WebPDemuxer* const dmux) { - const int has_fragments = !!(dmux->feature_flags_ & FRAGMENTS_FLAG); - const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG); - const Frame* f; + const int is_animation = !!(dmux->feature_flags_ & ANIMATION_FLAG); + const Frame* f = dmux->frames_; if (dmux->state_ == WEBP_DEMUX_PARSING_HEADER) return 1; if (dmux->canvas_width_ <= 0 || dmux->canvas_height_ <= 0) return 0; if (dmux->loop_count_ < 0) return 0; if (dmux->state_ == WEBP_DEMUX_DONE && dmux->frames_ == NULL) return 0; + if (dmux->feature_flags_ & ~ALL_VALID_FLAGS) return 0; // invalid bitstream - for (f = dmux->frames_; f != NULL; f = f->next_) { + while (f != NULL) { const int cur_frame_set = f->frame_num_; - int frame_count = 0, fragment_count = 0; + int frame_count = 0; - // Check frame properties and if the image is composed of fragments that - // each fragment came from a fragment. + // Check frame properties. for (; f != NULL && f->frame_num_ == cur_frame_set; f = f->next_) { const ChunkData* const image = f->img_components_; const ChunkData* const alpha = f->img_components_ + 1; - if (!has_fragments && f->is_fragment_) return 0; - if (!has_frames && f->frame_num_ > 1) return 0; - if (f->x_offset_ < 0 || f->y_offset_ < 0) return 0; + if (!is_animation && f->frame_num_ > 1) return 0; + if (f->complete_) { if (alpha->size_ == 0 && image->size_ == 0) return 0; // Ensure alpha precedes image bitstream. @@ -642,12 +631,14 @@ static int IsValidExtendedFormat(const WebPDemuxer* const dmux) { if (f->next_ != NULL) return 0; } - fragment_count += f->is_fragment_; + if (f->width_ > 0 && f->height_ > 0 && + !CheckFrameBounds(f, !is_animation, + dmux->canvas_width_, dmux->canvas_height_)) { + return 0; + } + ++frame_count; } - if (!has_fragments && frame_count > 1) return 0; - if (fragment_count > 0 && frame_count != fragment_count) return 0; - if (f == NULL) break; } return 1; } @@ -662,9 +653,45 @@ static void InitDemux(WebPDemuxer* const dmux, const MemBuffer* const mem) { dmux->canvas_width_ = -1; dmux->canvas_height_ = -1; dmux->frames_tail_ = &dmux->frames_; + dmux->chunks_tail_ = &dmux->chunks_; dmux->mem_ = *mem; } +static ParseStatus CreateRawImageDemuxer(MemBuffer* const mem, + WebPDemuxer** demuxer) { + WebPBitstreamFeatures features; + const VP8StatusCode status = + WebPGetFeatures(mem->buf_, mem->buf_size_, &features); + *demuxer = NULL; + if (status != VP8_STATUS_OK) { + return (status == VP8_STATUS_NOT_ENOUGH_DATA) ? PARSE_NEED_MORE_DATA + : PARSE_ERROR; + } + + { + WebPDemuxer* const dmux = (WebPDemuxer*)WebPSafeCalloc(1ULL, sizeof(*dmux)); + Frame* const frame = (Frame*)WebPSafeCalloc(1ULL, sizeof(*frame)); + if (dmux == NULL || frame == NULL) goto Error; + InitDemux(dmux, mem); + SetFrameInfo(0, mem->buf_size_, 1 /*frame_num*/, 1 /*complete*/, &features, + frame); + if (!AddFrame(dmux, frame)) goto Error; + dmux->state_ = WEBP_DEMUX_DONE; + dmux->canvas_width_ = frame->width_; + dmux->canvas_height_ = frame->height_; + dmux->feature_flags_ |= frame->has_alpha_ ? ALPHA_FLAG : 0; + dmux->num_frames_ = 1; + assert(IsValidSimpleFormat(dmux)); + *demuxer = dmux; + return PARSE_OK; + + Error: + WebPSafeFree(dmux); + WebPSafeFree(frame); + return PARSE_ERROR; + } +} + WebPDemuxer* WebPDemuxInternal(const WebPData* data, int allow_partial, WebPDemuxState* state, int version) { const ChunkParser* parser; @@ -673,29 +700,49 @@ WebPDemuxer* WebPDemuxInternal(const WebPData* data, int allow_partial, MemBuffer mem; WebPDemuxer* dmux; + if (state != NULL) *state = WEBP_DEMUX_PARSE_ERROR; + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DEMUX_ABI_VERSION)) return NULL; if (data == NULL || data->bytes == NULL || data->size == 0) return NULL; if (!InitMemBuffer(&mem, data->bytes, data->size)) return NULL; - if (!ReadHeader(&mem)) return NULL; + status = ReadHeader(&mem); + if (status != PARSE_OK) { + // If parsing of the webp file header fails attempt to handle a raw + // VP8/VP8L frame. Note 'allow_partial' is ignored in this case. + if (status == PARSE_ERROR) { + status = CreateRawImageDemuxer(&mem, &dmux); + if (status == PARSE_OK) { + if (state != NULL) *state = WEBP_DEMUX_DONE; + return dmux; + } + } + if (state != NULL) { + *state = (status == PARSE_NEED_MORE_DATA) ? WEBP_DEMUX_PARSING_HEADER + : WEBP_DEMUX_PARSE_ERROR; + } + return NULL; + } partial = (mem.buf_size_ < mem.riff_end_); if (!allow_partial && partial) return NULL; - dmux = (WebPDemuxer*)calloc(1, sizeof(*dmux)); + dmux = (WebPDemuxer*)WebPSafeCalloc(1ULL, sizeof(*dmux)); if (dmux == NULL) return NULL; InitDemux(dmux, &mem); + status = PARSE_ERROR; for (parser = kMasterChunks; parser->parse != NULL; ++parser) { if (!memcmp(parser->id, GetBuffer(&dmux->mem_), TAG_SIZE)) { status = parser->parse(dmux); if (status == PARSE_OK) dmux->state_ = WEBP_DEMUX_DONE; if (status == PARSE_NEED_MORE_DATA && !partial) status = PARSE_ERROR; if (status != PARSE_ERROR && !parser->valid(dmux)) status = PARSE_ERROR; + if (status == PARSE_ERROR) dmux->state_ = WEBP_DEMUX_PARSE_ERROR; break; } } - if (state) *state = dmux->state_; + if (state != NULL) *state = dmux->state_; if (status == PARSE_ERROR) { WebPDemuxDelete(dmux); @@ -712,14 +759,14 @@ void WebPDemuxDelete(WebPDemuxer* dmux) { for (f = dmux->frames_; f != NULL;) { Frame* const cur_frame = f; f = f->next_; - free(cur_frame); + WebPSafeFree(cur_frame); } for (c = dmux->chunks_; c != NULL;) { Chunk* const cur_chunk = c; c = c->next_; - free(cur_chunk); + WebPSafeFree(cur_chunk); } - free(dmux); + WebPSafeFree(dmux); } // ----------------------------------------------------------------------------- @@ -741,8 +788,6 @@ uint32_t WebPDemuxGetI(const WebPDemuxer* dmux, WebPFormatFeature feature) { // ----------------------------------------------------------------------------- // Frame iteration -// Find the first 'frame_num' frame. There may be multiple such frames in a -// fragmented frame. static const Frame* GetFrame(const WebPDemuxer* const dmux, int frame_num) { const Frame* f; for (f = dmux->frames_; f != NULL; f = f->next_) { @@ -751,21 +796,6 @@ static const Frame* GetFrame(const WebPDemuxer* const dmux, int frame_num) { return f; } -// Returns fragment 'fragment_num' and the total count. -static const Frame* GetFragment( - const Frame* const frame_set, int fragment_num, int* const count) { - const int this_frame = frame_set->frame_num_; - const Frame* f = frame_set; - const Frame* fragment = NULL; - int total; - - for (total = 0; f != NULL && f->frame_num_ == this_frame; f = f->next_) { - if (++total == fragment_num) fragment = f; - } - *count = total; - return fragment; -} - static const uint8_t* GetFramePayload(const uint8_t* const mem_buf, const Frame* const frame, size_t* const data_size) { @@ -792,32 +822,27 @@ static const uint8_t* GetFramePayload(const uint8_t* const mem_buf, // Create a whole 'frame' from VP8 (+ alpha) or lossless. static int SynthesizeFrame(const WebPDemuxer* const dmux, - const Frame* const first_frame, - int fragment_num, WebPIterator* const iter) { + const Frame* const frame, + WebPIterator* const iter) { const uint8_t* const mem_buf = dmux->mem_.buf_; - int num_fragments; size_t payload_size = 0; - const Frame* const fragment = - GetFragment(first_frame, fragment_num, &num_fragments); - const uint8_t* const payload = - GetFramePayload(mem_buf, fragment, &payload_size); + const uint8_t* const payload = GetFramePayload(mem_buf, frame, &payload_size); if (payload == NULL) return 0; - assert(first_frame != NULL); + assert(frame != NULL); - iter->frame_num = first_frame->frame_num_; + iter->frame_num = frame->frame_num_; iter->num_frames = dmux->num_frames_; - iter->fragment_num = fragment_num; - iter->num_fragments = num_fragments; - iter->x_offset = fragment->x_offset_; - iter->y_offset = fragment->y_offset_; - iter->width = fragment->width_; - iter->height = fragment->height_; - iter->duration = fragment->duration_; - iter->dispose_method = fragment->dispose_method_; - iter->complete = fragment->complete_; + iter->x_offset = frame->x_offset_; + iter->y_offset = frame->y_offset_; + iter->width = frame->width_; + iter->height = frame->height_; + iter->has_alpha = frame->has_alpha_; + iter->duration = frame->duration_; + iter->dispose_method = frame->dispose_method_; + iter->blend_method = frame->blend_method_; + iter->complete = frame->complete_; iter->fragment.bytes = payload; iter->fragment.size = payload_size; - // TODO(jzern): adjust offsets for 'FRGM's embedded in 'ANMF's return 1; } @@ -831,7 +856,7 @@ static int SetFrame(int frame_num, WebPIterator* const iter) { frame = GetFrame(dmux, frame_num); if (frame == NULL) return 0; - return SynthesizeFrame(dmux, frame, 1, iter); + return SynthesizeFrame(dmux, frame, iter); } int WebPDemuxGetFrame(const WebPDemuxer* dmux, int frame, WebPIterator* iter) { @@ -853,17 +878,6 @@ int WebPDemuxPrevFrame(WebPIterator* iter) { return SetFrame(iter->frame_num - 1, iter); } -int WebPDemuxSelectFragment(WebPIterator* iter, int fragment_num) { - if (iter != NULL && iter->private_ != NULL && fragment_num > 0) { - const WebPDemuxer* const dmux = (WebPDemuxer*)iter->private_; - const Frame* const frame = GetFrame(dmux, iter->frame_num); - if (frame == NULL) return 0; - - return SynthesizeFrame(dmux, frame, fragment_num, iter); - } - return 0; -} - void WebPDemuxReleaseIterator(WebPIterator* iter) { (void)iter; } @@ -949,6 +963,3 @@ void WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter) { (void)iter; } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/demux/libwebpdemux.pc.in b/3rdparty/libwebp/demux/libwebpdemux.pc.in new file mode 100644 index 0000000000..6dfbbbdeee --- /dev/null +++ b/3rdparty/libwebp/demux/libwebpdemux.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libwebpdemux +Description: Library for parsing the WebP graphics format container +Version: @PACKAGE_VERSION@ +Requires: libwebp >= 0.2.0 +Cflags: -I${includedir} +Libs: -L${libdir} -lwebpdemux diff --git a/3rdparty/libwebp/demux/libwebpdemux.rc b/3rdparty/libwebp/demux/libwebpdemux.rc new file mode 100644 index 0000000000..b9ab68b3c2 --- /dev/null +++ b/3rdparty/libwebp/demux/libwebpdemux.rc @@ -0,0 +1,41 @@ +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,3,0,2 + PRODUCTVERSION 0,3,0,2 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Google, Inc." + VALUE "FileDescription", "libwebpdemux DLL" + VALUE "FileVersion", "0.3.2" + VALUE "InternalName", "libwebpdemux.dll" + VALUE "LegalCopyright", "Copyright (C) 2017" + VALUE "OriginalFilename", "libwebpdemux.dll" + VALUE "ProductName", "WebP Image Demuxer" + VALUE "ProductVersion", "0.3.2" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources diff --git a/3rdparty/libwebp/dsp/Makefile.am b/3rdparty/libwebp/dsp/Makefile.am new file mode 100644 index 0000000000..33fc1e42ef --- /dev/null +++ b/3rdparty/libwebp/dsp/Makefile.am @@ -0,0 +1,153 @@ +noinst_LTLIBRARIES = libwebpdsp.la libwebpdsp_avx2.la +noinst_LTLIBRARIES += libwebpdsp_sse2.la libwebpdspdecode_sse2.la +noinst_LTLIBRARIES += libwebpdsp_sse41.la libwebpdspdecode_sse41.la +noinst_LTLIBRARIES += libwebpdsp_neon.la libwebpdspdecode_neon.la +noinst_LTLIBRARIES += libwebpdsp_msa.la libwebpdspdecode_msa.la + +if BUILD_LIBWEBPDECODER + noinst_LTLIBRARIES += libwebpdspdecode.la +endif + +common_HEADERS = ../webp/types.h +commondir = $(includedir)/webp + +COMMON_SOURCES = +COMMON_SOURCES += alpha_processing.c +COMMON_SOURCES += alpha_processing_mips_dsp_r2.c +COMMON_SOURCES += common_sse2.h +COMMON_SOURCES += cpu.c +COMMON_SOURCES += dec.c +COMMON_SOURCES += dec_clip_tables.c +COMMON_SOURCES += dec_mips32.c +COMMON_SOURCES += dec_mips_dsp_r2.c +COMMON_SOURCES += dsp.h +COMMON_SOURCES += filters.c +COMMON_SOURCES += filters_mips_dsp_r2.c +COMMON_SOURCES += lossless.c +COMMON_SOURCES += lossless.h +COMMON_SOURCES += lossless_common.h +COMMON_SOURCES += lossless_mips_dsp_r2.c +COMMON_SOURCES += mips_macro.h +COMMON_SOURCES += rescaler.c +COMMON_SOURCES += rescaler_mips32.c +COMMON_SOURCES += rescaler_mips_dsp_r2.c +COMMON_SOURCES += upsampling.c +COMMON_SOURCES += upsampling_mips_dsp_r2.c +COMMON_SOURCES += yuv.c +COMMON_SOURCES += yuv.h +COMMON_SOURCES += yuv_mips32.c +COMMON_SOURCES += yuv_mips_dsp_r2.c + +ENC_SOURCES = +ENC_SOURCES += argb.c +ENC_SOURCES += argb_mips_dsp_r2.c +ENC_SOURCES += cost.c +ENC_SOURCES += cost_mips32.c +ENC_SOURCES += cost_mips_dsp_r2.c +ENC_SOURCES += enc.c +ENC_SOURCES += enc_mips32.c +ENC_SOURCES += enc_mips_dsp_r2.c +ENC_SOURCES += lossless_enc.c +ENC_SOURCES += lossless_enc_mips32.c +ENC_SOURCES += lossless_enc_mips_dsp_r2.c + +libwebpdsp_avx2_la_SOURCES = +libwebpdsp_avx2_la_SOURCES += enc_avx2.c +libwebpdsp_avx2_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_avx2_la_CFLAGS = $(AM_CFLAGS) $(AVX2_FLAGS) + +libwebpdspdecode_sse41_la_SOURCES = +libwebpdspdecode_sse41_la_SOURCES += alpha_processing_sse41.c +libwebpdspdecode_sse41_la_SOURCES += dec_sse41.c +libwebpdspdecode_sse41_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdspdecode_sse41_la_CFLAGS = $(AM_CFLAGS) $(SSE41_FLAGS) + +libwebpdspdecode_sse2_la_SOURCES = +libwebpdspdecode_sse2_la_SOURCES += alpha_processing_sse2.c +libwebpdspdecode_sse2_la_SOURCES += dec_sse2.c +libwebpdspdecode_sse2_la_SOURCES += filters_sse2.c +libwebpdspdecode_sse2_la_SOURCES += lossless_sse2.c +libwebpdspdecode_sse2_la_SOURCES += rescaler_sse2.c +libwebpdspdecode_sse2_la_SOURCES += upsampling_sse2.c +libwebpdspdecode_sse2_la_SOURCES += yuv_sse2.c +libwebpdspdecode_sse2_la_CPPFLAGS = $(libwebpdsp_sse2_la_CPPFLAGS) +libwebpdspdecode_sse2_la_CFLAGS = $(libwebpdsp_sse2_la_CFLAGS) + +libwebpdspdecode_neon_la_SOURCES = +libwebpdspdecode_neon_la_SOURCES += alpha_processing_neon.c +libwebpdspdecode_neon_la_SOURCES += dec_neon.c +libwebpdspdecode_neon_la_SOURCES += filters_neon.c +libwebpdspdecode_neon_la_SOURCES += lossless_neon.c +libwebpdspdecode_neon_la_SOURCES += neon.h +libwebpdspdecode_neon_la_SOURCES += rescaler_neon.c +libwebpdspdecode_neon_la_SOURCES += upsampling_neon.c +libwebpdspdecode_neon_la_CPPFLAGS = $(libwebpdsp_neon_la_CPPFLAGS) +libwebpdspdecode_neon_la_CFLAGS = $(libwebpdsp_neon_la_CFLAGS) + +libwebpdspdecode_msa_la_SOURCES = +libwebpdspdecode_msa_la_SOURCES += dec_msa.c +libwebpdspdecode_msa_la_SOURCES += filters_msa.c +libwebpdspdecode_msa_la_SOURCES += lossless_msa.c +libwebpdspdecode_msa_la_SOURCES += msa_macro.h +libwebpdspdecode_msa_la_SOURCES += rescaler_msa.c +libwebpdspdecode_msa_la_SOURCES += upsampling_msa.c +libwebpdspdecode_msa_la_CPPFLAGS = $(libwebpdsp_msa_la_CPPFLAGS) +libwebpdspdecode_msa_la_CFLAGS = $(libwebpdsp_msa_la_CFLAGS) + +libwebpdsp_sse2_la_SOURCES = +libwebpdsp_sse2_la_SOURCES += argb_sse2.c +libwebpdsp_sse2_la_SOURCES += cost_sse2.c +libwebpdsp_sse2_la_SOURCES += enc_sse2.c +libwebpdsp_sse2_la_SOURCES += lossless_enc_sse2.c +libwebpdsp_sse2_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_sse2_la_CFLAGS = $(AM_CFLAGS) $(SSE2_FLAGS) +libwebpdsp_sse2_la_LIBADD = libwebpdspdecode_sse2.la + +libwebpdsp_sse41_la_SOURCES = +libwebpdsp_sse41_la_SOURCES += enc_sse41.c +libwebpdsp_sse41_la_SOURCES += lossless_enc_sse41.c +libwebpdsp_sse41_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_sse41_la_CFLAGS = $(AM_CFLAGS) $(SSE41_FLAGS) +libwebpdsp_sse41_la_LIBADD = libwebpdspdecode_sse41.la + +libwebpdsp_neon_la_SOURCES = +libwebpdsp_neon_la_SOURCES += enc_neon.c +libwebpdsp_neon_la_SOURCES += lossless_enc_neon.c +libwebpdsp_neon_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_neon_la_CFLAGS = $(AM_CFLAGS) $(NEON_FLAGS) +libwebpdsp_neon_la_LIBADD = libwebpdspdecode_neon.la + +libwebpdsp_msa_la_SOURCES = +libwebpdsp_msa_la_SOURCES += enc_msa.c +libwebpdsp_msa_la_SOURCES += lossless_enc_msa.c +libwebpdsp_msa_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_msa_la_CFLAGS = $(AM_CFLAGS) +libwebpdsp_msa_la_LIBADD = libwebpdspdecode_msa.la + +libwebpdsp_la_SOURCES = $(COMMON_SOURCES) $(ENC_SOURCES) + +noinst_HEADERS = +noinst_HEADERS += ../dec/vp8_dec.h +noinst_HEADERS += ../webp/decode.h + +libwebpdsp_la_CPPFLAGS = +libwebpdsp_la_CPPFLAGS += $(AM_CPPFLAGS) +libwebpdsp_la_CPPFLAGS += $(USE_EXPERIMENTAL_CODE) $(USE_SWAP_16BIT_CSP) +libwebpdsp_la_LDFLAGS = -lm +libwebpdsp_la_LIBADD = +libwebpdsp_la_LIBADD += libwebpdsp_avx2.la libwebpdsp_sse2.la +libwebpdsp_la_LIBADD += libwebpdsp_sse41.la +libwebpdsp_la_LIBADD += libwebpdsp_neon.la +libwebpdsp_la_LIBADD += libwebpdsp_msa.la + +if BUILD_LIBWEBPDECODER + libwebpdspdecode_la_SOURCES = $(COMMON_SOURCES) + + libwebpdspdecode_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) + libwebpdspdecode_la_LDFLAGS = $(libwebpdsp_la_LDFLAGS) + libwebpdspdecode_la_LIBADD = + libwebpdspdecode_la_LIBADD += libwebpdspdecode_sse2.la + libwebpdspdecode_la_LIBADD += libwebpdspdecode_sse41.la + libwebpdspdecode_la_LIBADD += libwebpdspdecode_neon.la + libwebpdspdecode_la_LIBADD += libwebpdspdecode_msa.la +endif diff --git a/3rdparty/libwebp/dsp/alpha_processing.c b/3rdparty/libwebp/dsp/alpha_processing.c new file mode 100644 index 0000000000..4b60e092be --- /dev/null +++ b/3rdparty/libwebp/dsp/alpha_processing.c @@ -0,0 +1,397 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include "./dsp.h" + +// Tables can be faster on some platform but incur some extra binary size (~2k). +// #define USE_TABLES_FOR_ALPHA_MULT + +// ----------------------------------------------------------------------------- + +#define MFIX 24 // 24bit fixed-point arithmetic +#define HALF ((1u << MFIX) >> 1) +#define KINV_255 ((1u << MFIX) / 255u) + +static uint32_t Mult(uint8_t x, uint32_t mult) { + const uint32_t v = (x * mult + HALF) >> MFIX; + assert(v <= 255); // <- 24bit precision is enough to ensure that. + return v; +} + +#ifdef USE_TABLES_FOR_ALPHA_MULT + +static const uint32_t kMultTables[2][256] = { + { // (255u << MFIX) / alpha + 0x00000000, 0xff000000, 0x7f800000, 0x55000000, 0x3fc00000, 0x33000000, + 0x2a800000, 0x246db6db, 0x1fe00000, 0x1c555555, 0x19800000, 0x172e8ba2, + 0x15400000, 0x139d89d8, 0x1236db6d, 0x11000000, 0x0ff00000, 0x0f000000, + 0x0e2aaaaa, 0x0d6bca1a, 0x0cc00000, 0x0c249249, 0x0b9745d1, 0x0b1642c8, + 0x0aa00000, 0x0a333333, 0x09cec4ec, 0x0971c71c, 0x091b6db6, 0x08cb08d3, + 0x08800000, 0x0839ce73, 0x07f80000, 0x07ba2e8b, 0x07800000, 0x07492492, + 0x07155555, 0x06e45306, 0x06b5e50d, 0x0689d89d, 0x06600000, 0x063831f3, + 0x06124924, 0x05ee23b8, 0x05cba2e8, 0x05aaaaaa, 0x058b2164, 0x056cefa8, + 0x05500000, 0x05343eb1, 0x05199999, 0x05000000, 0x04e76276, 0x04cfb2b7, + 0x04b8e38e, 0x04a2e8ba, 0x048db6db, 0x0479435e, 0x04658469, 0x045270d0, + 0x04400000, 0x042e29f7, 0x041ce739, 0x040c30c3, 0x03fc0000, 0x03ec4ec4, + 0x03dd1745, 0x03ce540f, 0x03c00000, 0x03b21642, 0x03a49249, 0x03976fc6, + 0x038aaaaa, 0x037e3f1f, 0x03722983, 0x03666666, 0x035af286, 0x034fcace, + 0x0344ec4e, 0x033a5440, 0x03300000, 0x0325ed09, 0x031c18f9, 0x0312818a, + 0x03092492, 0x03000000, 0x02f711dc, 0x02ee5846, 0x02e5d174, 0x02dd7baf, + 0x02d55555, 0x02cd5cd5, 0x02c590b2, 0x02bdef7b, 0x02b677d4, 0x02af286b, + 0x02a80000, 0x02a0fd5c, 0x029a1f58, 0x029364d9, 0x028ccccc, 0x0286562d, + 0x02800000, 0x0279c952, 0x0273b13b, 0x026db6db, 0x0267d95b, 0x026217ec, + 0x025c71c7, 0x0256e62a, 0x0251745d, 0x024c1bac, 0x0246db6d, 0x0241b2f9, + 0x023ca1af, 0x0237a6f4, 0x0232c234, 0x022df2df, 0x02293868, 0x02249249, + 0x02200000, 0x021b810e, 0x021714fb, 0x0212bb51, 0x020e739c, 0x020a3d70, + 0x02061861, 0x02020408, 0x01fe0000, 0x01fa0be8, 0x01f62762, 0x01f25213, + 0x01ee8ba2, 0x01ead3ba, 0x01e72a07, 0x01e38e38, 0x01e00000, 0x01dc7f10, + 0x01d90b21, 0x01d5a3e9, 0x01d24924, 0x01cefa8d, 0x01cbb7e3, 0x01c880e5, + 0x01c55555, 0x01c234f7, 0x01bf1f8f, 0x01bc14e5, 0x01b914c1, 0x01b61eed, + 0x01b33333, 0x01b05160, 0x01ad7943, 0x01aaaaaa, 0x01a7e567, 0x01a5294a, + 0x01a27627, 0x019fcbd2, 0x019d2a20, 0x019a90e7, 0x01980000, 0x01957741, + 0x0192f684, 0x01907da4, 0x018e0c7c, 0x018ba2e8, 0x018940c5, 0x0186e5f0, + 0x01849249, 0x018245ae, 0x01800000, 0x017dc11f, 0x017b88ee, 0x0179574e, + 0x01772c23, 0x01750750, 0x0172e8ba, 0x0170d045, 0x016ebdd7, 0x016cb157, + 0x016aaaaa, 0x0168a9b9, 0x0166ae6a, 0x0164b8a7, 0x0162c859, 0x0160dd67, + 0x015ef7bd, 0x015d1745, 0x015b3bea, 0x01596596, 0x01579435, 0x0155c7b4, + 0x01540000, 0x01523d03, 0x01507eae, 0x014ec4ec, 0x014d0fac, 0x014b5edc, + 0x0149b26c, 0x01480a4a, 0x01466666, 0x0144c6af, 0x01432b16, 0x0141938b, + 0x01400000, 0x013e7063, 0x013ce4a9, 0x013b5cc0, 0x0139d89d, 0x01385830, + 0x0136db6d, 0x01356246, 0x0133ecad, 0x01327a97, 0x01310bf6, 0x012fa0be, + 0x012e38e3, 0x012cd459, 0x012b7315, 0x012a150a, 0x0128ba2e, 0x01276276, + 0x01260dd6, 0x0124bc44, 0x01236db6, 0x01222222, 0x0120d97c, 0x011f93bc, + 0x011e50d7, 0x011d10c4, 0x011bd37a, 0x011a98ef, 0x0119611a, 0x01182bf2, + 0x0116f96f, 0x0115c988, 0x01149c34, 0x0113716a, 0x01124924, 0x01112358, + 0x01100000, 0x010edf12, 0x010dc087, 0x010ca458, 0x010b8a7d, 0x010a72f0, + 0x01095da8, 0x01084a9f, 0x010739ce, 0x01062b2e, 0x01051eb8, 0x01041465, + 0x01030c30, 0x01020612, 0x01010204, 0x01000000 }, + { // alpha * KINV_255 + 0x00000000, 0x00010101, 0x00020202, 0x00030303, 0x00040404, 0x00050505, + 0x00060606, 0x00070707, 0x00080808, 0x00090909, 0x000a0a0a, 0x000b0b0b, + 0x000c0c0c, 0x000d0d0d, 0x000e0e0e, 0x000f0f0f, 0x00101010, 0x00111111, + 0x00121212, 0x00131313, 0x00141414, 0x00151515, 0x00161616, 0x00171717, + 0x00181818, 0x00191919, 0x001a1a1a, 0x001b1b1b, 0x001c1c1c, 0x001d1d1d, + 0x001e1e1e, 0x001f1f1f, 0x00202020, 0x00212121, 0x00222222, 0x00232323, + 0x00242424, 0x00252525, 0x00262626, 0x00272727, 0x00282828, 0x00292929, + 0x002a2a2a, 0x002b2b2b, 0x002c2c2c, 0x002d2d2d, 0x002e2e2e, 0x002f2f2f, + 0x00303030, 0x00313131, 0x00323232, 0x00333333, 0x00343434, 0x00353535, + 0x00363636, 0x00373737, 0x00383838, 0x00393939, 0x003a3a3a, 0x003b3b3b, + 0x003c3c3c, 0x003d3d3d, 0x003e3e3e, 0x003f3f3f, 0x00404040, 0x00414141, + 0x00424242, 0x00434343, 0x00444444, 0x00454545, 0x00464646, 0x00474747, + 0x00484848, 0x00494949, 0x004a4a4a, 0x004b4b4b, 0x004c4c4c, 0x004d4d4d, + 0x004e4e4e, 0x004f4f4f, 0x00505050, 0x00515151, 0x00525252, 0x00535353, + 0x00545454, 0x00555555, 0x00565656, 0x00575757, 0x00585858, 0x00595959, + 0x005a5a5a, 0x005b5b5b, 0x005c5c5c, 0x005d5d5d, 0x005e5e5e, 0x005f5f5f, + 0x00606060, 0x00616161, 0x00626262, 0x00636363, 0x00646464, 0x00656565, + 0x00666666, 0x00676767, 0x00686868, 0x00696969, 0x006a6a6a, 0x006b6b6b, + 0x006c6c6c, 0x006d6d6d, 0x006e6e6e, 0x006f6f6f, 0x00707070, 0x00717171, + 0x00727272, 0x00737373, 0x00747474, 0x00757575, 0x00767676, 0x00777777, + 0x00787878, 0x00797979, 0x007a7a7a, 0x007b7b7b, 0x007c7c7c, 0x007d7d7d, + 0x007e7e7e, 0x007f7f7f, 0x00808080, 0x00818181, 0x00828282, 0x00838383, + 0x00848484, 0x00858585, 0x00868686, 0x00878787, 0x00888888, 0x00898989, + 0x008a8a8a, 0x008b8b8b, 0x008c8c8c, 0x008d8d8d, 0x008e8e8e, 0x008f8f8f, + 0x00909090, 0x00919191, 0x00929292, 0x00939393, 0x00949494, 0x00959595, + 0x00969696, 0x00979797, 0x00989898, 0x00999999, 0x009a9a9a, 0x009b9b9b, + 0x009c9c9c, 0x009d9d9d, 0x009e9e9e, 0x009f9f9f, 0x00a0a0a0, 0x00a1a1a1, + 0x00a2a2a2, 0x00a3a3a3, 0x00a4a4a4, 0x00a5a5a5, 0x00a6a6a6, 0x00a7a7a7, + 0x00a8a8a8, 0x00a9a9a9, 0x00aaaaaa, 0x00ababab, 0x00acacac, 0x00adadad, + 0x00aeaeae, 0x00afafaf, 0x00b0b0b0, 0x00b1b1b1, 0x00b2b2b2, 0x00b3b3b3, + 0x00b4b4b4, 0x00b5b5b5, 0x00b6b6b6, 0x00b7b7b7, 0x00b8b8b8, 0x00b9b9b9, + 0x00bababa, 0x00bbbbbb, 0x00bcbcbc, 0x00bdbdbd, 0x00bebebe, 0x00bfbfbf, + 0x00c0c0c0, 0x00c1c1c1, 0x00c2c2c2, 0x00c3c3c3, 0x00c4c4c4, 0x00c5c5c5, + 0x00c6c6c6, 0x00c7c7c7, 0x00c8c8c8, 0x00c9c9c9, 0x00cacaca, 0x00cbcbcb, + 0x00cccccc, 0x00cdcdcd, 0x00cecece, 0x00cfcfcf, 0x00d0d0d0, 0x00d1d1d1, + 0x00d2d2d2, 0x00d3d3d3, 0x00d4d4d4, 0x00d5d5d5, 0x00d6d6d6, 0x00d7d7d7, + 0x00d8d8d8, 0x00d9d9d9, 0x00dadada, 0x00dbdbdb, 0x00dcdcdc, 0x00dddddd, + 0x00dedede, 0x00dfdfdf, 0x00e0e0e0, 0x00e1e1e1, 0x00e2e2e2, 0x00e3e3e3, + 0x00e4e4e4, 0x00e5e5e5, 0x00e6e6e6, 0x00e7e7e7, 0x00e8e8e8, 0x00e9e9e9, + 0x00eaeaea, 0x00ebebeb, 0x00ececec, 0x00ededed, 0x00eeeeee, 0x00efefef, + 0x00f0f0f0, 0x00f1f1f1, 0x00f2f2f2, 0x00f3f3f3, 0x00f4f4f4, 0x00f5f5f5, + 0x00f6f6f6, 0x00f7f7f7, 0x00f8f8f8, 0x00f9f9f9, 0x00fafafa, 0x00fbfbfb, + 0x00fcfcfc, 0x00fdfdfd, 0x00fefefe, 0x00ffffff } +}; + +static WEBP_INLINE uint32_t GetScale(uint32_t a, int inverse) { + return kMultTables[!inverse][a]; +} + +#else + +static WEBP_INLINE uint32_t GetScale(uint32_t a, int inverse) { + return inverse ? (255u << MFIX) / a : a * KINV_255; +} + +#endif // USE_TABLES_FOR_ALPHA_MULT + +void WebPMultARGBRowC(uint32_t* const ptr, int width, int inverse) { + int x; + for (x = 0; x < width; ++x) { + const uint32_t argb = ptr[x]; + if (argb < 0xff000000u) { // alpha < 255 + if (argb <= 0x00ffffffu) { // alpha == 0 + ptr[x] = 0; + } else { + const uint32_t alpha = (argb >> 24) & 0xff; + const uint32_t scale = GetScale(alpha, inverse); + uint32_t out = argb & 0xff000000u; + out |= Mult(argb >> 0, scale) << 0; + out |= Mult(argb >> 8, scale) << 8; + out |= Mult(argb >> 16, scale) << 16; + ptr[x] = out; + } + } + } +} + +void WebPMultRowC(uint8_t* const ptr, const uint8_t* const alpha, + int width, int inverse) { + int x; + for (x = 0; x < width; ++x) { + const uint32_t a = alpha[x]; + if (a != 255) { + if (a == 0) { + ptr[x] = 0; + } else { + const uint32_t scale = GetScale(a, inverse); + ptr[x] = Mult(ptr[x], scale); + } + } + } +} + +#undef KINV_255 +#undef HALF +#undef MFIX + +void (*WebPMultARGBRow)(uint32_t* const ptr, int width, int inverse); +void (*WebPMultRow)(uint8_t* const ptr, const uint8_t* const alpha, + int width, int inverse); + +//------------------------------------------------------------------------------ +// Generic per-plane calls + +void WebPMultARGBRows(uint8_t* ptr, int stride, int width, int num_rows, + int inverse) { + int n; + for (n = 0; n < num_rows; ++n) { + WebPMultARGBRow((uint32_t*)ptr, width, inverse); + ptr += stride; + } +} + +void WebPMultRows(uint8_t* ptr, int stride, + const uint8_t* alpha, int alpha_stride, + int width, int num_rows, int inverse) { + int n; + for (n = 0; n < num_rows; ++n) { + WebPMultRow(ptr, alpha, width, inverse); + ptr += stride; + alpha += alpha_stride; + } +} + +//------------------------------------------------------------------------------ +// Premultiplied modes + +// non dithered-modes + +// (x * a * 32897) >> 23 is bit-wise equivalent to (int)(x * a / 255.) +// for all 8bit x or a. For bit-wise equivalence to (int)(x * a / 255. + .5), +// one can use instead: (x * a * 65793 + (1 << 23)) >> 24 +#if 1 // (int)(x * a / 255.) +#define MULTIPLIER(a) ((a) * 32897U) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) +#else // (int)(x * a / 255. + .5) +#define MULTIPLIER(a) ((a) * 65793U) +#define PREMULTIPLY(x, m) (((x) * (m) + (1U << 23)) >> 24) +#endif + +static void ApplyAlphaMultiply(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + while (h-- > 0) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + int i; + for (i = 0; i < w; ++i) { + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#undef MULTIPLIER +#undef PREMULTIPLY + +// rgbA4444 + +#define MULTIPLIER(a) ((a) * 0x1111) // 0x1111 ~= (1 << 16) / 15 + +static WEBP_INLINE uint8_t dither_hi(uint8_t x) { + return (x & 0xf0) | (x >> 4); +} + +static WEBP_INLINE uint8_t dither_lo(uint8_t x) { + return (x & 0x0f) | (x << 4); +} + +static WEBP_INLINE uint8_t multiply(uint8_t x, uint32_t m) { + return (x * m) >> 16; +} + +static WEBP_INLINE void ApplyAlphaMultiply4444(uint8_t* rgba4444, + int w, int h, int stride, + int rg_byte_pos /* 0 or 1 */) { + while (h-- > 0) { + int i; + for (i = 0; i < w; ++i) { + const uint32_t rg = rgba4444[2 * i + rg_byte_pos]; + const uint32_t ba = rgba4444[2 * i + (rg_byte_pos ^ 1)]; + const uint8_t a = ba & 0x0f; + const uint32_t mult = MULTIPLIER(a); + const uint8_t r = multiply(dither_hi(rg), mult); + const uint8_t g = multiply(dither_lo(rg), mult); + const uint8_t b = multiply(dither_hi(ba), mult); + rgba4444[2 * i + rg_byte_pos] = (r & 0xf0) | ((g >> 4) & 0x0f); + rgba4444[2 * i + (rg_byte_pos ^ 1)] = (b & 0xf0) | a; + } + rgba4444 += stride; + } +} +#undef MULTIPLIER + +static void ApplyAlphaMultiply_16b(uint8_t* rgba4444, + int w, int h, int stride) { +#ifdef WEBP_SWAP_16BIT_CSP + ApplyAlphaMultiply4444(rgba4444, w, h, stride, 1); +#else + ApplyAlphaMultiply4444(rgba4444, w, h, stride, 0); +#endif +} + +static int DispatchAlpha_C(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint8_t* dst, int dst_stride) { + uint32_t alpha_mask = 0xff; + int i, j; + + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_mask &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + + return (alpha_mask != 0xff); +} + +static void DispatchAlphaToGreen_C(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint32_t* dst, int dst_stride) { + int i, j; + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + dst[i] = alpha[i] << 8; // leave A/R/B channels zero'd. + } + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha_C(const uint8_t* argb, int argb_stride, + int width, int height, + uint8_t* alpha, int alpha_stride) { + uint8_t alpha_mask = 0xff; + int i, j; + + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + const uint8_t alpha_value = argb[4 * i]; + alpha[i] = alpha_value; + alpha_mask &= alpha_value; + } + argb += argb_stride; + alpha += alpha_stride; + } + return (alpha_mask == 0xff); +} + +static void ExtractGreen_C(const uint32_t* argb, uint8_t* alpha, int size) { + int i; + for (i = 0; i < size; ++i) alpha[i] = argb[i] >> 8; +} + +void (*WebPApplyAlphaMultiply)(uint8_t*, int, int, int, int); +void (*WebPApplyAlphaMultiply4444)(uint8_t*, int, int, int); +int (*WebPDispatchAlpha)(const uint8_t*, int, int, int, uint8_t*, int); +void (*WebPDispatchAlphaToGreen)(const uint8_t*, int, int, int, uint32_t*, int); +int (*WebPExtractAlpha)(const uint8_t*, int, int, int, uint8_t*, int); +void (*WebPExtractGreen)(const uint32_t* argb, uint8_t* alpha, int size); + +//------------------------------------------------------------------------------ +// Init function + +extern void WebPInitAlphaProcessingMIPSdspR2(void); +extern void WebPInitAlphaProcessingSSE2(void); +extern void WebPInitAlphaProcessingSSE41(void); +extern void WebPInitAlphaProcessingNEON(void); + +static volatile VP8CPUInfo alpha_processing_last_cpuinfo_used = + (VP8CPUInfo)&alpha_processing_last_cpuinfo_used; + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessing(void) { + if (alpha_processing_last_cpuinfo_used == VP8GetCPUInfo) return; + + WebPMultARGBRow = WebPMultARGBRowC; + WebPMultRow = WebPMultRowC; + WebPApplyAlphaMultiply = ApplyAlphaMultiply; + WebPApplyAlphaMultiply4444 = ApplyAlphaMultiply_16b; + + WebPDispatchAlpha = DispatchAlpha_C; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen_C; + WebPExtractAlpha = ExtractAlpha_C; + WebPExtractGreen = ExtractGreen_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitAlphaProcessingSSE2(); +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitAlphaProcessingSSE41(); + } +#endif + } +#endif +#if defined(WEBP_USE_NEON) + if (VP8GetCPUInfo(kNEON)) { + WebPInitAlphaProcessingNEON(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitAlphaProcessingMIPSdspR2(); + } +#endif + } + alpha_processing_last_cpuinfo_used = VP8GetCPUInfo; +} diff --git a/3rdparty/libwebp/dsp/alpha_processing_mips_dsp_r2.c b/3rdparty/libwebp/dsp/alpha_processing_mips_dsp_r2.c new file mode 100644 index 0000000000..c631d78905 --- /dev/null +++ b/3rdparty/libwebp/dsp/alpha_processing_mips_dsp_r2.c @@ -0,0 +1,141 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel. +// +// Author(s): Branimir Vasic (branimir.vasic@imgtec.com) +// Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +static int DispatchAlpha(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint8_t* dst, int dst_stride) { + uint32_t alpha_mask = 0xffffffff; + int i, j, temp0; + + for (j = 0; j < height; ++j) { + uint8_t* pdst = dst; + const uint8_t* palpha = alpha; + for (i = 0; i < (width >> 2); ++i) { + int temp1, temp2, temp3; + + __asm__ volatile ( + "ulw %[temp0], 0(%[palpha]) \n\t" + "addiu %[palpha], %[palpha], 4 \n\t" + "addiu %[pdst], %[pdst], 16 \n\t" + "srl %[temp1], %[temp0], 8 \n\t" + "srl %[temp2], %[temp0], 16 \n\t" + "srl %[temp3], %[temp0], 24 \n\t" + "and %[alpha_mask], %[alpha_mask], %[temp0] \n\t" + "sb %[temp0], -16(%[pdst]) \n\t" + "sb %[temp1], -12(%[pdst]) \n\t" + "sb %[temp2], -8(%[pdst]) \n\t" + "sb %[temp3], -4(%[pdst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [palpha]"+r"(palpha), [pdst]"+r"(pdst), + [alpha_mask]"+r"(alpha_mask) + : + : "memory" + ); + } + + for (i = 0; i < (width & 3); ++i) { + __asm__ volatile ( + "lbu %[temp0], 0(%[palpha]) \n\t" + "addiu %[palpha], %[palpha], 1 \n\t" + "sb %[temp0], 0(%[pdst]) \n\t" + "and %[alpha_mask], %[alpha_mask], %[temp0] \n\t" + "addiu %[pdst], %[pdst], 4 \n\t" + : [temp0]"=&r"(temp0), [palpha]"+r"(palpha), [pdst]"+r"(pdst), + [alpha_mask]"+r"(alpha_mask) + : + : "memory" + ); + } + alpha += alpha_stride; + dst += dst_stride; + } + + __asm__ volatile ( + "ext %[temp0], %[alpha_mask], 0, 16 \n\t" + "srl %[alpha_mask], %[alpha_mask], 16 \n\t" + "and %[alpha_mask], %[alpha_mask], %[temp0] \n\t" + "ext %[temp0], %[alpha_mask], 0, 8 \n\t" + "srl %[alpha_mask], %[alpha_mask], 8 \n\t" + "and %[alpha_mask], %[alpha_mask], %[temp0] \n\t" + : [temp0]"=&r"(temp0), [alpha_mask]"+r"(alpha_mask) + : + ); + + return (alpha_mask != 0xff); +} + +static void MultARGBRow(uint32_t* const ptr, int width, int inverse) { + int x; + const uint32_t c_00ffffff = 0x00ffffffu; + const uint32_t c_ff000000 = 0xff000000u; + const uint32_t c_8000000 = 0x00800000u; + const uint32_t c_8000080 = 0x00800080u; + for (x = 0; x < width; ++x) { + const uint32_t argb = ptr[x]; + if (argb < 0xff000000u) { // alpha < 255 + if (argb <= 0x00ffffffu) { // alpha == 0 + ptr[x] = 0; + } else { + int temp0, temp1, temp2, temp3, alpha; + __asm__ volatile ( + "srl %[alpha], %[argb], 24 \n\t" + "replv.qb %[temp0], %[alpha] \n\t" + "and %[temp0], %[temp0], %[c_00ffffff] \n\t" + "beqz %[inverse], 0f \n\t" + "divu $zero, %[c_ff000000], %[alpha] \n\t" + "mflo %[temp0] \n\t" + "0: \n\t" + "andi %[temp1], %[argb], 0xff \n\t" + "ext %[temp2], %[argb], 8, 8 \n\t" + "ext %[temp3], %[argb], 16, 8 \n\t" + "mul %[temp1], %[temp1], %[temp0] \n\t" + "mul %[temp2], %[temp2], %[temp0] \n\t" + "mul %[temp3], %[temp3], %[temp0] \n\t" + "precrq.ph.w %[temp1], %[temp2], %[temp1] \n\t" + "addu %[temp3], %[temp3], %[c_8000000] \n\t" + "addu %[temp1], %[temp1], %[c_8000080] \n\t" + "precrq.ph.w %[temp3], %[argb], %[temp3] \n\t" + "precrq.qb.ph %[temp1], %[temp3], %[temp1] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [alpha]"=&r"(alpha) + : [inverse]"r"(inverse), [c_00ffffff]"r"(c_00ffffff), + [c_8000000]"r"(c_8000000), [c_8000080]"r"(c_8000080), + [c_ff000000]"r"(c_ff000000), [argb]"r"(argb) + : "memory", "hi", "lo" + ); + ptr[x] = temp1; + } + } + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitAlphaProcessingMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingMIPSdspR2(void) { + WebPDispatchAlpha = DispatchAlpha; + WebPMultARGBRow = MultARGBRow; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/alpha_processing_neon.c b/3rdparty/libwebp/dsp/alpha_processing_neon.c new file mode 100644 index 0000000000..606a401cf7 --- /dev/null +++ b/3rdparty/libwebp/dsp/alpha_processing_neon.c @@ -0,0 +1,191 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel, NEON version. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_NEON) + +#include "./neon.h" + +//------------------------------------------------------------------------------ + +#define MULTIPLIER(a) ((a) * 0x8081) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) + +#define MULTIPLY_BY_ALPHA(V, ALPHA, OTHER) do { \ + const uint8x8_t alpha = (V).val[(ALPHA)]; \ + const uint16x8_t r1 = vmull_u8((V).val[1], alpha); \ + const uint16x8_t g1 = vmull_u8((V).val[2], alpha); \ + const uint16x8_t b1 = vmull_u8((V).val[(OTHER)], alpha); \ + /* we use: v / 255 = (v + 1 + (v >> 8)) >> 8 */ \ + const uint16x8_t r2 = vsraq_n_u16(r1, r1, 8); \ + const uint16x8_t g2 = vsraq_n_u16(g1, g1, 8); \ + const uint16x8_t b2 = vsraq_n_u16(b1, b1, 8); \ + const uint16x8_t r3 = vaddq_u16(r2, kOne); \ + const uint16x8_t g3 = vaddq_u16(g2, kOne); \ + const uint16x8_t b3 = vaddq_u16(b2, kOne); \ + (V).val[1] = vshrn_n_u16(r3, 8); \ + (V).val[2] = vshrn_n_u16(g3, 8); \ + (V).val[(OTHER)] = vshrn_n_u16(b3, 8); \ +} while (0) + +static void ApplyAlphaMultiply_NEON(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + const uint16x8_t kOne = vdupq_n_u16(1u); + while (h-- > 0) { + uint32_t* const rgbx = (uint32_t*)rgba; + int i = 0; + if (alpha_first) { + for (; i + 8 <= w; i += 8) { + // load aaaa...|rrrr...|gggg...|bbbb... + uint8x8x4_t RGBX = vld4_u8((const uint8_t*)(rgbx + i)); + MULTIPLY_BY_ALPHA(RGBX, 0, 3); + vst4_u8((uint8_t*)(rgbx + i), RGBX); + } + } else { + for (; i + 8 <= w; i += 8) { + uint8x8x4_t RGBX = vld4_u8((const uint8_t*)(rgbx + i)); + MULTIPLY_BY_ALPHA(RGBX, 3, 0); + vst4_u8((uint8_t*)(rgbx + i), RGBX); + } + } + // Finish with left-overs. + for (; i < w; ++i) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#undef MULTIPLY_BY_ALPHA +#undef MULTIPLIER +#undef PREMULTIPLY + +//------------------------------------------------------------------------------ + +static int DispatchAlpha_NEON(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint8_t* dst, int dst_stride) { + uint32_t alpha_mask = 0xffffffffu; + uint8x8_t mask8 = vdup_n_u8(0xff); + uint32_t tmp[2]; + int i, j; + for (j = 0; j < height; ++j) { + // We don't know if alpha is first or last in dst[] (depending on rgbA/Argb + // mode). So we must be sure dst[4*i + 8 - 1] is writable for the store. + // Hence the test with 'width - 1' instead of just 'width'. + for (i = 0; i + 8 <= width - 1; i += 8) { + uint8x8x4_t rgbX = vld4_u8((const uint8_t*)(dst + 4 * i)); + const uint8x8_t alphas = vld1_u8(alpha + i); + rgbX.val[0] = alphas; + vst4_u8((uint8_t*)(dst + 4 * i), rgbX); + mask8 = vand_u8(mask8, alphas); + } + for (; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_mask &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + vst1_u8((uint8_t*)tmp, mask8); + alpha_mask &= tmp[0]; + alpha_mask &= tmp[1]; + return (alpha_mask != 0xffffffffu); +} + +static void DispatchAlphaToGreen_NEON(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint32_t* dst, int dst_stride) { + int i, j; + uint8x8x4_t greens; // leave A/R/B channels zero'd. + greens.val[0] = vdup_n_u8(0); + greens.val[2] = vdup_n_u8(0); + greens.val[3] = vdup_n_u8(0); + for (j = 0; j < height; ++j) { + for (i = 0; i + 8 <= width; i += 8) { + greens.val[1] = vld1_u8(alpha + i); + vst4_u8((uint8_t*)(dst + i), greens); + } + for (; i < width; ++i) dst[i] = alpha[i] << 8; + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha_NEON(const uint8_t* argb, int argb_stride, + int width, int height, + uint8_t* alpha, int alpha_stride) { + uint32_t alpha_mask = 0xffffffffu; + uint8x8_t mask8 = vdup_n_u8(0xff); + uint32_t tmp[2]; + int i, j; + for (j = 0; j < height; ++j) { + // We don't know if alpha is first or last in dst[] (depending on rgbA/Argb + // mode). So we must be sure dst[4*i + 8 - 1] is writable for the store. + // Hence the test with 'width - 1' instead of just 'width'. + for (i = 0; i + 8 <= width - 1; i += 8) { + const uint8x8x4_t rgbX = vld4_u8((const uint8_t*)(argb + 4 * i)); + const uint8x8_t alphas = rgbX.val[0]; + vst1_u8((uint8_t*)(alpha + i), alphas); + mask8 = vand_u8(mask8, alphas); + } + for (; i < width; ++i) { + alpha[i] = argb[4 * i]; + alpha_mask &= alpha[i]; + } + argb += argb_stride; + alpha += alpha_stride; + } + vst1_u8((uint8_t*)tmp, mask8); + alpha_mask &= tmp[0]; + alpha_mask &= tmp[1]; + return (alpha_mask == 0xffffffffu); +} + +static void ExtractGreen_NEON(const uint32_t* argb, + uint8_t* alpha, int size) { + int i; + for (i = 0; i + 16 <= size; i += 16) { + const uint8x16x4_t rgbX = vld4q_u8((const uint8_t*)(argb + i)); + const uint8x16_t greens = rgbX.val[1]; + vst1q_u8(alpha + i, greens); + } + for (; i < size; ++i) alpha[i] = (argb[i] >> 8) & 0xff; +} + +//------------------------------------------------------------------------------ + +extern void WebPInitAlphaProcessingNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingNEON(void) { + WebPApplyAlphaMultiply = ApplyAlphaMultiply_NEON; + WebPDispatchAlpha = DispatchAlpha_NEON; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen_NEON; + WebPExtractAlpha = ExtractAlpha_NEON; + WebPExtractGreen = ExtractGreen_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingNEON) + +#endif // WEBP_USE_NEON diff --git a/3rdparty/libwebp/dsp/alpha_processing_sse2.c b/3rdparty/libwebp/dsp/alpha_processing_sse2.c new file mode 100644 index 0000000000..83dc559fac --- /dev/null +++ b/3rdparty/libwebp/dsp/alpha_processing_sse2.c @@ -0,0 +1,285 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE2) +#include + +//------------------------------------------------------------------------------ + +static int DispatchAlpha(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint8_t* dst, int dst_stride) { + // alpha_and stores an 'and' operation of all the alpha[] values. The final + // value is not 0xff if any of the alpha[] is not equal to 0xff. + uint32_t alpha_and = 0xff; + int i, j; + const __m128i zero = _mm_setzero_si128(); + const __m128i rgb_mask = _mm_set1_epi32(0xffffff00u); // to preserve RGB + const __m128i all_0xff = _mm_set_epi32(0, 0, ~0u, ~0u); + __m128i all_alphas = all_0xff; + + // We must be able to access 3 extra bytes after the last written byte + // 'dst[4 * width - 4]', because we don't know if alpha is the first or the + // last byte of the quadruplet. + const int limit = (width - 1) & ~7; + + for (j = 0; j < height; ++j) { + __m128i* out = (__m128i*)dst; + for (i = 0; i < limit; i += 8) { + // load 8 alpha bytes + const __m128i a0 = _mm_loadl_epi64((const __m128i*)&alpha[i]); + const __m128i a1 = _mm_unpacklo_epi8(a0, zero); + const __m128i a2_lo = _mm_unpacklo_epi16(a1, zero); + const __m128i a2_hi = _mm_unpackhi_epi16(a1, zero); + // load 8 dst pixels (32 bytes) + const __m128i b0_lo = _mm_loadu_si128(out + 0); + const __m128i b0_hi = _mm_loadu_si128(out + 1); + // mask dst alpha values + const __m128i b1_lo = _mm_and_si128(b0_lo, rgb_mask); + const __m128i b1_hi = _mm_and_si128(b0_hi, rgb_mask); + // combine + const __m128i b2_lo = _mm_or_si128(b1_lo, a2_lo); + const __m128i b2_hi = _mm_or_si128(b1_hi, a2_hi); + // store + _mm_storeu_si128(out + 0, b2_lo); + _mm_storeu_si128(out + 1, b2_hi); + // accumulate eight alpha 'and' in parallel + all_alphas = _mm_and_si128(all_alphas, a0); + out += 2; + } + for (; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_and &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + // Combine the eight alpha 'and' into a 8-bit mask. + alpha_and &= _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas, all_0xff)); + return (alpha_and != 0xff); +} + +static void DispatchAlphaToGreen(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint32_t* dst, int dst_stride) { + int i, j; + const __m128i zero = _mm_setzero_si128(); + const int limit = width & ~15; + for (j = 0; j < height; ++j) { + for (i = 0; i < limit; i += 16) { // process 16 alpha bytes + const __m128i a0 = _mm_loadu_si128((const __m128i*)&alpha[i]); + const __m128i a1 = _mm_unpacklo_epi8(zero, a0); // note the 'zero' first! + const __m128i b1 = _mm_unpackhi_epi8(zero, a0); + const __m128i a2_lo = _mm_unpacklo_epi16(a1, zero); + const __m128i b2_lo = _mm_unpacklo_epi16(b1, zero); + const __m128i a2_hi = _mm_unpackhi_epi16(a1, zero); + const __m128i b2_hi = _mm_unpackhi_epi16(b1, zero); + _mm_storeu_si128((__m128i*)&dst[i + 0], a2_lo); + _mm_storeu_si128((__m128i*)&dst[i + 4], a2_hi); + _mm_storeu_si128((__m128i*)&dst[i + 8], b2_lo); + _mm_storeu_si128((__m128i*)&dst[i + 12], b2_hi); + } + for (; i < width; ++i) dst[i] = alpha[i] << 8; + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha(const uint8_t* argb, int argb_stride, + int width, int height, + uint8_t* alpha, int alpha_stride) { + // alpha_and stores an 'and' operation of all the alpha[] values. The final + // value is not 0xff if any of the alpha[] is not equal to 0xff. + uint32_t alpha_and = 0xff; + int i, j; + const __m128i a_mask = _mm_set1_epi32(0xffu); // to preserve alpha + const __m128i all_0xff = _mm_set_epi32(0, 0, ~0u, ~0u); + __m128i all_alphas = all_0xff; + + // We must be able to access 3 extra bytes after the last written byte + // 'src[4 * width - 4]', because we don't know if alpha is the first or the + // last byte of the quadruplet. + const int limit = (width - 1) & ~7; + + for (j = 0; j < height; ++j) { + const __m128i* src = (const __m128i*)argb; + for (i = 0; i < limit; i += 8) { + // load 32 argb bytes + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i b0 = _mm_and_si128(a0, a_mask); + const __m128i b1 = _mm_and_si128(a1, a_mask); + const __m128i c0 = _mm_packs_epi32(b0, b1); + const __m128i d0 = _mm_packus_epi16(c0, c0); + // store + _mm_storel_epi64((__m128i*)&alpha[i], d0); + // accumulate eight alpha 'and' in parallel + all_alphas = _mm_and_si128(all_alphas, d0); + src += 2; + } + for (; i < width; ++i) { + const uint32_t alpha_value = argb[4 * i]; + alpha[i] = alpha_value; + alpha_and &= alpha_value; + } + argb += argb_stride; + alpha += alpha_stride; + } + // Combine the eight alpha 'and' into a 8-bit mask. + alpha_and &= _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas, all_0xff)); + return (alpha_and == 0xff); +} + +//------------------------------------------------------------------------------ +// Non-dither premultiplied modes + +#define MULTIPLIER(a) ((a) * 0x8081) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) + +// We can't use a 'const int' for the SHUFFLE value, because it has to be an +// immediate in the _mm_shufflexx_epi16() instruction. We really need a macro. +// We use: v / 255 = (v * 0x8081) >> 23, where v = alpha * {r,g,b} is a 16bit +// value. +#define APPLY_ALPHA(RGBX, SHUFFLE) do { \ + const __m128i argb0 = _mm_loadu_si128((const __m128i*)&(RGBX)); \ + const __m128i argb1_lo = _mm_unpacklo_epi8(argb0, zero); \ + const __m128i argb1_hi = _mm_unpackhi_epi8(argb0, zero); \ + const __m128i alpha0_lo = _mm_or_si128(argb1_lo, kMask); \ + const __m128i alpha0_hi = _mm_or_si128(argb1_hi, kMask); \ + const __m128i alpha1_lo = _mm_shufflelo_epi16(alpha0_lo, SHUFFLE); \ + const __m128i alpha1_hi = _mm_shufflelo_epi16(alpha0_hi, SHUFFLE); \ + const __m128i alpha2_lo = _mm_shufflehi_epi16(alpha1_lo, SHUFFLE); \ + const __m128i alpha2_hi = _mm_shufflehi_epi16(alpha1_hi, SHUFFLE); \ + /* alpha2 = [ff a0 a0 a0][ff a1 a1 a1] */ \ + const __m128i A0_lo = _mm_mullo_epi16(alpha2_lo, argb1_lo); \ + const __m128i A0_hi = _mm_mullo_epi16(alpha2_hi, argb1_hi); \ + const __m128i A1_lo = _mm_mulhi_epu16(A0_lo, kMult); \ + const __m128i A1_hi = _mm_mulhi_epu16(A0_hi, kMult); \ + const __m128i A2_lo = _mm_srli_epi16(A1_lo, 7); \ + const __m128i A2_hi = _mm_srli_epi16(A1_hi, 7); \ + const __m128i A3 = _mm_packus_epi16(A2_lo, A2_hi); \ + _mm_storeu_si128((__m128i*)&(RGBX), A3); \ +} while (0) + +static void ApplyAlphaMultiply_SSE2(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + const __m128i zero = _mm_setzero_si128(); + const __m128i kMult = _mm_set1_epi16(0x8081u); + const __m128i kMask = _mm_set_epi16(0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0); + const int kSpan = 4; + while (h-- > 0) { + uint32_t* const rgbx = (uint32_t*)rgba; + int i; + if (!alpha_first) { + for (i = 0; i + kSpan <= w; i += kSpan) { + APPLY_ALPHA(rgbx[i], _MM_SHUFFLE(2, 3, 3, 3)); + } + } else { + for (i = 0; i + kSpan <= w; i += kSpan) { + APPLY_ALPHA(rgbx[i], _MM_SHUFFLE(0, 0, 0, 1)); + } + } + // Finish with left-overs. + for (; i < w; ++i) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#undef MULTIPLIER +#undef PREMULTIPLY + +// ----------------------------------------------------------------------------- +// Apply alpha value to rows + +static void MultARGBRow_SSE2(uint32_t* const ptr, int width, int inverse) { + int x = 0; + if (!inverse) { + const int kSpan = 2; + const __m128i zero = _mm_setzero_si128(); + const __m128i k128 = _mm_set1_epi16(128); + const __m128i kMult = _mm_set1_epi16(0x0101); + const __m128i kMask = _mm_set_epi16(0, 0xff, 0, 0, 0, 0xff, 0, 0); + for (x = 0; x + kSpan <= width; x += kSpan) { + // To compute 'result = (int)(a * x / 255. + .5)', we use: + // tmp = a * v + 128, result = (tmp * 0x0101u) >> 16 + const __m128i A0 = _mm_loadl_epi64((const __m128i*)&ptr[x]); + const __m128i A1 = _mm_unpacklo_epi8(A0, zero); + const __m128i A2 = _mm_or_si128(A1, kMask); + const __m128i A3 = _mm_shufflelo_epi16(A2, _MM_SHUFFLE(2, 3, 3, 3)); + const __m128i A4 = _mm_shufflehi_epi16(A3, _MM_SHUFFLE(2, 3, 3, 3)); + // here, A4 = [ff a0 a0 a0][ff a1 a1 a1] + const __m128i A5 = _mm_mullo_epi16(A4, A1); + const __m128i A6 = _mm_add_epi16(A5, k128); + const __m128i A7 = _mm_mulhi_epu16(A6, kMult); + const __m128i A10 = _mm_packus_epi16(A7, zero); + _mm_storel_epi64((__m128i*)&ptr[x], A10); + } + } + width -= x; + if (width > 0) WebPMultARGBRowC(ptr + x, width, inverse); +} + +static void MultRow_SSE2(uint8_t* const ptr, const uint8_t* const alpha, + int width, int inverse) { + int x = 0; + if (!inverse) { + const __m128i zero = _mm_setzero_si128(); + const __m128i k128 = _mm_set1_epi16(128); + const __m128i kMult = _mm_set1_epi16(0x0101); + for (x = 0; x + 8 <= width; x += 8) { + const __m128i v0 = _mm_loadl_epi64((__m128i*)&ptr[x]); + const __m128i a0 = _mm_loadl_epi64((const __m128i*)&alpha[x]); + const __m128i v1 = _mm_unpacklo_epi8(v0, zero); + const __m128i a1 = _mm_unpacklo_epi8(a0, zero); + const __m128i v2 = _mm_mullo_epi16(v1, a1); + const __m128i v3 = _mm_add_epi16(v2, k128); + const __m128i v4 = _mm_mulhi_epu16(v3, kMult); + const __m128i v5 = _mm_packus_epi16(v4, zero); + _mm_storel_epi64((__m128i*)&ptr[x], v5); + } + } + width -= x; + if (width > 0) WebPMultRowC(ptr + x, alpha + x, width, inverse); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitAlphaProcessingSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingSSE2(void) { + WebPMultARGBRow = MultARGBRow_SSE2; + WebPMultRow = MultRow_SSE2; + WebPApplyAlphaMultiply = ApplyAlphaMultiply_SSE2; + WebPDispatchAlpha = DispatchAlpha; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen; + WebPExtractAlpha = ExtractAlpha; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/dsp/alpha_processing_sse41.c b/3rdparty/libwebp/dsp/alpha_processing_sse41.c new file mode 100644 index 0000000000..986fde94ed --- /dev/null +++ b/3rdparty/libwebp/dsp/alpha_processing_sse41.c @@ -0,0 +1,92 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel, SSE4.1 variant. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE41) + +#include + +//------------------------------------------------------------------------------ + +static int ExtractAlpha(const uint8_t* argb, int argb_stride, + int width, int height, + uint8_t* alpha, int alpha_stride) { + // alpha_and stores an 'and' operation of all the alpha[] values. The final + // value is not 0xff if any of the alpha[] is not equal to 0xff. + uint32_t alpha_and = 0xff; + int i, j; + const __m128i all_0xff = _mm_set1_epi32(~0u); + __m128i all_alphas = all_0xff; + + // We must be able to access 3 extra bytes after the last written byte + // 'src[4 * width - 4]', because we don't know if alpha is the first or the + // last byte of the quadruplet. + const int limit = (width - 1) & ~15; + const __m128i kCstAlpha0 = _mm_set_epi8(-1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 12, 8, 4, 0); + const __m128i kCstAlpha1 = _mm_set_epi8(-1, -1, -1, -1, -1, -1, -1, -1, + 12, 8, 4, 0, -1, -1, -1, -1); + const __m128i kCstAlpha2 = _mm_set_epi8(-1, -1, -1, -1, 12, 8, 4, 0, + -1, -1, -1, -1, -1, -1, -1, -1); + const __m128i kCstAlpha3 = _mm_set_epi8(12, 8, 4, 0, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1); + for (j = 0; j < height; ++j) { + const __m128i* src = (const __m128i*)argb; + for (i = 0; i < limit; i += 16) { + // load 64 argb bytes + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i a2 = _mm_loadu_si128(src + 2); + const __m128i a3 = _mm_loadu_si128(src + 3); + const __m128i b0 = _mm_shuffle_epi8(a0, kCstAlpha0); + const __m128i b1 = _mm_shuffle_epi8(a1, kCstAlpha1); + const __m128i b2 = _mm_shuffle_epi8(a2, kCstAlpha2); + const __m128i b3 = _mm_shuffle_epi8(a3, kCstAlpha3); + const __m128i c0 = _mm_or_si128(b0, b1); + const __m128i c1 = _mm_or_si128(b2, b3); + const __m128i d0 = _mm_or_si128(c0, c1); + // store + _mm_storeu_si128((__m128i*)&alpha[i], d0); + // accumulate sixteen alpha 'and' in parallel + all_alphas = _mm_and_si128(all_alphas, d0); + src += 4; + } + for (; i < width; ++i) { + const uint32_t alpha_value = argb[4 * i]; + alpha[i] = alpha_value; + alpha_and &= alpha_value; + } + argb += argb_stride; + alpha += alpha_stride; + } + // Combine the sixteen alpha 'and' into an 8-bit mask. + alpha_and |= 0xff00u; // pretend the upper bits [8..15] were tested ok. + alpha_and &= _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas, all_0xff)); + return (alpha_and == 0xffffu); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitAlphaProcessingSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingSSE41(void) { + WebPExtractAlpha = ExtractAlpha; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/3rdparty/libwebp/dsp/argb.c b/3rdparty/libwebp/dsp/argb.c new file mode 100644 index 0000000000..cc1f9a96c3 --- /dev/null +++ b/3rdparty/libwebp/dsp/argb.c @@ -0,0 +1,68 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// ARGB making functions. +// +// Author: Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +static WEBP_INLINE uint32_t MakeARGB32(int a, int r, int g, int b) { + return (((uint32_t)a << 24) | (r << 16) | (g << 8) | b); +} + +static void PackARGB(const uint8_t* a, const uint8_t* r, const uint8_t* g, + const uint8_t* b, int len, uint32_t* out) { + int i; + for (i = 0; i < len; ++i) { + out[i] = MakeARGB32(a[4 * i], r[4 * i], g[4 * i], b[4 * i]); + } +} + +static void PackRGB(const uint8_t* r, const uint8_t* g, const uint8_t* b, + int len, int step, uint32_t* out) { + int i, offset = 0; + for (i = 0; i < len; ++i) { + out[i] = MakeARGB32(0xff, r[offset], g[offset], b[offset]); + offset += step; + } +} + +void (*VP8PackARGB)(const uint8_t*, const uint8_t*, const uint8_t*, + const uint8_t*, int, uint32_t*); +void (*VP8PackRGB)(const uint8_t*, const uint8_t*, const uint8_t*, + int, int, uint32_t*); + +extern void VP8EncDspARGBInitMIPSdspR2(void); +extern void VP8EncDspARGBInitSSE2(void); + +static volatile VP8CPUInfo argb_last_cpuinfo_used = + (VP8CPUInfo)&argb_last_cpuinfo_used; + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspARGBInit(void) { + if (argb_last_cpuinfo_used == VP8GetCPUInfo) return; + + VP8PackARGB = PackARGB; + VP8PackRGB = PackRGB; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8EncDspARGBInitSSE2(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8EncDspARGBInitMIPSdspR2(); + } +#endif + } + argb_last_cpuinfo_used = VP8GetCPUInfo; +} diff --git a/3rdparty/libwebp/dsp/argb_mips_dsp_r2.c b/3rdparty/libwebp/dsp/argb_mips_dsp_r2.c new file mode 100644 index 0000000000..af65acb8ff --- /dev/null +++ b/3rdparty/libwebp/dsp/argb_mips_dsp_r2.c @@ -0,0 +1,110 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// ARGB making functions (mips version). +// +// Author: Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +static void PackARGB(const uint8_t* a, const uint8_t* r, const uint8_t* g, + const uint8_t* b, int len, uint32_t* out) { + int temp0, temp1, temp2, temp3, offset; + const int rest = len & 1; + const uint32_t* const loop_end = out + len - rest; + const int step = 4; + __asm__ volatile ( + "xor %[offset], %[offset], %[offset] \n\t" + "beq %[loop_end], %[out], 0f \n\t" + "2: \n\t" + "lbux %[temp0], %[offset](%[a]) \n\t" + "lbux %[temp1], %[offset](%[r]) \n\t" + "lbux %[temp2], %[offset](%[g]) \n\t" + "lbux %[temp3], %[offset](%[b]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "addiu %[out], %[out], 4 \n\t" + "precr.qb.ph %[temp0], %[temp1], %[temp3] \n\t" + "sw %[temp0], -4(%[out]) \n\t" + "addu %[offset], %[offset], %[step] \n\t" + "bne %[loop_end], %[out], 2b \n\t" + "0: \n\t" + "beq %[rest], $zero, 1f \n\t" + "lbux %[temp0], %[offset](%[a]) \n\t" + "lbux %[temp1], %[offset](%[r]) \n\t" + "lbux %[temp2], %[offset](%[g]) \n\t" + "lbux %[temp3], %[offset](%[b]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "precr.qb.ph %[temp0], %[temp1], %[temp3] \n\t" + "sw %[temp0], 0(%[out]) \n\t" + "1: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [offset]"=&r"(offset), [out]"+&r"(out) + : [a]"r"(a), [r]"r"(r), [g]"r"(g), [b]"r"(b), [step]"r"(step), + [loop_end]"r"(loop_end), [rest]"r"(rest) + : "memory" + ); +} + +static void PackRGB(const uint8_t* r, const uint8_t* g, const uint8_t* b, + int len, int step, uint32_t* out) { + int temp0, temp1, temp2, offset; + const int rest = len & 1; + const int a = 0xff; + const uint32_t* const loop_end = out + len - rest; + __asm__ volatile ( + "xor %[offset], %[offset], %[offset] \n\t" + "beq %[loop_end], %[out], 0f \n\t" + "2: \n\t" + "lbux %[temp0], %[offset](%[r]) \n\t" + "lbux %[temp1], %[offset](%[g]) \n\t" + "lbux %[temp2], %[offset](%[b]) \n\t" + "ins %[temp0], %[a], 16, 16 \n\t" + "ins %[temp2], %[temp1], 16, 16 \n\t" + "addiu %[out], %[out], 4 \n\t" + "precr.qb.ph %[temp0], %[temp0], %[temp2] \n\t" + "sw %[temp0], -4(%[out]) \n\t" + "addu %[offset], %[offset], %[step] \n\t" + "bne %[loop_end], %[out], 2b \n\t" + "0: \n\t" + "beq %[rest], $zero, 1f \n\t" + "lbux %[temp0], %[offset](%[r]) \n\t" + "lbux %[temp1], %[offset](%[g]) \n\t" + "lbux %[temp2], %[offset](%[b]) \n\t" + "ins %[temp0], %[a], 16, 16 \n\t" + "ins %[temp2], %[temp1], 16, 16 \n\t" + "precr.qb.ph %[temp0], %[temp0], %[temp2] \n\t" + "sw %[temp0], 0(%[out]) \n\t" + "1: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [offset]"=&r"(offset), [out]"+&r"(out) + : [a]"r"(a), [r]"r"(r), [g]"r"(g), [b]"r"(b), [step]"r"(step), + [loop_end]"r"(loop_end), [rest]"r"(rest) + : "memory" + ); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspARGBInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspARGBInitMIPSdspR2(void) { + VP8PackARGB = PackARGB; + VP8PackRGB = PackRGB; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8EncDspARGBInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/argb_sse2.c b/3rdparty/libwebp/dsp/argb_sse2.c new file mode 100644 index 0000000000..afcb1957e7 --- /dev/null +++ b/3rdparty/libwebp/dsp/argb_sse2.c @@ -0,0 +1,67 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// ARGB making functions (SSE2 version). +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE2) + +#include +#include +#include + +static WEBP_INLINE uint32_t MakeARGB32(int a, int r, int g, int b) { + return (((uint32_t)a << 24) | (r << 16) | (g << 8) | b); +} + +static void PackARGB(const uint8_t* a, const uint8_t* r, const uint8_t* g, + const uint8_t* b, int len, uint32_t* out) { + if (g == r + 1) { // RGBA input order. Need to swap R and B. + int i = 0; + const int len_max = len & ~3; // max length processed in main loop + const __m128i red_blue_mask = _mm_set1_epi32(0x00ff00ffu); + assert(b == r + 2); + assert(a == r + 3); + for (; i < len_max; i += 4) { + const __m128i A = _mm_loadu_si128((const __m128i*)(r + 4 * i)); + const __m128i B = _mm_and_si128(A, red_blue_mask); // R 0 B 0 + const __m128i C = _mm_andnot_si128(red_blue_mask, A); // 0 G 0 A + const __m128i D = _mm_shufflelo_epi16(B, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i E = _mm_shufflehi_epi16(D, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i F = _mm_or_si128(E, C); + _mm_storeu_si128((__m128i*)(out + i), F); + } + for (; i < len; ++i) { + out[i] = MakeARGB32(a[4 * i], r[4 * i], g[4 * i], b[4 * i]); + } + } else { + assert(g == b + 1); + assert(r == b + 2); + assert(a == b + 3); + memcpy(out, b, len * 4); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspARGBInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspARGBInitSSE2(void) { + VP8PackARGB = PackARGB; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8EncDspARGBInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/dsp/common_sse2.h b/3rdparty/libwebp/dsp/common_sse2.h new file mode 100644 index 0000000000..995d7cf4ea --- /dev/null +++ b/3rdparty/libwebp/dsp/common_sse2.h @@ -0,0 +1,194 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 code common to several files. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_COMMON_SSE2_H_ +#define WEBP_DSP_COMMON_SSE2_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(WEBP_USE_SSE2) + +#include + +//------------------------------------------------------------------------------ +// Quite useful macro for debugging. Left here for convenience. + +#if 0 +#include +static WEBP_INLINE void PrintReg(const __m128i r, const char* const name, + int size) { + int n; + union { + __m128i r; + uint8_t i8[16]; + uint16_t i16[8]; + uint32_t i32[4]; + uint64_t i64[2]; + } tmp; + tmp.r = r; + fprintf(stderr, "%s\t: ", name); + if (size == 8) { + for (n = 0; n < 16; ++n) fprintf(stderr, "%.2x ", tmp.i8[n]); + } else if (size == 16) { + for (n = 0; n < 8; ++n) fprintf(stderr, "%.4x ", tmp.i16[n]); + } else if (size == 32) { + for (n = 0; n < 4; ++n) fprintf(stderr, "%.8x ", tmp.i32[n]); + } else { + for (n = 0; n < 2; ++n) fprintf(stderr, "%.16lx ", tmp.i64[n]); + } + fprintf(stderr, "\n"); +} +#endif + +//------------------------------------------------------------------------------ +// Math functions. + +// Return the sum of all the 8b in the register. +static WEBP_INLINE int VP8HorizontalAdd8b(const __m128i* const a) { + const __m128i zero = _mm_setzero_si128(); + const __m128i sad8x2 = _mm_sad_epu8(*a, zero); + // sum the two sads: sad8x2[0:1] + sad8x2[8:9] + const __m128i sum = _mm_add_epi32(sad8x2, _mm_shuffle_epi32(sad8x2, 2)); + return _mm_cvtsi128_si32(sum); +} + +// Transpose two 4x4 16b matrices horizontally stored in registers. +static WEBP_INLINE void VP8Transpose_2_4x4_16b( + const __m128i* const in0, const __m128i* const in1, + const __m128i* const in2, const __m128i* const in3, __m128i* const out0, + __m128i* const out1, __m128i* const out2, __m128i* const out3) { + // Transpose the two 4x4. + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + const __m128i transpose0_0 = _mm_unpacklo_epi16(*in0, *in1); + const __m128i transpose0_1 = _mm_unpacklo_epi16(*in2, *in3); + const __m128i transpose0_2 = _mm_unpackhi_epi16(*in0, *in1); + const __m128i transpose0_3 = _mm_unpackhi_epi16(*in2, *in3); + // a00 a10 a01 a11 a02 a12 a03 a13 + // a20 a30 a21 a31 a22 a32 a23 a33 + // b00 b10 b01 b11 b02 b12 b03 b13 + // b20 b30 b21 b31 b22 b32 b23 b33 + const __m128i transpose1_0 = _mm_unpacklo_epi32(transpose0_0, transpose0_1); + const __m128i transpose1_1 = _mm_unpacklo_epi32(transpose0_2, transpose0_3); + const __m128i transpose1_2 = _mm_unpackhi_epi32(transpose0_0, transpose0_1); + const __m128i transpose1_3 = _mm_unpackhi_epi32(transpose0_2, transpose0_3); + // a00 a10 a20 a30 a01 a11 a21 a31 + // b00 b10 b20 b30 b01 b11 b21 b31 + // a02 a12 a22 a32 a03 a13 a23 a33 + // b02 b12 a22 b32 b03 b13 b23 b33 + *out0 = _mm_unpacklo_epi64(transpose1_0, transpose1_1); + *out1 = _mm_unpackhi_epi64(transpose1_0, transpose1_1); + *out2 = _mm_unpacklo_epi64(transpose1_2, transpose1_3); + *out3 = _mm_unpackhi_epi64(transpose1_2, transpose1_3); + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 +} + +//------------------------------------------------------------------------------ +// Channel mixing. + +// Function used several times in VP8PlanarTo24b. +// It samples the in buffer as follows: one every two unsigned char is stored +// at the beginning of the buffer, while the other half is stored at the end. +#define VP8PlanarTo24bHelper(IN, OUT) \ + do { \ + const __m128i v_mask = _mm_set1_epi16(0x00ff); \ + /* Take one every two upper 8b values.*/ \ + (OUT##0) = _mm_packus_epi16(_mm_and_si128((IN##0), v_mask), \ + _mm_and_si128((IN##1), v_mask)); \ + (OUT##1) = _mm_packus_epi16(_mm_and_si128((IN##2), v_mask), \ + _mm_and_si128((IN##3), v_mask)); \ + (OUT##2) = _mm_packus_epi16(_mm_and_si128((IN##4), v_mask), \ + _mm_and_si128((IN##5), v_mask)); \ + /* Take one every two lower 8b values.*/ \ + (OUT##3) = _mm_packus_epi16(_mm_srli_epi16((IN##0), 8), \ + _mm_srli_epi16((IN##1), 8)); \ + (OUT##4) = _mm_packus_epi16(_mm_srli_epi16((IN##2), 8), \ + _mm_srli_epi16((IN##3), 8)); \ + (OUT##5) = _mm_packus_epi16(_mm_srli_epi16((IN##4), 8), \ + _mm_srli_epi16((IN##5), 8)); \ + } while (0) + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void VP8PlanarTo24b(__m128i* const in0, __m128i* const in1, + __m128i* const in2, __m128i* const in3, + __m128i* const in4, __m128i* const in5) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + __m128i tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + VP8PlanarTo24bHelper(*in, tmp); + VP8PlanarTo24bHelper(tmp, *in); + VP8PlanarTo24bHelper(*in, tmp); + // We need to do it two more times than the example as we have sixteen bytes. + { + __m128i out0, out1, out2, out3, out4, out5; + VP8PlanarTo24bHelper(tmp, out); + VP8PlanarTo24bHelper(out, *in); + } +} + +#undef VP8PlanarTo24bHelper + +// Convert four packed four-channel buffers like argbargbargbargb... into the +// split channels aaaaa ... rrrr ... gggg .... bbbbb ...... +static WEBP_INLINE void VP8L32bToPlanar(__m128i* const in0, + __m128i* const in1, + __m128i* const in2, + __m128i* const in3) { + // Column-wise transpose. + const __m128i A0 = _mm_unpacklo_epi8(*in0, *in1); + const __m128i A1 = _mm_unpackhi_epi8(*in0, *in1); + const __m128i A2 = _mm_unpacklo_epi8(*in2, *in3); + const __m128i A3 = _mm_unpackhi_epi8(*in2, *in3); + const __m128i B0 = _mm_unpacklo_epi8(A0, A1); + const __m128i B1 = _mm_unpackhi_epi8(A0, A1); + const __m128i B2 = _mm_unpacklo_epi8(A2, A3); + const __m128i B3 = _mm_unpackhi_epi8(A2, A3); + // C0 = g7 g6 ... g1 g0 | b7 b6 ... b1 b0 + // C1 = a7 a6 ... a1 a0 | r7 r6 ... r1 r0 + const __m128i C0 = _mm_unpacklo_epi8(B0, B1); + const __m128i C1 = _mm_unpackhi_epi8(B0, B1); + const __m128i C2 = _mm_unpacklo_epi8(B2, B3); + const __m128i C3 = _mm_unpackhi_epi8(B2, B3); + // Gather the channels. + *in0 = _mm_unpackhi_epi64(C1, C3); + *in1 = _mm_unpacklo_epi64(C1, C3); + *in2 = _mm_unpackhi_epi64(C0, C2); + *in3 = _mm_unpacklo_epi64(C0, C2); +} + +#endif // WEBP_USE_SSE2 + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_COMMON_SSE2_H_ diff --git a/3rdparty/libwebp/enc/cost.c b/3rdparty/libwebp/dsp/cost.c similarity index 61% rename from 3rdparty/libwebp/enc/cost.c rename to 3rdparty/libwebp/dsp/cost.c index d4916d7cd2..58ddea7248 100644 --- a/3rdparty/libwebp/enc/cost.c +++ b/3rdparty/libwebp/dsp/cost.c @@ -1,4 +1,4 @@ -// Copyright 2011 Google Inc. All Rights Reserved. +// Copyright 2014 Google Inc. All Rights Reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the COPYING file in the root of the source @@ -7,15 +7,10 @@ // be found in the AUTHORS file in the root of the source tree. // ----------------------------------------------------------------------------- // -// Cost tables for level and modes -// // Author: Skal (pascal.massimino@gmail.com) -#include "./cost.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./dsp.h" +#include "../enc/cost_enc.h" //------------------------------------------------------------------------------ // Boolean-cost cost table @@ -52,29 +47,6 @@ const uint16_t VP8EntropyCost[256] = { //------------------------------------------------------------------------------ // Level cost tables -// For each given level, the following table gives the pattern of contexts to -// use for coding it (in [][0]) as well as the bit value to use for each -// context (in [][1]). -const uint16_t VP8LevelCodes[MAX_VARIABLE_LEVEL][2] = { - {0x001, 0x000}, {0x007, 0x001}, {0x00f, 0x005}, - {0x00f, 0x00d}, {0x033, 0x003}, {0x033, 0x003}, {0x033, 0x023}, - {0x033, 0x023}, {0x033, 0x023}, {0x033, 0x023}, {0x0d3, 0x013}, - {0x0d3, 0x013}, {0x0d3, 0x013}, {0x0d3, 0x013}, {0x0d3, 0x013}, - {0x0d3, 0x013}, {0x0d3, 0x013}, {0x0d3, 0x013}, {0x0d3, 0x093}, - {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, - {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, - {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, - {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, {0x153, 0x053}, - {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, - {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, - {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, - {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, - {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, - {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, - {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, - {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x153} -}; - // fixed costs for coding levels, deduce from the coding tree. // This is only the part that doesn't depend on the probability state. const uint16_t VP8LevelFixedCosts[MAX_LEVEL + 1] = { @@ -336,161 +308,105 @@ const uint16_t VP8LevelFixedCosts[MAX_LEVEL + 1] = { 7694, 7703, 7709, 7729, 7735, 7744, 7750, 7761 }; -static int VariableLevelCost(int level, const uint8_t probas[NUM_PROBAS]) { - int pattern = VP8LevelCodes[level - 1][0]; - int bits = VP8LevelCodes[level - 1][1]; - int cost = 0; - int i; - for (i = 2; pattern; ++i) { - if (pattern & 1) { - cost += VP8BitCost(bits & 1, probas[i]); +//------------------------------------------------------------------------------ +// Tables for level coding + +const uint8_t VP8EncBands[16 + 1] = { + 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, + 0 // sentinel +}; + +//------------------------------------------------------------------------------ +// Mode costs + +static int GetResidualCost(int ctx0, const VP8Residual* const res) { + int n = res->first; + // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 + const int p0 = res->prob[n][ctx0][0]; + CostArrayPtr const costs = res->costs; + const uint16_t* t = costs[n][ctx0]; + // bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0; + + if (res->last < 0) { + return VP8BitCost(0, p0); + } + for (; n < res->last; ++n) { + const int v = abs(res->coeffs[n]); + const int ctx = (v >= 2) ? 2 : v; + cost += VP8LevelCost(t, v); + t = costs[n + 1][ctx]; + } + // Last coefficient is always non-zero + { + const int v = abs(res->coeffs[n]); + assert(v != 0); + cost += VP8LevelCost(t, v); + if (n < 15) { + const int b = VP8EncBands[n + 1]; + const int ctx = (v == 1) ? 1 : 2; + const int last_p0 = res->prob[b][ctx][0]; + cost += VP8BitCost(0, last_p0); } - bits >>= 1; - pattern >>= 1; } return cost; } -//------------------------------------------------------------------------------ -// Pre-calc level costs once for all - -void VP8CalculateLevelCosts(VP8Proba* const proba) { - int ctype, band, ctx; - - if (!proba->dirty_) return; // nothing to do. - - for (ctype = 0; ctype < NUM_TYPES; ++ctype) { - for (band = 0; band < NUM_BANDS; ++band) { - for (ctx = 0; ctx < NUM_CTX; ++ctx) { - const uint8_t* const p = proba->coeffs_[ctype][band][ctx]; - uint16_t* const table = proba->level_cost_[ctype][band][ctx]; - const int cost_base = VP8BitCost(1, p[1]); - int v; - table[0] = VP8BitCost(0, p[1]); - for (v = 1; v <= MAX_VARIABLE_LEVEL; ++v) { - table[v] = cost_base + VariableLevelCost(v, p); - } - // Starting at level 67 and up, the variable part of the cost is - // actually constant. - } +static void SetResidualCoeffs(const int16_t* const coeffs, + VP8Residual* const res) { + int n; + res->last = -1; + assert(res->first == 0 || coeffs[0] == 0); + for (n = 15; n >= 0; --n) { + if (coeffs[n]) { + res->last = n; + break; } } - proba->dirty_ = 0; + res->coeffs = coeffs; } //------------------------------------------------------------------------------ -// Mode cost tables. +// init function -// These are the fixed probabilities (in the coding trees) turned into bit-cost -// by calling VP8BitCost(). -const uint16_t VP8FixedCostsUV[4] = { 302, 984, 439, 642 }; -// note: these values include the fixed VP8BitCost(1, 145) mode selection cost. -const uint16_t VP8FixedCostsI16[4] = { 663, 919, 872, 919 }; -const uint16_t VP8FixedCostsI4[NUM_BMODES][NUM_BMODES][NUM_BMODES] = { - { { 251, 1362, 1934, 2085, 2314, 2230, 1839, 1988, 2437, 2348 }, - { 403, 680, 1507, 1519, 2060, 2005, 1992, 1914, 1924, 1733 }, - { 353, 1121, 973, 1895, 2060, 1787, 1671, 1516, 2012, 1868 }, - { 770, 852, 1581, 632, 1393, 1780, 1823, 1936, 1074, 1218 }, - { 510, 1270, 1467, 1319, 847, 1279, 1792, 2094, 1080, 1353 }, - { 488, 1322, 918, 1573, 1300, 883, 1814, 1752, 1756, 1502 }, - { 425, 992, 1820, 1514, 1843, 2440, 937, 1771, 1924, 1129 }, - { 363, 1248, 1257, 1970, 2194, 2385, 1569, 953, 1951, 1601 }, - { 723, 1257, 1631, 964, 963, 1508, 1697, 1824, 671, 1418 }, - { 635, 1038, 1573, 930, 1673, 1413, 1410, 1687, 1410, 749 } }, - { { 451, 613, 1345, 1702, 1870, 1716, 1728, 1766, 2190, 2310 }, - { 678, 453, 1171, 1443, 1925, 1831, 2045, 1781, 1887, 1602 }, - { 711, 666, 674, 1718, 1910, 1493, 1775, 1193, 2325, 2325 }, - { 883, 854, 1583, 542, 1800, 1878, 1664, 2149, 1207, 1087 }, - { 669, 994, 1248, 1122, 949, 1179, 1376, 1729, 1070, 1244 }, - { 715, 1026, 715, 1350, 1430, 930, 1717, 1296, 1479, 1479 }, - { 544, 841, 1656, 1450, 2094, 3883, 1010, 1759, 2076, 809 }, - { 610, 855, 957, 1553, 2067, 1561, 1704, 824, 2066, 1226 }, - { 833, 960, 1416, 819, 1277, 1619, 1501, 1617, 757, 1182 }, - { 711, 964, 1252, 879, 1441, 1828, 1508, 1636, 1594, 734 } }, - { { 605, 764, 734, 1713, 1747, 1192, 1819, 1353, 1877, 2392 }, - { 866, 641, 586, 1622, 2072, 1431, 1888, 1346, 2189, 1764 }, - { 901, 851, 456, 2165, 2281, 1405, 1739, 1193, 2183, 2443 }, - { 770, 1045, 952, 1078, 1342, 1191, 1436, 1063, 1303, 995 }, - { 901, 1086, 727, 1170, 884, 1105, 1267, 1401, 1739, 1337 }, - { 951, 1162, 595, 1488, 1388, 703, 1790, 1366, 2057, 1724 }, - { 534, 986, 1273, 1987, 3273, 1485, 1024, 1399, 1583, 866 }, - { 699, 1182, 695, 1978, 1726, 1986, 1326, 714, 1750, 1672 }, - { 951, 1217, 1209, 920, 1062, 1441, 1548, 999, 952, 932 }, - { 733, 1284, 784, 1256, 1557, 1098, 1257, 1357, 1414, 908 } }, - { { 316, 1075, 1653, 1220, 2145, 2051, 1730, 2131, 1884, 1790 }, - { 745, 516, 1404, 894, 1599, 2375, 2013, 2105, 1475, 1381 }, - { 516, 729, 1088, 1319, 1637, 3426, 1636, 1275, 1531, 1453 }, - { 894, 943, 2138, 468, 1704, 2259, 2069, 1763, 1266, 1158 }, - { 605, 1025, 1235, 871, 1170, 1767, 1493, 1500, 1104, 1258 }, - { 739, 826, 1207, 1151, 1412, 846, 1305, 2726, 1014, 1569 }, - { 558, 825, 1820, 1398, 3344, 1556, 1218, 1550, 1228, 878 }, - { 429, 951, 1089, 1816, 3861, 3861, 1556, 969, 1568, 1828 }, - { 883, 961, 1752, 769, 1468, 1810, 2081, 2346, 613, 1298 }, - { 803, 895, 1372, 641, 1303, 1708, 1686, 1700, 1306, 1033 } }, - { { 439, 1267, 1270, 1579, 963, 1193, 1723, 1729, 1198, 1993 }, - { 705, 725, 1029, 1153, 1176, 1103, 1821, 1567, 1259, 1574 }, - { 723, 859, 802, 1253, 972, 1202, 1407, 1665, 1520, 1674 }, - { 894, 960, 1254, 887, 1052, 1607, 1344, 1349, 865, 1150 }, - { 833, 1312, 1337, 1205, 572, 1288, 1414, 1529, 1088, 1430 }, - { 842, 1279, 1068, 1861, 862, 688, 1861, 1630, 1039, 1381 }, - { 766, 938, 1279, 1546, 3338, 1550, 1031, 1542, 1288, 640 }, - { 715, 1090, 835, 1609, 1100, 1100, 1603, 1019, 1102, 1617 }, - { 894, 1813, 1500, 1188, 789, 1194, 1491, 1919, 617, 1333 }, - { 610, 1076, 1644, 1281, 1283, 975, 1179, 1688, 1434, 889 } }, - { { 544, 971, 1146, 1849, 1221, 740, 1857, 1621, 1683, 2430 }, - { 723, 705, 961, 1371, 1426, 821, 2081, 2079, 1839, 1380 }, - { 783, 857, 703, 2145, 1419, 814, 1791, 1310, 1609, 2206 }, - { 997, 1000, 1153, 792, 1229, 1162, 1810, 1418, 942, 979 }, - { 901, 1226, 883, 1289, 793, 715, 1904, 1649, 1319, 3108 }, - { 979, 1478, 782, 2216, 1454, 455, 3092, 1591, 1997, 1664 }, - { 663, 1110, 1504, 1114, 1522, 3311, 676, 1522, 1530, 1024 }, - { 605, 1138, 1153, 1314, 1569, 1315, 1157, 804, 1574, 1320 }, - { 770, 1216, 1218, 1227, 869, 1384, 1232, 1375, 834, 1239 }, - { 775, 1007, 843, 1216, 1225, 1074, 2527, 1479, 1149, 975 } }, - { { 477, 817, 1309, 1439, 1708, 1454, 1159, 1241, 1945, 1672 }, - { 577, 796, 1112, 1271, 1618, 1458, 1087, 1345, 1831, 1265 }, - { 663, 776, 753, 1940, 1690, 1690, 1227, 1097, 3149, 1361 }, - { 766, 1299, 1744, 1161, 1565, 1106, 1045, 1230, 1232, 707 }, - { 915, 1026, 1404, 1182, 1184, 851, 1428, 2425, 1043, 789 }, - { 883, 1456, 790, 1082, 1086, 985, 1083, 1484, 1238, 1160 }, - { 507, 1345, 2261, 1995, 1847, 3636, 653, 1761, 2287, 933 }, - { 553, 1193, 1470, 2057, 2059, 2059, 833, 779, 2058, 1263 }, - { 766, 1275, 1515, 1039, 957, 1554, 1286, 1540, 1289, 705 }, - { 499, 1378, 1496, 1385, 1850, 1850, 1044, 2465, 1515, 720 } }, - { { 553, 930, 978, 2077, 1968, 1481, 1457, 761, 1957, 2362 }, - { 694, 864, 905, 1720, 1670, 1621, 1429, 718, 2125, 1477 }, - { 699, 968, 658, 3190, 2024, 1479, 1865, 750, 2060, 2320 }, - { 733, 1308, 1296, 1062, 1576, 1322, 1062, 1112, 1172, 816 }, - { 920, 927, 1052, 939, 947, 1156, 1152, 1073, 3056, 1268 }, - { 723, 1534, 711, 1547, 1294, 892, 1553, 928, 1815, 1561 }, - { 663, 1366, 1583, 2111, 1712, 3501, 522, 1155, 2130, 1133 }, - { 614, 1731, 1188, 2343, 1944, 3733, 1287, 487, 3546, 1758 }, - { 770, 1585, 1312, 826, 884, 2673, 1185, 1006, 1195, 1195 }, - { 758, 1333, 1273, 1023, 1621, 1162, 1351, 833, 1479, 862 } }, - { { 376, 1193, 1446, 1149, 1545, 1577, 1870, 1789, 1175, 1823 }, - { 803, 633, 1136, 1058, 1350, 1323, 1598, 2247, 1072, 1252 }, - { 614, 1048, 943, 981, 1152, 1869, 1461, 1020, 1618, 1618 }, - { 1107, 1085, 1282, 592, 1779, 1933, 1648, 2403, 691, 1246 }, - { 851, 1309, 1223, 1243, 895, 1593, 1792, 2317, 627, 1076 }, - { 770, 1216, 1030, 1125, 921, 981, 1629, 1131, 1049, 1646 }, - { 626, 1469, 1456, 1081, 1489, 3278, 981, 1232, 1498, 733 }, - { 617, 1201, 812, 1220, 1476, 1476, 1478, 970, 1228, 1488 }, - { 1179, 1393, 1540, 999, 1243, 1503, 1916, 1925, 414, 1614 }, - { 943, 1088, 1490, 682, 1112, 1372, 1756, 1505, 966, 966 } }, - { { 322, 1142, 1589, 1396, 2144, 1859, 1359, 1925, 2084, 1518 }, - { 617, 625, 1241, 1234, 2121, 1615, 1524, 1858, 1720, 1004 }, - { 553, 851, 786, 1299, 1452, 1560, 1372, 1561, 1967, 1713 }, - { 770, 977, 1396, 568, 1893, 1639, 1540, 2108, 1430, 1013 }, - { 684, 1120, 1375, 982, 930, 2719, 1638, 1643, 933, 993 }, - { 553, 1103, 996, 1356, 1361, 1005, 1507, 1761, 1184, 1268 }, - { 419, 1247, 1537, 1554, 1817, 3606, 1026, 1666, 1829, 923 }, - { 439, 1139, 1101, 1257, 3710, 1922, 1205, 1040, 1931, 1529 }, - { 979, 935, 1269, 847, 1202, 1286, 1530, 1535, 827, 1036 }, - { 516, 1378, 1569, 1110, 1798, 1798, 1198, 2199, 1543, 712 } }, -}; +VP8GetResidualCostFunc VP8GetResidualCost; +VP8SetResidualCoeffsFunc VP8SetResidualCoeffs; + +extern void VP8EncDspCostInitMIPS32(void); +extern void VP8EncDspCostInitMIPSdspR2(void); +extern void VP8EncDspCostInitSSE2(void); + +static volatile VP8CPUInfo cost_last_cpuinfo_used = + (VP8CPUInfo)&cost_last_cpuinfo_used; + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInit(void) { + if (cost_last_cpuinfo_used == VP8GetCPUInfo) return; + + VP8GetResidualCost = GetResidualCost; + VP8SetResidualCoeffs = SetResidualCoeffs; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + VP8EncDspCostInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8EncDspCostInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8EncDspCostInitSSE2(); + } +#endif + } + + cost_last_cpuinfo_used = VP8GetCPUInfo; +} //------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dsp/cost_mips32.c b/3rdparty/libwebp/dsp/cost_mips32.c new file mode 100644 index 0000000000..3102da877a --- /dev/null +++ b/3rdparty/libwebp/dsp/cost_mips32.c @@ -0,0 +1,154 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS32) + +#include "../enc/cost_enc.h" + +static int GetResidualCost(int ctx0, const VP8Residual* const res) { + int temp0, temp1; + int v_reg, ctx_reg; + int n = res->first; + // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 + int p0 = res->prob[n][ctx0][0]; + CostArrayPtr const costs = res->costs; + const uint16_t* t = costs[n][ctx0]; + // bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0; + const int16_t* res_coeffs = res->coeffs; + const int res_last = res->last; + const int const_max_level = MAX_VARIABLE_LEVEL; + const int const_2 = 2; + const uint16_t** p_costs = &costs[n][0]; + const size_t inc_p_costs = NUM_CTX * sizeof(*p_costs); + + if (res->last < 0) { + return VP8BitCost(0, p0); + } + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "subu %[temp1], %[res_last], %[n] \n\t" + "sll %[temp0], %[n], 1 \n\t" + "blez %[temp1], 2f \n\t" + " addu %[res_coeffs], %[res_coeffs], %[temp0] \n\t" + "1: \n\t" + "lh %[v_reg], 0(%[res_coeffs]) \n\t" + "addiu %[n], %[n], 1 \n\t" + "negu %[temp0], %[v_reg] \n\t" + "slti %[temp1], %[v_reg], 0 \n\t" + "movn %[v_reg], %[temp0], %[temp1] \n\t" + "sltiu %[temp0], %[v_reg], 2 \n\t" + "move %[ctx_reg], %[v_reg] \n\t" + "movz %[ctx_reg], %[const_2], %[temp0] \n\t" + "sll %[temp1], %[v_reg], 1 \n\t" + "addu %[temp1], %[temp1], %[VP8LevelFixedCosts] \n\t" + "lhu %[temp1], 0(%[temp1]) \n\t" + "slt %[temp0], %[v_reg], %[const_max_level] \n\t" + "movz %[v_reg], %[const_max_level], %[temp0] \n\t" + "addu %[cost], %[cost], %[temp1] \n\t" + "sll %[v_reg], %[v_reg], 1 \n\t" + "sll %[ctx_reg], %[ctx_reg], 2 \n\t" + "addu %[v_reg], %[v_reg], %[t] \n\t" + "lhu %[temp0], 0(%[v_reg]) \n\t" + "addu %[p_costs], %[p_costs], %[inc_p_costs] \n\t" + "addu %[t], %[p_costs], %[ctx_reg] \n\t" + "addu %[cost], %[cost], %[temp0] \n\t" + "addiu %[res_coeffs], %[res_coeffs], 2 \n\t" + "bne %[n], %[res_last], 1b \n\t" + " lw %[t], 0(%[t]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [cost]"+&r"(cost), [t]"+&r"(t), [n]"+&r"(n), [v_reg]"=&r"(v_reg), + [ctx_reg]"=&r"(ctx_reg), [p_costs]"+&r"(p_costs), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1), [res_coeffs]"+&r"(res_coeffs) + : [const_2]"r"(const_2), [const_max_level]"r"(const_max_level), + [VP8LevelFixedCosts]"r"(VP8LevelFixedCosts), [res_last]"r"(res_last), + [inc_p_costs]"r"(inc_p_costs) + : "memory" + ); + + // Last coefficient is always non-zero + { + const int v = abs(res->coeffs[n]); + assert(v != 0); + cost += VP8LevelCost(t, v); + if (n < 15) { + const int b = VP8EncBands[n + 1]; + const int ctx = (v == 1) ? 1 : 2; + const int last_p0 = res->prob[b][ctx][0]; + cost += VP8BitCost(0, last_p0); + } + } + return cost; +} + +static void SetResidualCoeffs(const int16_t* const coeffs, + VP8Residual* const res) { + const int16_t* p_coeffs = (int16_t*)coeffs; + int temp0, temp1, temp2, n, n1; + assert(res->first == 0 || coeffs[0] == 0); + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "addiu %[p_coeffs], %[p_coeffs], 28 \n\t" + "li %[n], 15 \n\t" + "li %[temp2], -1 \n\t" + "0: \n\t" + "ulw %[temp0], 0(%[p_coeffs]) \n\t" + "beqz %[temp0], 1f \n\t" +#if defined(WORDS_BIGENDIAN) + " sll %[temp1], %[temp0], 16 \n\t" +#else + " srl %[temp1], %[temp0], 16 \n\t" +#endif + "addiu %[n1], %[n], -1 \n\t" + "movz %[temp0], %[n1], %[temp1] \n\t" + "movn %[temp0], %[n], %[temp1] \n\t" + "j 2f \n\t" + " addiu %[temp2], %[temp0], 0 \n\t" + "1: \n\t" + "addiu %[n], %[n], -2 \n\t" + "bgtz %[n], 0b \n\t" + " addiu %[p_coeffs], %[p_coeffs], -4 \n\t" + "2: \n\t" + ".set pop \n\t" + : [p_coeffs]"+&r"(p_coeffs), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [n]"=&r"(n), [n1]"=&r"(n1) + : + : "memory" + ); + res->last = temp2; + res->coeffs = coeffs; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspCostInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInitMIPS32(void) { + VP8GetResidualCost = GetResidualCost; + VP8SetResidualCoeffs = SetResidualCoeffs; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(VP8EncDspCostInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/3rdparty/libwebp/dsp/cost_mips_dsp_r2.c b/3rdparty/libwebp/dsp/cost_mips_dsp_r2.c new file mode 100644 index 0000000000..6ec8aeb610 --- /dev/null +++ b/3rdparty/libwebp/dsp/cost_mips_dsp_r2.c @@ -0,0 +1,107 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "../enc/cost_enc.h" + +static int GetResidualCost(int ctx0, const VP8Residual* const res) { + int temp0, temp1; + int v_reg, ctx_reg; + int n = res->first; + // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 + int p0 = res->prob[n][ctx0][0]; + CostArrayPtr const costs = res->costs; + const uint16_t* t = costs[n][ctx0]; + // bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0; + const int16_t* res_coeffs = res->coeffs; + const int res_last = res->last; + const int const_max_level = MAX_VARIABLE_LEVEL; + const int const_2 = 2; + const uint16_t** p_costs = &costs[n][0]; + const size_t inc_p_costs = NUM_CTX * sizeof(*p_costs); + + if (res->last < 0) { + return VP8BitCost(0, p0); + } + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "subu %[temp1], %[res_last], %[n] \n\t" + "blez %[temp1], 2f \n\t" + " nop \n\t" + "1: \n\t" + "sll %[temp0], %[n], 1 \n\t" + "lhx %[v_reg], %[temp0](%[res_coeffs]) \n\t" + "addiu %[n], %[n], 1 \n\t" + "absq_s.w %[v_reg], %[v_reg] \n\t" + "sltiu %[temp0], %[v_reg], 2 \n\t" + "move %[ctx_reg], %[v_reg] \n\t" + "movz %[ctx_reg], %[const_2], %[temp0] \n\t" + "sll %[temp1], %[v_reg], 1 \n\t" + "lhx %[temp1], %[temp1](%[VP8LevelFixedCosts]) \n\t" + "slt %[temp0], %[v_reg], %[const_max_level] \n\t" + "movz %[v_reg], %[const_max_level], %[temp0] \n\t" + "addu %[cost], %[cost], %[temp1] \n\t" + "sll %[v_reg], %[v_reg], 1 \n\t" + "sll %[ctx_reg], %[ctx_reg], 2 \n\t" + "lhx %[temp0], %[v_reg](%[t]) \n\t" + "addu %[p_costs], %[p_costs], %[inc_p_costs] \n\t" + "addu %[t], %[p_costs], %[ctx_reg] \n\t" + "addu %[cost], %[cost], %[temp0] \n\t" + "bne %[n], %[res_last], 1b \n\t" + " lw %[t], 0(%[t]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [cost]"+&r"(cost), [t]"+&r"(t), [n]"+&r"(n), [v_reg]"=&r"(v_reg), + [ctx_reg]"=&r"(ctx_reg), [p_costs]"+&r"(p_costs), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1) + : [const_2]"r"(const_2), [const_max_level]"r"(const_max_level), + [VP8LevelFixedCosts]"r"(VP8LevelFixedCosts), [res_last]"r"(res_last), + [res_coeffs]"r"(res_coeffs), [inc_p_costs]"r"(inc_p_costs) + : "memory" + ); + + // Last coefficient is always non-zero + { + const int v = abs(res->coeffs[n]); + assert(v != 0); + cost += VP8LevelCost(t, v); + if (n < 15) { + const int b = VP8EncBands[n + 1]; + const int ctx = (v == 1) ? 1 : 2; + const int last_p0 = res->prob[b][ctx][0]; + cost += VP8BitCost(0, last_p0); + } + } + return cost; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspCostInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInitMIPSdspR2(void) { + VP8GetResidualCost = GetResidualCost; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8EncDspCostInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/cost_sse2.c b/3rdparty/libwebp/dsp/cost_sse2.c new file mode 100644 index 0000000000..421d51fdd5 --- /dev/null +++ b/3rdparty/libwebp/dsp/cost_sse2.c @@ -0,0 +1,119 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 version of cost functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE2) +#include + +#include "../enc/cost_enc.h" +#include "../enc/vp8i_enc.h" +#include "../utils/utils.h" + +//------------------------------------------------------------------------------ + +static void SetResidualCoeffsSSE2(const int16_t* const coeffs, + VP8Residual* const res) { + const __m128i c0 = _mm_loadu_si128((const __m128i*)(coeffs + 0)); + const __m128i c1 = _mm_loadu_si128((const __m128i*)(coeffs + 8)); + // Use SSE2 to compare 16 values with a single instruction. + const __m128i zero = _mm_setzero_si128(); + const __m128i m0 = _mm_packs_epi16(c0, c1); + const __m128i m1 = _mm_cmpeq_epi8(m0, zero); + // Get the comparison results as a bitmask into 16bits. Negate the mask to get + // the position of entries that are not equal to zero. We don't need to mask + // out least significant bits according to res->first, since coeffs[0] is 0 + // if res->first > 0. + const uint32_t mask = 0x0000ffffu ^ (uint32_t)_mm_movemask_epi8(m1); + // The position of the most significant non-zero bit indicates the position of + // the last non-zero value. + assert(res->first == 0 || coeffs[0] == 0); + res->last = mask ? BitsLog2Floor(mask) : -1; + res->coeffs = coeffs; +} + +static int GetResidualCostSSE2(int ctx0, const VP8Residual* const res) { + uint8_t levels[16], ctxs[16]; + uint16_t abs_levels[16]; + int n = res->first; + // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 + const int p0 = res->prob[n][ctx0][0]; + CostArrayPtr const costs = res->costs; + const uint16_t* t = costs[n][ctx0]; + // bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0; + + if (res->last < 0) { + return VP8BitCost(0, p0); + } + + { // precompute clamped levels and contexts, packed to 8b. + const __m128i zero = _mm_setzero_si128(); + const __m128i kCst2 = _mm_set1_epi8(2); + const __m128i kCst67 = _mm_set1_epi8(MAX_VARIABLE_LEVEL); + const __m128i c0 = _mm_loadu_si128((const __m128i*)&res->coeffs[0]); + const __m128i c1 = _mm_loadu_si128((const __m128i*)&res->coeffs[8]); + const __m128i D0 = _mm_sub_epi16(zero, c0); + const __m128i D1 = _mm_sub_epi16(zero, c1); + const __m128i E0 = _mm_max_epi16(c0, D0); // abs(v), 16b + const __m128i E1 = _mm_max_epi16(c1, D1); + const __m128i F = _mm_packs_epi16(E0, E1); + const __m128i G = _mm_min_epu8(F, kCst2); // context = 0,1,2 + const __m128i H = _mm_min_epu8(F, kCst67); // clamp_level in [0..67] + + _mm_storeu_si128((__m128i*)&ctxs[0], G); + _mm_storeu_si128((__m128i*)&levels[0], H); + + _mm_storeu_si128((__m128i*)&abs_levels[0], E0); + _mm_storeu_si128((__m128i*)&abs_levels[8], E1); + } + for (; n < res->last; ++n) { + const int ctx = ctxs[n]; + const int level = levels[n]; + const int flevel = abs_levels[n]; // full level + cost += VP8LevelFixedCosts[flevel] + t[level]; // simplified VP8LevelCost() + t = costs[n + 1][ctx]; + } + // Last coefficient is always non-zero + { + const int level = levels[n]; + const int flevel = abs_levels[n]; + assert(flevel != 0); + cost += VP8LevelFixedCosts[flevel] + t[level]; + if (n < 15) { + const int b = VP8EncBands[n + 1]; + const int ctx = ctxs[n]; + const int last_p0 = res->prob[b][ctx][0]; + cost += VP8BitCost(0, last_p0); + } + } + return cost; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspCostInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInitSSE2(void) { + VP8SetResidualCoeffs = SetResidualCoeffsSSE2; + VP8GetResidualCost = GetResidualCostSSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8EncDspCostInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/dsp/cpu.c b/3rdparty/libwebp/dsp/cpu.c index 179901e100..b5583b6e9b 100644 --- a/3rdparty/libwebp/dsp/cpu.c +++ b/3rdparty/libwebp/dsp/cpu.c @@ -13,12 +13,13 @@ #include "./dsp.h" -#if defined(__ANDROID__) -#include +#if defined(WEBP_HAVE_NEON_RTCD) +#include +#include #endif -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { +#if defined(WEBP_ANDROID_NEON) +#include #endif //------------------------------------------------------------------------------ @@ -33,33 +34,141 @@ static WEBP_INLINE void GetCPUInfo(int cpu_info[4], int info_type) { "cpuid\n" "xchg %%edi, %%ebx\n" : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) - : "a"(info_type)); + : "a"(info_type), "c"(0)); +} +#elif defined(__x86_64__) && \ + (defined(__code_model_medium__) || defined(__code_model_large__)) && \ + defined(__PIC__) +static WEBP_INLINE void GetCPUInfo(int cpu_info[4], int info_type) { + __asm__ volatile ( + "xchg{q}\t{%%rbx}, %q1\n" + "cpuid\n" + "xchg{q}\t{%%rbx}, %q1\n" + : "=a"(cpu_info[0]), "=&r"(cpu_info[1]), "=c"(cpu_info[2]), + "=d"(cpu_info[3]) + : "a"(info_type), "c"(0)); } #elif defined(__i386__) || defined(__x86_64__) static WEBP_INLINE void GetCPUInfo(int cpu_info[4], int info_type) { __asm__ volatile ( "cpuid\n" : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) - : "a"(info_type)); + : "a"(info_type), "c"(0)); } +#elif (defined(_M_X64) || defined(_M_IX86)) && \ + defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 150030729 // >= VS2008 SP1 +#include +#define GetCPUInfo(info, type) __cpuidex(info, type, 0) // set ecx=0 #elif defined(WEBP_MSC_SSE2) #define GetCPUInfo __cpuid #endif +// NaCl has no support for xgetbv or the raw opcode. +#if !defined(__native_client__) && (defined(__i386__) || defined(__x86_64__)) +static WEBP_INLINE uint64_t xgetbv(void) { + const uint32_t ecx = 0; + uint32_t eax, edx; + // Use the raw opcode for xgetbv for compatibility with older toolchains. + __asm__ volatile ( + ".byte 0x0f, 0x01, 0xd0\n" + : "=a"(eax), "=d"(edx) : "c" (ecx)); + return ((uint64_t)edx << 32) | eax; +} +#elif (defined(_M_X64) || defined(_M_IX86)) && \ + defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 160040219 // >= VS2010 SP1 +#include +#define xgetbv() _xgetbv(0) +#elif defined(_MSC_VER) && defined(_M_IX86) +static WEBP_INLINE uint64_t xgetbv(void) { + uint32_t eax_, edx_; + __asm { + xor ecx, ecx // ecx = 0 + // Use the raw opcode for xgetbv for compatibility with older toolchains. + __asm _emit 0x0f __asm _emit 0x01 __asm _emit 0xd0 + mov eax_, eax + mov edx_, edx + } + return ((uint64_t)edx_ << 32) | eax_; +} +#else +#define xgetbv() 0U // no AVX for older x64 or unrecognized toolchains. +#endif + #if defined(__i386__) || defined(__x86_64__) || defined(WEBP_MSC_SSE2) + +// helper function for run-time detection of slow SSSE3 platforms +static int CheckSlowModel(int info) { + // Table listing display models with longer latencies for the bsr instruction + // (ie 2 cycles vs 10/16 cycles) and some SSSE3 instructions like pshufb. + // Refer to Intel 64 and IA-32 Architectures Optimization Reference Manual. + static const uint8_t kSlowModels[] = { + 0x37, 0x4a, 0x4d, // Silvermont Microarchitecture + 0x1c, 0x26, 0x27 // Atom Microarchitecture + }; + const uint32_t model = ((info & 0xf0000) >> 12) | ((info >> 4) & 0xf); + const uint32_t family = (info >> 8) & 0xf; + if (family == 0x06) { + size_t i; + for (i = 0; i < sizeof(kSlowModels) / sizeof(kSlowModels[0]); ++i) { + if (model == kSlowModels[i]) return 1; + } + } + return 0; +} + static int x86CPUInfo(CPUFeature feature) { + int max_cpuid_value; int cpu_info[4]; + int is_intel = 0; + + // get the highest feature value cpuid supports + GetCPUInfo(cpu_info, 0); + max_cpuid_value = cpu_info[0]; + if (max_cpuid_value < 1) { + return 0; + } else { + const int VENDOR_ID_INTEL_EBX = 0x756e6547; // uneG + const int VENDOR_ID_INTEL_EDX = 0x49656e69; // Ieni + const int VENDOR_ID_INTEL_ECX = 0x6c65746e; // letn + is_intel = (cpu_info[1] == VENDOR_ID_INTEL_EBX && + cpu_info[2] == VENDOR_ID_INTEL_ECX && + cpu_info[3] == VENDOR_ID_INTEL_EDX); // genuine Intel? + } + GetCPUInfo(cpu_info, 1); if (feature == kSSE2) { - return 0 != (cpu_info[3] & 0x04000000); + return !!(cpu_info[3] & (1 << 26)); } if (feature == kSSE3) { - return 0 != (cpu_info[2] & 0x00000001); + return !!(cpu_info[2] & (1 << 0)); + } + if (feature == kSlowSSSE3) { + if (is_intel && (cpu_info[2] & (1 << 0))) { // SSSE3? + return CheckSlowModel(cpu_info[0]); + } + return 0; + } + + if (feature == kSSE4_1) { + return !!(cpu_info[2] & (1 << 19)); + } + if (feature == kAVX) { + // bits 27 (OSXSAVE) & 28 (256-bit AVX) + if ((cpu_info[2] & 0x18000000) == 0x18000000) { + // XMM state and YMM state enabled by the OS. + return (xgetbv() & 0x6) == 0x6; + } + } + if (feature == kAVX2) { + if (x86CPUInfo(kAVX) && max_cpuid_value >= 7) { + GetCPUInfo(cpu_info, 7); + return !!(cpu_info[1] & (1 << 5)); + } } return 0; } VP8CPUInfo VP8GetCPUInfo = x86CPUInfo; -#elif defined(WEBP_ANDROID_NEON) +#elif defined(WEBP_ANDROID_NEON) // NB: needs to be before generic NEON test. static int AndroidCPUInfo(CPUFeature feature) { const AndroidCpuFamily cpu_family = android_getCpuFamily(); const uint64_t cpu_features = android_getCpuFeatures(); @@ -70,18 +179,44 @@ static int AndroidCPUInfo(CPUFeature feature) { return 0; } VP8CPUInfo VP8GetCPUInfo = AndroidCPUInfo; -#elif defined(__ARM_NEON__) +#elif defined(WEBP_USE_NEON) // define a dummy function to enable turning off NEON at runtime by setting // VP8DecGetCPUInfo = NULL static int armCPUInfo(CPUFeature feature) { - (void)feature; + if (feature != kNEON) return 0; +#if defined(__linux__) && defined(WEBP_HAVE_NEON_RTCD) + { + int has_neon = 0; + char line[200]; + FILE* const cpuinfo = fopen("/proc/cpuinfo", "r"); + if (cpuinfo == NULL) return 0; + while (fgets(line, sizeof(line), cpuinfo)) { + if (!strncmp(line, "Features", 8)) { + if (strstr(line, " neon ") != NULL) { + has_neon = 1; + break; + } + } + } + fclose(cpuinfo); + return has_neon; + } +#else return 1; +#endif } VP8CPUInfo VP8GetCPUInfo = armCPUInfo; +#elif defined(WEBP_USE_MIPS32) || defined(WEBP_USE_MIPS_DSP_R2) || \ + defined(WEBP_USE_MSA) +static int mipsCPUInfo(CPUFeature feature) { + if ((feature == kMIPS32) || (feature == kMIPSdspR2) || (feature == kMSA)) { + return 1; + } else { + return 0; + } + +} +VP8CPUInfo VP8GetCPUInfo = mipsCPUInfo; #else VP8CPUInfo VP8GetCPUInfo = NULL; #endif - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/dsp/dec.c b/3rdparty/libwebp/dsp/dec.c index 2fbd6b1ac4..007e985d8b 100644 --- a/3rdparty/libwebp/dsp/dec.c +++ b/3rdparty/libwebp/dsp/dec.c @@ -7,49 +7,15 @@ // be found in the AUTHORS file in the root of the source tree. // ----------------------------------------------------------------------------- // -// Speed-critical decoding functions. +// Speed-critical decoding functions, default plain-C implementations. // // Author: Skal (pascal.massimino@gmail.com) #include "./dsp.h" -#include "../dec/vp8i.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "../dec/vp8i_dec.h" +#include "../utils/utils.h" //------------------------------------------------------------------------------ -// run-time tables (~4k) - -static uint8_t abs0[255 + 255 + 1]; // abs(i) -static uint8_t abs1[255 + 255 + 1]; // abs(i)>>1 -static int8_t sclip1[1020 + 1020 + 1]; // clips [-1020, 1020] to [-128, 127] -static int8_t sclip2[112 + 112 + 1]; // clips [-112, 112] to [-16, 15] -static uint8_t clip1[255 + 510 + 1]; // clips [-255,510] to [0,255] - -// We declare this variable 'volatile' to prevent instruction reordering -// and make sure it's set to true _last_ (so as to be thread-safe) -static volatile int tables_ok = 0; - -static void DspInitTables(void) { - if (!tables_ok) { - int i; - for (i = -255; i <= 255; ++i) { - abs0[255 + i] = (i < 0) ? -i : i; - abs1[255 + i] = abs0[255 + i] >> 1; - } - for (i = -1020; i <= 1020; ++i) { - sclip1[1020 + i] = (i < -128) ? -128 : (i > 127) ? 127 : i; - } - for (i = -112; i <= 112; ++i) { - sclip2[112 + i] = (i < -16) ? -16 : (i > 15) ? 15 : i; - } - for (i = -255; i <= 255 + 255; ++i) { - clip1[255 + i] = (i < 0) ? 0 : (i > 255) ? 255 : i; - } - tables_ok = 1; - } -} static WEBP_INLINE uint8_t clip_8b(int v) { return (!(v & ~0xff)) ? v : (v < 0) ? 0 : 255; @@ -61,9 +27,16 @@ static WEBP_INLINE uint8_t clip_8b(int v) { #define STORE(x, y, v) \ dst[x + y * BPS] = clip_8b(dst[x + y * BPS] + ((v) >> 3)) -static const int kC1 = 20091 + (1 << 16); -static const int kC2 = 35468; -#define MUL(a, b) (((a) * (b)) >> 16) +#define STORE2(y, dc, d, c) do { \ + const int DC = (dc); \ + STORE(0, y, DC + (d)); \ + STORE(1, y, DC + (c)); \ + STORE(2, y, DC - (c)); \ + STORE(3, y, DC - (d)); \ +} while (0) + +#define MUL1(a) ((((a) * 20091) >> 16) + (a)) +#define MUL2(a) (((a) * 35468) >> 16) static void TransformOne(const int16_t* in, uint8_t* dst) { int C[4 * 4], *tmp; @@ -72,8 +45,8 @@ static void TransformOne(const int16_t* in, uint8_t* dst) { for (i = 0; i < 4; ++i) { // vertical pass const int a = in[0] + in[8]; // [-4096, 4094] const int b = in[0] - in[8]; // [-4095, 4095] - const int c = MUL(in[4], kC2) - MUL(in[12], kC1); // [-3783, 3783] - const int d = MUL(in[4], kC1) + MUL(in[12], kC2); // [-3785, 3781] + const int c = MUL2(in[4]) - MUL1(in[12]); // [-3783, 3783] + const int d = MUL1(in[4]) + MUL2(in[12]); // [-3785, 3781] tmp[0] = a + d; // [-7881, 7875] tmp[1] = b + c; // [-7878, 7878] tmp[2] = b - c; // [-7878, 7878] @@ -82,7 +55,7 @@ static void TransformOne(const int16_t* in, uint8_t* dst) { in++; } // Each pass is expanding the dynamic range by ~3.85 (upper bound). - // The exact value is (2. + (kC1 + kC2) / 65536). + // The exact value is (2. + (20091 + 35468) / 65536). // After the second pass, maximum interval is [-3794, 3794], assuming // an input in [-2048, 2047] interval. We then need to add a dst value // in the [0, 255] range. @@ -93,8 +66,8 @@ static void TransformOne(const int16_t* in, uint8_t* dst) { const int dc = tmp[0] + 4; const int a = dc + tmp[8]; const int b = dc - tmp[8]; - const int c = MUL(tmp[4], kC2) - MUL(tmp[12], kC1); - const int d = MUL(tmp[4], kC1) + MUL(tmp[12], kC2); + const int c = MUL2(tmp[4]) - MUL1(tmp[12]); + const int d = MUL1(tmp[4]) + MUL2(tmp[12]); STORE(0, 0, a + d); STORE(1, 0, b + c); STORE(2, 0, b - c); @@ -103,7 +76,22 @@ static void TransformOne(const int16_t* in, uint8_t* dst) { dst += BPS; } } -#undef MUL + +// Simplified transform when only in[0], in[1] and in[4] are non-zero +static void TransformAC3(const int16_t* in, uint8_t* dst) { + const int a = in[0] + 4; + const int c4 = MUL2(in[4]); + const int d4 = MUL1(in[4]); + const int c1 = MUL2(in[1]); + const int d1 = MUL1(in[1]); + STORE2(0, a + d4, d1, c1); + STORE2(1, a + c4, d1, c1); + STORE2(2, a - c4, d1, c1); + STORE2(3, a - d4, d1, c1); +} +#undef MUL1 +#undef MUL2 +#undef STORE2 static void TransformTwo(const int16_t* in, uint8_t* dst, int do_two) { TransformOne(in, dst); @@ -117,7 +105,7 @@ static void TransformUV(const int16_t* in, uint8_t* dst) { VP8Transform(in + 2 * 16, dst + 4 * BPS, 1); } -static void TransformDC(const int16_t *in, uint8_t* dst) { +static void TransformDC(const int16_t* in, uint8_t* dst) { const int DC = in[0] + 4; int i, j; for (j = 0; j < 4; ++j) { @@ -128,10 +116,10 @@ static void TransformDC(const int16_t *in, uint8_t* dst) { } static void TransformDCUV(const int16_t* in, uint8_t* dst) { - if (in[0 * 16]) TransformDC(in + 0 * 16, dst); - if (in[1 * 16]) TransformDC(in + 1 * 16, dst + 4); - if (in[2 * 16]) TransformDC(in + 2 * 16, dst + 4 * BPS); - if (in[3 * 16]) TransformDC(in + 3 * 16, dst + 4 * BPS + 4); + if (in[0 * 16]) VP8TransformDC(in + 0 * 16, dst); + if (in[1 * 16]) VP8TransformDC(in + 1 * 16, dst + 4); + if (in[2 * 16]) VP8TransformDC(in + 2 * 16, dst + 4 * BPS); + if (in[3 * 16]) VP8TransformDC(in + 3 * 16, dst + 4 * BPS + 4); } #undef STORE @@ -166,16 +154,16 @@ static void TransformWHT(const int16_t* in, int16_t* out) { } } -void (*VP8TransformWHT)(const int16_t* in, int16_t* out) = TransformWHT; +void (*VP8TransformWHT)(const int16_t* in, int16_t* out); //------------------------------------------------------------------------------ // Intra predictions #define DST(x, y) dst[(x) + (y) * BPS] -static WEBP_INLINE void TrueMotion(uint8_t *dst, int size) { +static WEBP_INLINE void TrueMotion(uint8_t* dst, int size) { const uint8_t* top = dst - BPS; - const uint8_t* const clip0 = clip1 + 255 - top[-1]; + const uint8_t* const clip0 = VP8kclip1 - top[-1]; int y; for (y = 0; y < size; ++y) { const uint8_t* const clip = clip0 + dst[-1]; @@ -186,21 +174,21 @@ static WEBP_INLINE void TrueMotion(uint8_t *dst, int size) { dst += BPS; } } -static void TM4(uint8_t *dst) { TrueMotion(dst, 4); } -static void TM8uv(uint8_t *dst) { TrueMotion(dst, 8); } -static void TM16(uint8_t *dst) { TrueMotion(dst, 16); } +static void TM4(uint8_t* dst) { TrueMotion(dst, 4); } +static void TM8uv(uint8_t* dst) { TrueMotion(dst, 8); } +static void TM16(uint8_t* dst) { TrueMotion(dst, 16); } //------------------------------------------------------------------------------ // 16x16 -static void VE16(uint8_t *dst) { // vertical +static void VE16(uint8_t* dst) { // vertical int j; for (j = 0; j < 16; ++j) { memcpy(dst + j * BPS, dst - BPS, 16); } } -static void HE16(uint8_t *dst) { // horizontal +static void HE16(uint8_t* dst) { // horizontal int j; for (j = 16; j > 0; --j) { memset(dst, dst[-1], 16); @@ -215,7 +203,7 @@ static WEBP_INLINE void Put16(int v, uint8_t* dst) { } } -static void DC16(uint8_t *dst) { // DC +static void DC16(uint8_t* dst) { // DC int DC = 16; int j; for (j = 0; j < 16; ++j) { @@ -224,7 +212,7 @@ static void DC16(uint8_t *dst) { // DC Put16(DC >> 5, dst); } -static void DC16NoTop(uint8_t *dst) { // DC with top samples not available +static void DC16NoTop(uint8_t* dst) { // DC with top samples not available int DC = 8; int j; for (j = 0; j < 16; ++j) { @@ -233,7 +221,7 @@ static void DC16NoTop(uint8_t *dst) { // DC with top samples not available Put16(DC >> 4, dst); } -static void DC16NoLeft(uint8_t *dst) { // DC with left samples not available +static void DC16NoLeft(uint8_t* dst) { // DC with left samples not available int DC = 8; int i; for (i = 0; i < 16; ++i) { @@ -242,17 +230,19 @@ static void DC16NoLeft(uint8_t *dst) { // DC with left samples not available Put16(DC >> 4, dst); } -static void DC16NoTopLeft(uint8_t *dst) { // DC with no top and left samples +static void DC16NoTopLeft(uint8_t* dst) { // DC with no top and left samples Put16(0x80, dst); } +VP8PredFunc VP8PredLuma16[NUM_B_DC_MODES]; + //------------------------------------------------------------------------------ // 4x4 -#define AVG3(a, b, c) (((a) + 2 * (b) + (c) + 2) >> 2) +#define AVG3(a, b, c) ((uint8_t)(((a) + 2 * (b) + (c) + 2) >> 2)) #define AVG2(a, b) (((a) + (b) + 1) >> 1) -static void VE4(uint8_t *dst) { // vertical +static void VE4(uint8_t* dst) { // vertical const uint8_t* top = dst - BPS; const uint8_t vals[4] = { AVG3(top[-1], top[0], top[1]), @@ -266,19 +256,19 @@ static void VE4(uint8_t *dst) { // vertical } } -static void HE4(uint8_t *dst) { // horizontal +static void HE4(uint8_t* dst) { // horizontal const int A = dst[-1 - BPS]; const int B = dst[-1]; const int C = dst[-1 + BPS]; const int D = dst[-1 + 2 * BPS]; const int E = dst[-1 + 3 * BPS]; - *(uint32_t*)(dst + 0 * BPS) = 0x01010101U * AVG3(A, B, C); - *(uint32_t*)(dst + 1 * BPS) = 0x01010101U * AVG3(B, C, D); - *(uint32_t*)(dst + 2 * BPS) = 0x01010101U * AVG3(C, D, E); - *(uint32_t*)(dst + 3 * BPS) = 0x01010101U * AVG3(D, E, E); + WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(A, B, C)); + WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(B, C, D)); + WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(C, D, E)); + WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(D, E, E)); } -static void DC4(uint8_t *dst) { // DC +static void DC4(uint8_t* dst) { // DC uint32_t dc = 4; int i; for (i = 0; i < 4; ++i) dc += dst[i - BPS] + dst[-1 + i * BPS]; @@ -286,7 +276,7 @@ static void DC4(uint8_t *dst) { // DC for (i = 0; i < 4; ++i) memset(dst + i * BPS, dc, 4); } -static void RD4(uint8_t *dst) { // Down-right +static void RD4(uint8_t* dst) { // Down-right const int I = dst[-1 + 0 * BPS]; const int J = dst[-1 + 1 * BPS]; const int K = dst[-1 + 2 * BPS]; @@ -297,15 +287,15 @@ static void RD4(uint8_t *dst) { // Down-right const int C = dst[2 - BPS]; const int D = dst[3 - BPS]; DST(0, 3) = AVG3(J, K, L); - DST(0, 2) = DST(1, 3) = AVG3(I, J, K); - DST(0, 1) = DST(1, 2) = DST(2, 3) = AVG3(X, I, J); - DST(0, 0) = DST(1, 1) = DST(2, 2) = DST(3, 3) = AVG3(A, X, I); - DST(1, 0) = DST(2, 1) = DST(3, 2) = AVG3(B, A, X); - DST(2, 0) = DST(3, 1) = AVG3(C, B, A); - DST(3, 0) = AVG3(D, C, B); + DST(1, 3) = DST(0, 2) = AVG3(I, J, K); + DST(2, 3) = DST(1, 2) = DST(0, 1) = AVG3(X, I, J); + DST(3, 3) = DST(2, 2) = DST(1, 1) = DST(0, 0) = AVG3(A, X, I); + DST(3, 2) = DST(2, 1) = DST(1, 0) = AVG3(B, A, X); + DST(3, 1) = DST(2, 0) = AVG3(C, B, A); + DST(3, 0) = AVG3(D, C, B); } -static void LD4(uint8_t *dst) { // Down-Left +static void LD4(uint8_t* dst) { // Down-Left const int A = dst[0 - BPS]; const int B = dst[1 - BPS]; const int C = dst[2 - BPS]; @@ -318,12 +308,12 @@ static void LD4(uint8_t *dst) { // Down-Left DST(1, 0) = DST(0, 1) = AVG3(B, C, D); DST(2, 0) = DST(1, 1) = DST(0, 2) = AVG3(C, D, E); DST(3, 0) = DST(2, 1) = DST(1, 2) = DST(0, 3) = AVG3(D, E, F); - DST(3, 1) = DST(2, 2) = DST(1, 3) = AVG3(E, F, G); - DST(3, 2) = DST(2, 3) = AVG3(F, G, H); - DST(3, 3) = AVG3(G, H, H); + DST(3, 1) = DST(2, 2) = DST(1, 3) = AVG3(E, F, G); + DST(3, 2) = DST(2, 3) = AVG3(F, G, H); + DST(3, 3) = AVG3(G, H, H); } -static void VR4(uint8_t *dst) { // Vertical-Right +static void VR4(uint8_t* dst) { // Vertical-Right const int I = dst[-1 + 0 * BPS]; const int J = dst[-1 + 1 * BPS]; const int K = dst[-1 + 2 * BPS]; @@ -345,7 +335,7 @@ static void VR4(uint8_t *dst) { // Vertical-Right DST(3, 1) = AVG3(B, C, D); } -static void VL4(uint8_t *dst) { // Vertical-Left +static void VL4(uint8_t* dst) { // Vertical-Left const int A = dst[0 - BPS]; const int B = dst[1 - BPS]; const int C = dst[2 - BPS]; @@ -367,7 +357,7 @@ static void VL4(uint8_t *dst) { // Vertical-Left DST(3, 3) = AVG3(F, G, H); } -static void HU4(uint8_t *dst) { // Horizontal-Up +static void HU4(uint8_t* dst) { // Horizontal-Up const int I = dst[-1 + 0 * BPS]; const int J = dst[-1 + 1 * BPS]; const int K = dst[-1 + 2 * BPS]; @@ -382,7 +372,7 @@ static void HU4(uint8_t *dst) { // Horizontal-Up DST(0, 3) = DST(1, 3) = DST(2, 3) = DST(3, 3) = L; } -static void HD4(uint8_t *dst) { // Horizontal-Down +static void HD4(uint8_t* dst) { // Horizontal-Down const int I = dst[-1 + 0 * BPS]; const int J = dst[-1 + 1 * BPS]; const int K = dst[-1 + 2 * BPS]; @@ -409,17 +399,19 @@ static void HD4(uint8_t *dst) { // Horizontal-Down #undef AVG3 #undef AVG2 +VP8PredFunc VP8PredLuma4[NUM_BMODES]; + //------------------------------------------------------------------------------ // Chroma -static void VE8uv(uint8_t *dst) { // vertical +static void VE8uv(uint8_t* dst) { // vertical int j; for (j = 0; j < 8; ++j) { memcpy(dst + j * BPS, dst - BPS, 8); } } -static void HE8uv(uint8_t *dst) { // horizontal +static void HE8uv(uint8_t* dst) { // horizontal int j; for (j = 0; j < 8; ++j) { memset(dst, dst[-1], 8); @@ -430,17 +422,12 @@ static void HE8uv(uint8_t *dst) { // horizontal // helper for chroma-DC predictions static WEBP_INLINE void Put8x8uv(uint8_t value, uint8_t* dst) { int j; -#ifndef WEBP_REFERENCE_IMPLEMENTATION - const uint64_t v = (uint64_t)value * 0x0101010101010101ULL; for (j = 0; j < 8; ++j) { - *(uint64_t*)(dst + j * BPS) = v; + memset(dst + j * BPS, value, 8); } -#else - for (j = 0; j < 8; ++j) memset(dst + j * BPS, value, 8); -#endif } -static void DC8uv(uint8_t *dst) { // DC +static void DC8uv(uint8_t* dst) { // DC int dc0 = 8; int i; for (i = 0; i < 8; ++i) { @@ -449,7 +436,7 @@ static void DC8uv(uint8_t *dst) { // DC Put8x8uv(dc0 >> 4, dst); } -static void DC8uvNoLeft(uint8_t *dst) { // DC with no left samples +static void DC8uvNoLeft(uint8_t* dst) { // DC with no left samples int dc0 = 4; int i; for (i = 0; i < 8; ++i) { @@ -458,7 +445,7 @@ static void DC8uvNoLeft(uint8_t *dst) { // DC with no left samples Put8x8uv(dc0 >> 3, dst); } -static void DC8uvNoTop(uint8_t *dst) { // DC with no top samples +static void DC8uvNoTop(uint8_t* dst) { // DC with no top samples int dc0 = 4; int i; for (i = 0; i < 8; ++i) { @@ -467,26 +454,11 @@ static void DC8uvNoTop(uint8_t *dst) { // DC with no top samples Put8x8uv(dc0 >> 3, dst); } -static void DC8uvNoTopLeft(uint8_t *dst) { // DC with nothing +static void DC8uvNoTopLeft(uint8_t* dst) { // DC with nothing Put8x8uv(0x80, dst); } -//------------------------------------------------------------------------------ -// default C implementations - -const VP8PredFunc VP8PredLuma4[NUM_BMODES] = { - DC4, TM4, VE4, HE4, RD4, VR4, LD4, VL4, HD4, HU4 -}; - -const VP8PredFunc VP8PredLuma16[NUM_B_DC_MODES] = { - DC16, TM16, VE16, HE16, - DC16NoTop, DC16NoLeft, DC16NoTopLeft -}; - -const VP8PredFunc VP8PredChroma8[NUM_B_DC_MODES] = { - DC8uv, TM8uv, VE8uv, HE8uv, - DC8uvNoTop, DC8uvNoLeft, DC8uvNoTopLeft -}; +VP8PredFunc VP8PredChroma8[NUM_B_DC_MODES]; //------------------------------------------------------------------------------ // Edge filtering functions @@ -494,61 +466,62 @@ const VP8PredFunc VP8PredChroma8[NUM_B_DC_MODES] = { // 4 pixels in, 2 pixels out static WEBP_INLINE void do_filter2(uint8_t* p, int step) { const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; - const int a = 3 * (q0 - p0) + sclip1[1020 + p1 - q1]; - const int a1 = sclip2[112 + ((a + 4) >> 3)]; - const int a2 = sclip2[112 + ((a + 3) >> 3)]; - p[-step] = clip1[255 + p0 + a2]; - p[ 0] = clip1[255 + q0 - a1]; + const int a = 3 * (q0 - p0) + VP8ksclip1[p1 - q1]; // in [-893,892] + const int a1 = VP8ksclip2[(a + 4) >> 3]; // in [-16,15] + const int a2 = VP8ksclip2[(a + 3) >> 3]; + p[-step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; } // 4 pixels in, 4 pixels out static WEBP_INLINE void do_filter4(uint8_t* p, int step) { const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; const int a = 3 * (q0 - p0); - const int a1 = sclip2[112 + ((a + 4) >> 3)]; - const int a2 = sclip2[112 + ((a + 3) >> 3)]; + const int a1 = VP8ksclip2[(a + 4) >> 3]; + const int a2 = VP8ksclip2[(a + 3) >> 3]; const int a3 = (a1 + 1) >> 1; - p[-2*step] = clip1[255 + p1 + a3]; - p[- step] = clip1[255 + p0 + a2]; - p[ 0] = clip1[255 + q0 - a1]; - p[ step] = clip1[255 + q1 - a3]; + p[-2*step] = VP8kclip1[p1 + a3]; + p[- step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a3]; } // 6 pixels in, 6 pixels out static WEBP_INLINE void do_filter6(uint8_t* p, int step) { const int p2 = p[-3*step], p1 = p[-2*step], p0 = p[-step]; const int q0 = p[0], q1 = p[step], q2 = p[2*step]; - const int a = sclip1[1020 + 3 * (q0 - p0) + sclip1[1020 + p1 - q1]]; + const int a = VP8ksclip1[3 * (q0 - p0) + VP8ksclip1[p1 - q1]]; + // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] const int a1 = (27 * a + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 const int a2 = (18 * a + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 const int a3 = (9 * a + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 - p[-3*step] = clip1[255 + p2 + a3]; - p[-2*step] = clip1[255 + p1 + a2]; - p[- step] = clip1[255 + p0 + a1]; - p[ 0] = clip1[255 + q0 - a1]; - p[ step] = clip1[255 + q1 - a2]; - p[ 2*step] = clip1[255 + q2 - a3]; + p[-3*step] = VP8kclip1[p2 + a3]; + p[-2*step] = VP8kclip1[p1 + a2]; + p[- step] = VP8kclip1[p0 + a1]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a2]; + p[ 2*step] = VP8kclip1[q2 - a3]; } static WEBP_INLINE int hev(const uint8_t* p, int step, int thresh) { const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; - return (abs0[255 + p1 - p0] > thresh) || (abs0[255 + q1 - q0] > thresh); + return (VP8kabs0[p1 - p0] > thresh) || (VP8kabs0[q1 - q0] > thresh); } -static WEBP_INLINE int needs_filter(const uint8_t* p, int step, int thresh) { - const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; - return (2 * abs0[255 + p0 - q0] + abs1[255 + p1 - q1]) <= thresh; +static WEBP_INLINE int needs_filter(const uint8_t* p, int step, int t) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + return ((4 * VP8kabs0[p0 - q0] + VP8kabs0[p1 - q1]) <= t); } static WEBP_INLINE int needs_filter2(const uint8_t* p, int step, int t, int it) { - const int p3 = p[-4*step], p2 = p[-3*step], p1 = p[-2*step], p0 = p[-step]; - const int q0 = p[0], q1 = p[step], q2 = p[2*step], q3 = p[3*step]; - if ((2 * abs0[255 + p0 - q0] + abs1[255 + p1 - q1]) > t) - return 0; - return abs0[255 + p3 - p2] <= it && abs0[255 + p2 - p1] <= it && - abs0[255 + p1 - p0] <= it && abs0[255 + q3 - q2] <= it && - abs0[255 + q2 - q1] <= it && abs0[255 + q1 - q0] <= it; + const int p3 = p[-4 * step], p2 = p[-3 * step], p1 = p[-2 * step]; + const int p0 = p[-step], q0 = p[0]; + const int q1 = p[step], q2 = p[2 * step], q3 = p[3 * step]; + if ((4 * VP8kabs0[p0 - q0] + VP8kabs0[p1 - q1]) > t) return 0; + return VP8kabs0[p3 - p2] <= it && VP8kabs0[p2 - p1] <= it && + VP8kabs0[p1 - p0] <= it && VP8kabs0[q3 - q2] <= it && + VP8kabs0[q2 - q1] <= it && VP8kabs0[q1 - q0] <= it; } //------------------------------------------------------------------------------ @@ -556,8 +529,9 @@ static WEBP_INLINE int needs_filter2(const uint8_t* p, static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { int i; + const int thresh2 = 2 * thresh + 1; for (i = 0; i < 16; ++i) { - if (needs_filter(p + i, stride, thresh)) { + if (needs_filter(p + i, stride, thresh2)) { do_filter2(p + i, stride); } } @@ -565,8 +539,9 @@ static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { static void SimpleHFilter16(uint8_t* p, int stride, int thresh) { int i; + const int thresh2 = 2 * thresh + 1; for (i = 0; i < 16; ++i) { - if (needs_filter(p + i * stride, 1, thresh)) { + if (needs_filter(p + i * stride, 1, thresh2)) { do_filter2(p + i * stride, 1); } } @@ -594,8 +569,9 @@ static void SimpleHFilter16i(uint8_t* p, int stride, int thresh) { static WEBP_INLINE void FilterLoop26(uint8_t* p, int hstride, int vstride, int size, int thresh, int ithresh, int hev_thresh) { + const int thresh2 = 2 * thresh + 1; while (size-- > 0) { - if (needs_filter2(p, hstride, thresh, ithresh)) { + if (needs_filter2(p, hstride, thresh2, ithresh)) { if (hev(p, hstride, hev_thresh)) { do_filter2(p, hstride); } else { @@ -609,8 +585,9 @@ static WEBP_INLINE void FilterLoop26(uint8_t* p, static WEBP_INLINE void FilterLoop24(uint8_t* p, int hstride, int vstride, int size, int thresh, int ithresh, int hev_thresh) { + const int thresh2 = 2 * thresh + 1; while (size-- > 0) { - if (needs_filter2(p, hstride, thresh, ithresh)) { + if (needs_filter2(p, hstride, thresh2, ithresh)) { if (hev(p, hstride, hev_thresh)) { do_filter2(p, hstride); } else { @@ -678,7 +655,25 @@ static void HFilter8i(uint8_t* u, uint8_t* v, int stride, //------------------------------------------------------------------------------ +static void DitherCombine8x8(const uint8_t* dither, uint8_t* dst, + int dst_stride) { + int i, j; + for (j = 0; j < 8; ++j) { + for (i = 0; i < 8; ++i) { + const int delta0 = dither[i] - VP8_DITHER_AMP_CENTER; + const int delta1 = + (delta0 + VP8_DITHER_DESCALE_ROUNDER) >> VP8_DITHER_DESCALE; + dst[i] = clip_8b((int)dst[i] + delta1); + } + dst += dst_stride; + dither += 8; + } +} + +//------------------------------------------------------------------------------ + VP8DecIdct2 VP8Transform; +VP8DecIdct VP8TransformAC3; VP8DecIdct VP8TransformUV; VP8DecIdct VP8TransformDC; VP8DecIdct VP8TransformDCUV; @@ -696,16 +691,30 @@ VP8SimpleFilterFunc VP8SimpleHFilter16; VP8SimpleFilterFunc VP8SimpleVFilter16i; VP8SimpleFilterFunc VP8SimpleHFilter16i; +void (*VP8DitherCombine8x8)(const uint8_t* dither, uint8_t* dst, + int dst_stride); + extern void VP8DspInitSSE2(void); +extern void VP8DspInitSSE41(void); extern void VP8DspInitNEON(void); +extern void VP8DspInitMIPS32(void); +extern void VP8DspInitMIPSdspR2(void); +extern void VP8DspInitMSA(void); -void VP8DspInit(void) { - DspInitTables(); +static volatile VP8CPUInfo dec_last_cpuinfo_used = + (VP8CPUInfo)&dec_last_cpuinfo_used; +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInit(void) { + if (dec_last_cpuinfo_used == VP8GetCPUInfo) return; + + VP8InitClipTables(); + + VP8TransformWHT = TransformWHT; VP8Transform = TransformTwo; VP8TransformUV = TransformUV; VP8TransformDC = TransformDC; VP8TransformDCUV = TransformDCUV; + VP8TransformAC3 = TransformAC3; VP8VFilter16 = VFilter16; VP8HFilter16 = HFilter16; @@ -720,20 +729,67 @@ void VP8DspInit(void) { VP8SimpleVFilter16i = SimpleVFilter16i; VP8SimpleHFilter16i = SimpleHFilter16i; + VP8PredLuma4[0] = DC4; + VP8PredLuma4[1] = TM4; + VP8PredLuma4[2] = VE4; + VP8PredLuma4[3] = HE4; + VP8PredLuma4[4] = RD4; + VP8PredLuma4[5] = VR4; + VP8PredLuma4[6] = LD4; + VP8PredLuma4[7] = VL4; + VP8PredLuma4[8] = HD4; + VP8PredLuma4[9] = HU4; + + VP8PredLuma16[0] = DC16; + VP8PredLuma16[1] = TM16; + VP8PredLuma16[2] = VE16; + VP8PredLuma16[3] = HE16; + VP8PredLuma16[4] = DC16NoTop; + VP8PredLuma16[5] = DC16NoLeft; + VP8PredLuma16[6] = DC16NoTopLeft; + + VP8PredChroma8[0] = DC8uv; + VP8PredChroma8[1] = TM8uv; + VP8PredChroma8[2] = VE8uv; + VP8PredChroma8[3] = HE8uv; + VP8PredChroma8[4] = DC8uvNoTop; + VP8PredChroma8[5] = DC8uvNoLeft; + VP8PredChroma8[6] = DC8uvNoTopLeft; + + VP8DitherCombine8x8 = DitherCombine8x8; + // If defined, use CPUInfo() to overwrite some pointers with faster versions. - if (VP8GetCPUInfo) { + if (VP8GetCPUInfo != NULL) { #if defined(WEBP_USE_SSE2) if (VP8GetCPUInfo(kSSE2)) { VP8DspInitSSE2(); +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + VP8DspInitSSE41(); + } +#endif } -#elif defined(WEBP_USE_NEON) +#endif +#if defined(WEBP_USE_NEON) if (VP8GetCPUInfo(kNEON)) { VP8DspInitNEON(); } #endif - } -} - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + VP8DspInitMIPS32(); + } #endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8DspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8DspInitMSA(); + } +#endif + } + dec_last_cpuinfo_used = VP8GetCPUInfo; +} diff --git a/3rdparty/libwebp/dsp/dec_clip_tables.c b/3rdparty/libwebp/dsp/dec_clip_tables.c new file mode 100644 index 0000000000..74ba34c0bb --- /dev/null +++ b/3rdparty/libwebp/dsp/dec_clip_tables.c @@ -0,0 +1,366 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Clipping tables for filtering +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#define USE_STATIC_TABLES // undefine to have run-time table initialization + +#ifdef USE_STATIC_TABLES + +static const uint8_t abs0[255 + 255 + 1] = { + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, + 0xf3, 0xf2, 0xf1, 0xf0, 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, 0xdf, 0xde, 0xdd, 0xdc, + 0xdb, 0xda, 0xd9, 0xd8, 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, + 0xcf, 0xce, 0xcd, 0xcc, 0xcb, 0xca, 0xc9, 0xc8, 0xc7, 0xc6, 0xc5, 0xc4, + 0xc3, 0xc2, 0xc1, 0xc0, 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8, + 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, 0xaf, 0xae, 0xad, 0xac, + 0xab, 0xaa, 0xa9, 0xa8, 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, + 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, + 0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, + 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x7f, 0x7e, 0x7d, 0x7c, + 0x7b, 0x7a, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, + 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, + 0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, + 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e, 0x4d, 0x4c, + 0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40, + 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, + 0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, + 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, + 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, + 0x03, 0x02, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, + 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, + 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, + 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +static const uint8_t sclip1[1020 + 1020 + 1] = { + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, + 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, + 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f +}; + +static const uint8_t sclip2[112 + 112 + 1] = { + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f +}; + +static const uint8_t clip1[255 + 511 + 1] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, + 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, + 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, + 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +#else + +// uninitialized tables +static uint8_t abs0[255 + 255 + 1]; +static int8_t sclip1[1020 + 1020 + 1]; +static int8_t sclip2[112 + 112 + 1]; +static uint8_t clip1[255 + 511 + 1]; + +// We declare this variable 'volatile' to prevent instruction reordering +// and make sure it's set to true _last_ (so as to be thread-safe) +static volatile int tables_ok = 0; + +#endif + +const int8_t* const VP8ksclip1 = (const int8_t*)&sclip1[1020]; +const int8_t* const VP8ksclip2 = (const int8_t*)&sclip2[112]; +const uint8_t* const VP8kclip1 = &clip1[255]; +const uint8_t* const VP8kabs0 = &abs0[255]; + +WEBP_TSAN_IGNORE_FUNCTION void VP8InitClipTables(void) { +#if !defined(USE_STATIC_TABLES) + int i; + if (!tables_ok) { + for (i = -255; i <= 255; ++i) { + abs0[255 + i] = (i < 0) ? -i : i; + } + for (i = -1020; i <= 1020; ++i) { + sclip1[1020 + i] = (i < -128) ? -128 : (i > 127) ? 127 : i; + } + for (i = -112; i <= 112; ++i) { + sclip2[112 + i] = (i < -16) ? -16 : (i > 15) ? 15 : i; + } + for (i = -255; i <= 255 + 255; ++i) { + clip1[255 + i] = (i < 0) ? 0 : (i > 255) ? 255 : i; + } + tables_ok = 1; + } +#endif // USE_STATIC_TABLES +} diff --git a/3rdparty/libwebp/dsp/dec_mips32.c b/3rdparty/libwebp/dsp/dec_mips32.c new file mode 100644 index 0000000000..4e9ef42605 --- /dev/null +++ b/3rdparty/libwebp/dsp/dec_mips32.c @@ -0,0 +1,587 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of dsp functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS32) + +#include "./mips_macro.h" + +static const int kC1 = 20091 + (1 << 16); +static const int kC2 = 35468; + +static WEBP_INLINE int abs_mips32(int x) { + const int sign = x >> 31; + return (x ^ sign) - sign; +} + +// 4 pixels in, 2 pixels out +static WEBP_INLINE void do_filter2(uint8_t* p, int step) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + const int a = 3 * (q0 - p0) + VP8ksclip1[p1 - q1]; + const int a1 = VP8ksclip2[(a + 4) >> 3]; + const int a2 = VP8ksclip2[(a + 3) >> 3]; + p[-step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; +} + +// 4 pixels in, 4 pixels out +static WEBP_INLINE void do_filter4(uint8_t* p, int step) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + const int a = 3 * (q0 - p0); + const int a1 = VP8ksclip2[(a + 4) >> 3]; + const int a2 = VP8ksclip2[(a + 3) >> 3]; + const int a3 = (a1 + 1) >> 1; + p[-2 * step] = VP8kclip1[p1 + a3]; + p[- step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a3]; +} + +// 6 pixels in, 6 pixels out +static WEBP_INLINE void do_filter6(uint8_t* p, int step) { + const int p2 = p[-3 * step], p1 = p[-2 * step], p0 = p[-step]; + const int q0 = p[0], q1 = p[step], q2 = p[2 * step]; + const int a = VP8ksclip1[3 * (q0 - p0) + VP8ksclip1[p1 - q1]]; + // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] + const int a1 = (27 * a + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 + const int a2 = (18 * a + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 + const int a3 = (9 * a + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 + p[-3 * step] = VP8kclip1[p2 + a3]; + p[-2 * step] = VP8kclip1[p1 + a2]; + p[- step] = VP8kclip1[p0 + a1]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a2]; + p[ 2 * step] = VP8kclip1[q2 - a3]; +} + +static WEBP_INLINE int hev(const uint8_t* p, int step, int thresh) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + return (abs_mips32(p1 - p0) > thresh) || (abs_mips32(q1 - q0) > thresh); +} + +static WEBP_INLINE int needs_filter(const uint8_t* p, int step, int t) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + return ((4 * abs_mips32(p0 - q0) + abs_mips32(p1 - q1)) <= t); +} + +static WEBP_INLINE int needs_filter2(const uint8_t* p, + int step, int t, int it) { + const int p3 = p[-4 * step], p2 = p[-3 * step]; + const int p1 = p[-2 * step], p0 = p[-step]; + const int q0 = p[0], q1 = p[step], q2 = p[2 * step], q3 = p[3 * step]; + if ((4 * abs_mips32(p0 - q0) + abs_mips32(p1 - q1)) > t) { + return 0; + } + return abs_mips32(p3 - p2) <= it && abs_mips32(p2 - p1) <= it && + abs_mips32(p1 - p0) <= it && abs_mips32(q3 - q2) <= it && + abs_mips32(q2 - q1) <= it && abs_mips32(q1 - q0) <= it; +} + +static WEBP_INLINE void FilterLoop26(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + while (size-- > 0) { + if (needs_filter2(p, hstride, thresh2, ithresh)) { + if (hev(p, hstride, hev_thresh)) { + do_filter2(p, hstride); + } else { + do_filter6(p, hstride); + } + } + p += vstride; + } +} + +static WEBP_INLINE void FilterLoop24(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + while (size-- > 0) { + if (needs_filter2(p, hstride, thresh2, ithresh)) { + if (hev(p, hstride, hev_thresh)) { + do_filter2(p, hstride); + } else { + do_filter4(p, hstride); + } + } + p += vstride; + } +} + +// on macroblock edges +static void VFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(p, stride, 1, 16, thresh, ithresh, hev_thresh); +} + +static void HFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(p, 1, stride, 16, thresh, ithresh, hev_thresh); +} + +// 8-pixels wide variant, for chroma filtering +static void VFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(u, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop26(v, stride, 1, 8, thresh, ithresh, hev_thresh); +} + +static void HFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(u, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop26(v, 1, stride, 8, thresh, ithresh, hev_thresh); +} + +static void VFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24(u + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop24(v + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); +} + +static void HFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24(u + 4, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop24(v + 4, 1, stride, 8, thresh, ithresh, hev_thresh); +} + +// on three inner edges +static void VFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + FilterLoop24(p, stride, 1, 16, thresh, ithresh, hev_thresh); + } +} + +static void HFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + FilterLoop24(p, 1, stride, 16, thresh, ithresh, hev_thresh); + } +} + +//------------------------------------------------------------------------------ +// Simple In-loop filtering (Paragraph 15.2) + +static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + for (i = 0; i < 16; ++i) { + if (needs_filter(p + i, stride, thresh2)) { + do_filter2(p + i, stride); + } + } +} + +static void SimpleHFilter16(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + for (i = 0; i < 16; ++i) { + if (needs_filter(p + i * stride, 1, thresh2)) { + do_filter2(p + i * stride, 1); + } + } +} + +static void SimpleVFilter16i(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + SimpleVFilter16(p, stride, thresh); + } +} + +static void SimpleHFilter16i(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + SimpleHFilter16(p, stride, thresh); + } +} + +static void TransformOne(const int16_t* in, uint8_t* dst) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14; + int temp15, temp16, temp17, temp18; + int16_t* p_in = (int16_t*)in; + + // loops unrolled and merged to avoid usage of tmp buffer + // and to reduce number of stalls. MUL macro is written + // in assembler and inlined + __asm__ volatile( + "lh %[temp0], 0(%[in]) \n\t" + "lh %[temp8], 16(%[in]) \n\t" + "lh %[temp4], 8(%[in]) \n\t" + "lh %[temp12], 24(%[in]) \n\t" + "addu %[temp16], %[temp0], %[temp8] \n\t" + "subu %[temp0], %[temp0], %[temp8] \n\t" + "mul %[temp8], %[temp4], %[kC2] \n\t" + "mul %[temp17], %[temp12], %[kC1] \n\t" + "mul %[temp4], %[temp4], %[kC1] \n\t" + "mul %[temp12], %[temp12], %[kC2] \n\t" + "lh %[temp1], 2(%[in]) \n\t" + "lh %[temp5], 10(%[in]) \n\t" + "lh %[temp9], 18(%[in]) \n\t" + "lh %[temp13], 26(%[in]) \n\t" + "sra %[temp8], %[temp8], 16 \n\t" + "sra %[temp17], %[temp17], 16 \n\t" + "sra %[temp4], %[temp4], 16 \n\t" + "sra %[temp12], %[temp12], 16 \n\t" + "lh %[temp2], 4(%[in]) \n\t" + "lh %[temp6], 12(%[in]) \n\t" + "lh %[temp10], 20(%[in]) \n\t" + "lh %[temp14], 28(%[in]) \n\t" + "subu %[temp17], %[temp8], %[temp17] \n\t" + "addu %[temp4], %[temp4], %[temp12] \n\t" + "addu %[temp8], %[temp16], %[temp4] \n\t" + "subu %[temp4], %[temp16], %[temp4] \n\t" + "addu %[temp16], %[temp1], %[temp9] \n\t" + "subu %[temp1], %[temp1], %[temp9] \n\t" + "lh %[temp3], 6(%[in]) \n\t" + "lh %[temp7], 14(%[in]) \n\t" + "lh %[temp11], 22(%[in]) \n\t" + "lh %[temp15], 30(%[in]) \n\t" + "addu %[temp12], %[temp0], %[temp17] \n\t" + "subu %[temp0], %[temp0], %[temp17] \n\t" + "mul %[temp9], %[temp5], %[kC2] \n\t" + "mul %[temp17], %[temp13], %[kC1] \n\t" + "mul %[temp5], %[temp5], %[kC1] \n\t" + "mul %[temp13], %[temp13], %[kC2] \n\t" + "sra %[temp9], %[temp9], 16 \n\t" + "sra %[temp17], %[temp17], 16 \n\t" + "subu %[temp17], %[temp9], %[temp17] \n\t" + "sra %[temp5], %[temp5], 16 \n\t" + "sra %[temp13], %[temp13], 16 \n\t" + "addu %[temp5], %[temp5], %[temp13] \n\t" + "addu %[temp13], %[temp1], %[temp17] \n\t" + "subu %[temp1], %[temp1], %[temp17] \n\t" + "mul %[temp17], %[temp14], %[kC1] \n\t" + "mul %[temp14], %[temp14], %[kC2] \n\t" + "addu %[temp9], %[temp16], %[temp5] \n\t" + "subu %[temp5], %[temp16], %[temp5] \n\t" + "addu %[temp16], %[temp2], %[temp10] \n\t" + "subu %[temp2], %[temp2], %[temp10] \n\t" + "mul %[temp10], %[temp6], %[kC2] \n\t" + "mul %[temp6], %[temp6], %[kC1] \n\t" + "sra %[temp17], %[temp17], 16 \n\t" + "sra %[temp14], %[temp14], 16 \n\t" + "sra %[temp10], %[temp10], 16 \n\t" + "sra %[temp6], %[temp6], 16 \n\t" + "subu %[temp17], %[temp10], %[temp17] \n\t" + "addu %[temp6], %[temp6], %[temp14] \n\t" + "addu %[temp10], %[temp16], %[temp6] \n\t" + "subu %[temp6], %[temp16], %[temp6] \n\t" + "addu %[temp14], %[temp2], %[temp17] \n\t" + "subu %[temp2], %[temp2], %[temp17] \n\t" + "mul %[temp17], %[temp15], %[kC1] \n\t" + "mul %[temp15], %[temp15], %[kC2] \n\t" + "addu %[temp16], %[temp3], %[temp11] \n\t" + "subu %[temp3], %[temp3], %[temp11] \n\t" + "mul %[temp11], %[temp7], %[kC2] \n\t" + "mul %[temp7], %[temp7], %[kC1] \n\t" + "addiu %[temp8], %[temp8], 4 \n\t" + "addiu %[temp12], %[temp12], 4 \n\t" + "addiu %[temp0], %[temp0], 4 \n\t" + "addiu %[temp4], %[temp4], 4 \n\t" + "sra %[temp17], %[temp17], 16 \n\t" + "sra %[temp15], %[temp15], 16 \n\t" + "sra %[temp11], %[temp11], 16 \n\t" + "sra %[temp7], %[temp7], 16 \n\t" + "subu %[temp17], %[temp11], %[temp17] \n\t" + "addu %[temp7], %[temp7], %[temp15] \n\t" + "addu %[temp15], %[temp3], %[temp17] \n\t" + "subu %[temp3], %[temp3], %[temp17] \n\t" + "addu %[temp11], %[temp16], %[temp7] \n\t" + "subu %[temp7], %[temp16], %[temp7] \n\t" + "addu %[temp16], %[temp8], %[temp10] \n\t" + "subu %[temp8], %[temp8], %[temp10] \n\t" + "mul %[temp10], %[temp9], %[kC2] \n\t" + "mul %[temp17], %[temp11], %[kC1] \n\t" + "mul %[temp9], %[temp9], %[kC1] \n\t" + "mul %[temp11], %[temp11], %[kC2] \n\t" + "sra %[temp10], %[temp10], 16 \n\t" + "sra %[temp17], %[temp17], 16 \n\t" + "sra %[temp9], %[temp9], 16 \n\t" + "sra %[temp11], %[temp11], 16 \n\t" + "subu %[temp17], %[temp10], %[temp17] \n\t" + "addu %[temp11], %[temp9], %[temp11] \n\t" + "addu %[temp10], %[temp12], %[temp14] \n\t" + "subu %[temp12], %[temp12], %[temp14] \n\t" + "mul %[temp14], %[temp13], %[kC2] \n\t" + "mul %[temp9], %[temp15], %[kC1] \n\t" + "mul %[temp13], %[temp13], %[kC1] \n\t" + "mul %[temp15], %[temp15], %[kC2] \n\t" + "sra %[temp14], %[temp14], 16 \n\t" + "sra %[temp9], %[temp9], 16 \n\t" + "sra %[temp13], %[temp13], 16 \n\t" + "sra %[temp15], %[temp15], 16 \n\t" + "subu %[temp9], %[temp14], %[temp9] \n\t" + "addu %[temp15], %[temp13], %[temp15] \n\t" + "addu %[temp14], %[temp0], %[temp2] \n\t" + "subu %[temp0], %[temp0], %[temp2] \n\t" + "mul %[temp2], %[temp1], %[kC2] \n\t" + "mul %[temp13], %[temp3], %[kC1] \n\t" + "mul %[temp1], %[temp1], %[kC1] \n\t" + "mul %[temp3], %[temp3], %[kC2] \n\t" + "sra %[temp2], %[temp2], 16 \n\t" + "sra %[temp13], %[temp13], 16 \n\t" + "sra %[temp1], %[temp1], 16 \n\t" + "sra %[temp3], %[temp3], 16 \n\t" + "subu %[temp13], %[temp2], %[temp13] \n\t" + "addu %[temp3], %[temp1], %[temp3] \n\t" + "addu %[temp2], %[temp4], %[temp6] \n\t" + "subu %[temp4], %[temp4], %[temp6] \n\t" + "mul %[temp6], %[temp5], %[kC2] \n\t" + "mul %[temp1], %[temp7], %[kC1] \n\t" + "mul %[temp5], %[temp5], %[kC1] \n\t" + "mul %[temp7], %[temp7], %[kC2] \n\t" + "sra %[temp6], %[temp6], 16 \n\t" + "sra %[temp1], %[temp1], 16 \n\t" + "sra %[temp5], %[temp5], 16 \n\t" + "sra %[temp7], %[temp7], 16 \n\t" + "subu %[temp1], %[temp6], %[temp1] \n\t" + "addu %[temp7], %[temp5], %[temp7] \n\t" + "addu %[temp5], %[temp16], %[temp11] \n\t" + "subu %[temp16], %[temp16], %[temp11] \n\t" + "addu %[temp11], %[temp8], %[temp17] \n\t" + "subu %[temp8], %[temp8], %[temp17] \n\t" + "sra %[temp5], %[temp5], 3 \n\t" + "sra %[temp16], %[temp16], 3 \n\t" + "sra %[temp11], %[temp11], 3 \n\t" + "sra %[temp8], %[temp8], 3 \n\t" + "addu %[temp17], %[temp10], %[temp15] \n\t" + "subu %[temp10], %[temp10], %[temp15] \n\t" + "addu %[temp15], %[temp12], %[temp9] \n\t" + "subu %[temp12], %[temp12], %[temp9] \n\t" + "sra %[temp17], %[temp17], 3 \n\t" + "sra %[temp10], %[temp10], 3 \n\t" + "sra %[temp15], %[temp15], 3 \n\t" + "sra %[temp12], %[temp12], 3 \n\t" + "addu %[temp9], %[temp14], %[temp3] \n\t" + "subu %[temp14], %[temp14], %[temp3] \n\t" + "addu %[temp3], %[temp0], %[temp13] \n\t" + "subu %[temp0], %[temp0], %[temp13] \n\t" + "sra %[temp9], %[temp9], 3 \n\t" + "sra %[temp14], %[temp14], 3 \n\t" + "sra %[temp3], %[temp3], 3 \n\t" + "sra %[temp0], %[temp0], 3 \n\t" + "addu %[temp13], %[temp2], %[temp7] \n\t" + "subu %[temp2], %[temp2], %[temp7] \n\t" + "addu %[temp7], %[temp4], %[temp1] \n\t" + "subu %[temp4], %[temp4], %[temp1] \n\t" + "sra %[temp13], %[temp13], 3 \n\t" + "sra %[temp2], %[temp2], 3 \n\t" + "sra %[temp7], %[temp7], 3 \n\t" + "sra %[temp4], %[temp4], 3 \n\t" + "addiu %[temp6], $zero, 255 \n\t" + "lbu %[temp1], 0+0*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp1], %[temp1], %[temp5] \n\t" + "sra %[temp5], %[temp1], 8 \n\t" + "sra %[temp18], %[temp1], 31 \n\t" + "beqz %[temp5], 1f \n\t" + "xor %[temp1], %[temp1], %[temp1] \n\t" + "movz %[temp1], %[temp6], %[temp18] \n\t" + "1: \n\t" + "lbu %[temp18], 1+0*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp1], 0+0*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp18], %[temp18], %[temp11] \n\t" + "sra %[temp11], %[temp18], 8 \n\t" + "sra %[temp1], %[temp18], 31 \n\t" + "beqz %[temp11], 2f \n\t" + "xor %[temp18], %[temp18], %[temp18] \n\t" + "movz %[temp18], %[temp6], %[temp1] \n\t" + "2: \n\t" + "lbu %[temp1], 2+0*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp18], 1+0*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp1], %[temp1], %[temp8] \n\t" + "sra %[temp8], %[temp1], 8 \n\t" + "sra %[temp18], %[temp1], 31 \n\t" + "beqz %[temp8], 3f \n\t" + "xor %[temp1], %[temp1], %[temp1] \n\t" + "movz %[temp1], %[temp6], %[temp18] \n\t" + "3: \n\t" + "lbu %[temp18], 3+0*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp1], 2+0*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp18], %[temp18], %[temp16] \n\t" + "sra %[temp16], %[temp18], 8 \n\t" + "sra %[temp1], %[temp18], 31 \n\t" + "beqz %[temp16], 4f \n\t" + "xor %[temp18], %[temp18], %[temp18] \n\t" + "movz %[temp18], %[temp6], %[temp1] \n\t" + "4: \n\t" + "sb %[temp18], 3+0*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp5], 0+1*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp8], 1+1*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp11], 2+1*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp16], 3+1*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp5], %[temp5], %[temp17] \n\t" + "addu %[temp8], %[temp8], %[temp15] \n\t" + "addu %[temp11], %[temp11], %[temp12] \n\t" + "addu %[temp16], %[temp16], %[temp10] \n\t" + "sra %[temp18], %[temp5], 8 \n\t" + "sra %[temp1], %[temp5], 31 \n\t" + "beqz %[temp18], 5f \n\t" + "xor %[temp5], %[temp5], %[temp5] \n\t" + "movz %[temp5], %[temp6], %[temp1] \n\t" + "5: \n\t" + "sra %[temp18], %[temp8], 8 \n\t" + "sra %[temp1], %[temp8], 31 \n\t" + "beqz %[temp18], 6f \n\t" + "xor %[temp8], %[temp8], %[temp8] \n\t" + "movz %[temp8], %[temp6], %[temp1] \n\t" + "6: \n\t" + "sra %[temp18], %[temp11], 8 \n\t" + "sra %[temp1], %[temp11], 31 \n\t" + "sra %[temp17], %[temp16], 8 \n\t" + "sra %[temp15], %[temp16], 31 \n\t" + "beqz %[temp18], 7f \n\t" + "xor %[temp11], %[temp11], %[temp11] \n\t" + "movz %[temp11], %[temp6], %[temp1] \n\t" + "7: \n\t" + "beqz %[temp17], 8f \n\t" + "xor %[temp16], %[temp16], %[temp16] \n\t" + "movz %[temp16], %[temp6], %[temp15] \n\t" + "8: \n\t" + "sb %[temp5], 0+1*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp8], 1+1*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp11], 2+1*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp16], 3+1*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp5], 0+2*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp8], 1+2*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp11], 2+2*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp16], 3+2*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp5], %[temp5], %[temp9] \n\t" + "addu %[temp8], %[temp8], %[temp3] \n\t" + "addu %[temp11], %[temp11], %[temp0] \n\t" + "addu %[temp16], %[temp16], %[temp14] \n\t" + "sra %[temp18], %[temp5], 8 \n\t" + "sra %[temp1], %[temp5], 31 \n\t" + "sra %[temp17], %[temp8], 8 \n\t" + "sra %[temp15], %[temp8], 31 \n\t" + "sra %[temp12], %[temp11], 8 \n\t" + "sra %[temp10], %[temp11], 31 \n\t" + "sra %[temp9], %[temp16], 8 \n\t" + "sra %[temp3], %[temp16], 31 \n\t" + "beqz %[temp18], 9f \n\t" + "xor %[temp5], %[temp5], %[temp5] \n\t" + "movz %[temp5], %[temp6], %[temp1] \n\t" + "9: \n\t" + "beqz %[temp17], 10f \n\t" + "xor %[temp8], %[temp8], %[temp8] \n\t" + "movz %[temp8], %[temp6], %[temp15] \n\t" + "10: \n\t" + "beqz %[temp12], 11f \n\t" + "xor %[temp11], %[temp11], %[temp11] \n\t" + "movz %[temp11], %[temp6], %[temp10] \n\t" + "11: \n\t" + "beqz %[temp9], 12f \n\t" + "xor %[temp16], %[temp16], %[temp16] \n\t" + "movz %[temp16], %[temp6], %[temp3] \n\t" + "12: \n\t" + "sb %[temp5], 0+2*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp8], 1+2*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp11], 2+2*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp16], 3+2*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp5], 0+3*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp8], 1+3*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp11], 2+3*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp16], 3+3*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp5], %[temp5], %[temp13] \n\t" + "addu %[temp8], %[temp8], %[temp7] \n\t" + "addu %[temp11], %[temp11], %[temp4] \n\t" + "addu %[temp16], %[temp16], %[temp2] \n\t" + "sra %[temp18], %[temp5], 8 \n\t" + "sra %[temp1], %[temp5], 31 \n\t" + "sra %[temp17], %[temp8], 8 \n\t" + "sra %[temp15], %[temp8], 31 \n\t" + "sra %[temp12], %[temp11], 8 \n\t" + "sra %[temp10], %[temp11], 31 \n\t" + "sra %[temp9], %[temp16], 8 \n\t" + "sra %[temp3], %[temp16], 31 \n\t" + "beqz %[temp18], 13f \n\t" + "xor %[temp5], %[temp5], %[temp5] \n\t" + "movz %[temp5], %[temp6], %[temp1] \n\t" + "13: \n\t" + "beqz %[temp17], 14f \n\t" + "xor %[temp8], %[temp8], %[temp8] \n\t" + "movz %[temp8], %[temp6], %[temp15] \n\t" + "14: \n\t" + "beqz %[temp12], 15f \n\t" + "xor %[temp11], %[temp11], %[temp11] \n\t" + "movz %[temp11], %[temp6], %[temp10] \n\t" + "15: \n\t" + "beqz %[temp9], 16f \n\t" + "xor %[temp16], %[temp16], %[temp16] \n\t" + "movz %[temp16], %[temp6], %[temp3] \n\t" + "16: \n\t" + "sb %[temp5], 0+3*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp8], 1+3*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp11], 2+3*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp16], 3+3*" XSTR(BPS) "(%[dst]) \n\t" + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11), + [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), [temp14]"=&r"(temp14), + [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), [temp17]"=&r"(temp17), + [temp18]"=&r"(temp18) + : [in]"r"(p_in), [kC1]"r"(kC1), [kC2]"r"(kC2), [dst]"r"(dst) + : "memory", "hi", "lo" + ); +} + +static void TransformTwo(const int16_t* in, uint8_t* dst, int do_two) { + TransformOne(in, dst); + if (do_two) { + TransformOne(in + 16, dst + 4); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitMIPS32(void) { + VP8InitClipTables(); + + VP8Transform = TransformTwo; + + VP8VFilter16 = VFilter16; + VP8HFilter16 = HFilter16; + VP8VFilter8 = VFilter8; + VP8HFilter8 = HFilter8; + VP8VFilter16i = VFilter16i; + VP8HFilter16i = HFilter16i; + VP8VFilter8i = VFilter8i; + VP8HFilter8i = HFilter8i; + + VP8SimpleVFilter16 = SimpleVFilter16; + VP8SimpleHFilter16 = SimpleHFilter16; + VP8SimpleVFilter16i = SimpleVFilter16i; + VP8SimpleHFilter16i = SimpleHFilter16i; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(VP8DspInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/3rdparty/libwebp/dsp/dec_mips_dsp_r2.c b/3rdparty/libwebp/dsp/dec_mips_dsp_r2.c new file mode 100644 index 0000000000..db5c657228 --- /dev/null +++ b/3rdparty/libwebp/dsp/dec_mips_dsp_r2.c @@ -0,0 +1,994 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of dsp functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "./mips_macro.h" + +static const int kC1 = 20091 + (1 << 16); +static const int kC2 = 35468; + +#define MUL(a, b) (((a) * (b)) >> 16) + +static void TransformDC(const int16_t* in, uint8_t* dst) { + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9, temp10; + + __asm__ volatile ( + LOAD_WITH_OFFSET_X4(temp1, temp2, temp3, temp4, dst, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + "lh %[temp5], 0(%[in]) \n\t" + "addiu %[temp5], %[temp5], 4 \n\t" + "ins %[temp5], %[temp5], 16, 16 \n\t" + "shra.ph %[temp5], %[temp5], 3 \n\t" + CONVERT_2_BYTES_TO_HALF(temp6, temp7, temp8, temp9, temp10, temp1, temp2, + temp3, temp1, temp2, temp3, temp4) + STORE_SAT_SUM_X2(temp6, temp7, temp8, temp9, temp10, temp1, temp2, temp3, + temp5, temp5, temp5, temp5, temp5, temp5, temp5, temp5, + dst, 0, 1, 2, 3, BPS) + + OUTPUT_EARLY_CLOBBER_REGS_10() + : [in]"r"(in), [dst]"r"(dst) + : "memory" + ); +} + +static void TransformAC3(const int16_t* in, uint8_t* dst) { + const int a = in[0] + 4; + int c4 = MUL(in[4], kC2); + const int d4 = MUL(in[4], kC1); + const int c1 = MUL(in[1], kC2); + const int d1 = MUL(in[1], kC1); + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17, temp18; + + __asm__ volatile ( + "ins %[c4], %[d4], 16, 16 \n\t" + "replv.ph %[temp1], %[a] \n\t" + "replv.ph %[temp4], %[d1] \n\t" + ADD_SUB_HALVES(temp2, temp3, temp1, c4) + "replv.ph %[temp5], %[c1] \n\t" + SHIFT_R_SUM_X2(temp1, temp6, temp7, temp8, temp2, temp9, temp10, temp4, + temp2, temp2, temp3, temp3, temp4, temp5, temp4, temp5) + LOAD_WITH_OFFSET_X4(temp3, temp5, temp11, temp12, dst, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp13, temp14, temp3, temp15, temp5, temp16, + temp11, temp17, temp3, temp5, temp11, temp12) + PACK_2_HALVES_TO_WORD(temp12, temp18, temp7, temp6, temp1, temp8, temp2, + temp4, temp7, temp6, temp10, temp9) + STORE_SAT_SUM_X2(temp13, temp14, temp3, temp15, temp5, temp16, temp11, + temp17, temp12, temp18, temp1, temp8, temp2, temp4, + temp7, temp6, dst, 0, 1, 2, 3, BPS) + + OUTPUT_EARLY_CLOBBER_REGS_18(), + [c4]"+&r"(c4) + : [dst]"r"(dst), [a]"r"(a), [d1]"r"(d1), [d4]"r"(d4), [c1]"r"(c1) + : "memory" + ); +} + +static void TransformOne(const int16_t* in, uint8_t* dst) { + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17, temp18; + + __asm__ volatile ( + "ulw %[temp1], 0(%[in]) \n\t" + "ulw %[temp2], 16(%[in]) \n\t" + LOAD_IN_X2(temp5, temp6, 24, 26) + ADD_SUB_HALVES(temp3, temp4, temp1, temp2) + LOAD_IN_X2(temp1, temp2, 8, 10) + MUL_SHIFT_SUM(temp7, temp8, temp9, temp10, temp11, temp12, temp13, temp14, + temp10, temp8, temp9, temp7, temp1, temp2, temp5, temp6, + temp13, temp11, temp14, temp12) + INSERT_HALF_X2(temp8, temp7, temp10, temp9) + "ulw %[temp17], 4(%[in]) \n\t" + "ulw %[temp18], 20(%[in]) \n\t" + ADD_SUB_HALVES(temp1, temp2, temp3, temp8) + ADD_SUB_HALVES(temp5, temp6, temp4, temp7) + ADD_SUB_HALVES(temp7, temp8, temp17, temp18) + LOAD_IN_X2(temp17, temp18, 12, 14) + LOAD_IN_X2(temp9, temp10, 28, 30) + MUL_SHIFT_SUM(temp11, temp12, temp13, temp14, temp15, temp16, temp4, temp17, + temp12, temp14, temp11, temp13, temp17, temp18, temp9, temp10, + temp15, temp4, temp16, temp17) + INSERT_HALF_X2(temp11, temp12, temp13, temp14) + ADD_SUB_HALVES(temp17, temp8, temp8, temp11) + ADD_SUB_HALVES(temp3, temp4, temp7, temp12) + + // horizontal + SRA_16(temp9, temp10, temp11, temp12, temp1, temp2, temp5, temp6) + INSERT_HALF_X2(temp1, temp6, temp5, temp2) + SRA_16(temp13, temp14, temp15, temp16, temp3, temp4, temp17, temp8) + "repl.ph %[temp2], 0x4 \n\t" + INSERT_HALF_X2(temp3, temp8, temp17, temp4) + "addq.ph %[temp1], %[temp1], %[temp2] \n\t" + "addq.ph %[temp6], %[temp6], %[temp2] \n\t" + ADD_SUB_HALVES(temp2, temp4, temp1, temp3) + ADD_SUB_HALVES(temp5, temp7, temp6, temp8) + MUL_SHIFT_SUM(temp1, temp3, temp6, temp8, temp9, temp13, temp17, temp18, + temp3, temp13, temp1, temp9, temp9, temp13, temp11, temp15, + temp6, temp17, temp8, temp18) + MUL_SHIFT_SUM(temp6, temp8, temp18, temp17, temp11, temp15, temp12, temp16, + temp8, temp15, temp6, temp11, temp12, temp16, temp10, temp14, + temp18, temp12, temp17, temp16) + INSERT_HALF_X2(temp1, temp3, temp9, temp13) + INSERT_HALF_X2(temp6, temp8, temp11, temp15) + SHIFT_R_SUM_X2(temp9, temp10, temp11, temp12, temp13, temp14, temp15, + temp16, temp2, temp4, temp5, temp7, temp3, temp1, temp8, + temp6) + PACK_2_HALVES_TO_WORD(temp1, temp2, temp3, temp4, temp9, temp12, temp13, + temp16, temp11, temp10, temp15, temp14) + LOAD_WITH_OFFSET_X4(temp10, temp11, temp14, temp15, dst, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp5, temp6, temp7, temp8, temp17, temp18, temp10, + temp11, temp10, temp11, temp14, temp15) + STORE_SAT_SUM_X2(temp5, temp6, temp7, temp8, temp17, temp18, temp10, temp11, + temp9, temp12, temp1, temp2, temp13, temp16, temp3, temp4, + dst, 0, 1, 2, 3, BPS) + + OUTPUT_EARLY_CLOBBER_REGS_18() + : [dst]"r"(dst), [in]"r"(in), [kC1]"r"(kC1), [kC2]"r"(kC2) + : "memory", "hi", "lo" + ); +} + +static void TransformTwo(const int16_t* in, uint8_t* dst, int do_two) { + TransformOne(in, dst); + if (do_two) { + TransformOne(in + 16, dst + 4); + } +} + +static WEBP_INLINE void FilterLoop26(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15; + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "1: \n\t" + "negu %[temp1], %[hstride] \n\t" + "addiu %[size], %[size], -1 \n\t" + "sll %[temp2], %[hstride], 1 \n\t" + "sll %[temp3], %[temp1], 1 \n\t" + "addu %[temp4], %[temp2], %[hstride] \n\t" + "addu %[temp5], %[temp3], %[temp1] \n\t" + "lbu %[temp7], 0(%[p]) \n\t" + "sll %[temp6], %[temp3], 1 \n\t" + "lbux %[temp8], %[temp5](%[p]) \n\t" + "lbux %[temp9], %[temp3](%[p]) \n\t" + "lbux %[temp10], %[temp1](%[p]) \n\t" + "lbux %[temp11], %[temp6](%[p]) \n\t" + "lbux %[temp12], %[hstride](%[p]) \n\t" + "lbux %[temp13], %[temp2](%[p]) \n\t" + "lbux %[temp14], %[temp4](%[p]) \n\t" + "subu %[temp1], %[temp10], %[temp7] \n\t" + "subu %[temp2], %[temp9], %[temp12] \n\t" + "absq_s.w %[temp3], %[temp1] \n\t" + "absq_s.w %[temp4], %[temp2] \n\t" + "negu %[temp1], %[temp1] \n\t" + "sll %[temp3], %[temp3], 2 \n\t" + "addu %[temp15], %[temp3], %[temp4] \n\t" + "subu %[temp3], %[temp15], %[thresh2] \n\t" + "sll %[temp6], %[temp1], 1 \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp4], %[temp11], %[temp8] \n\t" + "absq_s.w %[temp4], %[temp4] \n\t" + "shll_s.w %[temp2], %[temp2], 24 \n\t" + "subu %[temp4], %[temp4], %[ithresh] \n\t" + "bgtz %[temp4], 3f \n\t" + " subu %[temp3], %[temp8], %[temp9] \n\t" + "absq_s.w %[temp3], %[temp3] \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp5], %[temp9], %[temp10] \n\t" + "absq_s.w %[temp3], %[temp5] \n\t" + "absq_s.w %[temp5], %[temp5] \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp3], %[temp14], %[temp13] \n\t" + "absq_s.w %[temp3], %[temp3] \n\t" + "slt %[temp5], %[hev_thresh], %[temp5] \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp3], %[temp13], %[temp12] \n\t" + "absq_s.w %[temp3], %[temp3] \n\t" + "sra %[temp4], %[temp2], 24 \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp15], %[temp12], %[temp7] \n\t" + "absq_s.w %[temp3], %[temp15] \n\t" + "absq_s.w %[temp15], %[temp15] \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " slt %[temp15], %[hev_thresh], %[temp15] \n\t" + "addu %[temp3], %[temp6], %[temp1] \n\t" + "or %[temp2], %[temp5], %[temp15] \n\t" + "addu %[temp5], %[temp4], %[temp3] \n\t" + "beqz %[temp2], 4f \n\t" + " shra_r.w %[temp1], %[temp5], 3 \n\t" + "addiu %[temp2], %[temp5], 3 \n\t" + "sra %[temp2], %[temp2], 3 \n\t" + "shll_s.w %[temp1], %[temp1], 27 \n\t" + "shll_s.w %[temp2], %[temp2], 27 \n\t" + "subu %[temp3], %[p], %[hstride] \n\t" + "sra %[temp1], %[temp1], 27 \n\t" + "sra %[temp2], %[temp2], 27 \n\t" + "subu %[temp1], %[temp7], %[temp1] \n\t" + "addu %[temp2], %[temp10], %[temp2] \n\t" + "lbux %[temp2], %[temp2](%[VP8kclip1]) \n\t" + "lbux %[temp1], %[temp1](%[VP8kclip1]) \n\t" + "sb %[temp2], 0(%[temp3]) \n\t" + "j 3f \n\t" + " sb %[temp1], 0(%[p]) \n\t" + "4: \n\t" + "shll_s.w %[temp5], %[temp5], 24 \n\t" + "subu %[temp14], %[p], %[hstride] \n\t" + "subu %[temp11], %[temp14], %[hstride] \n\t" + "sra %[temp6], %[temp5], 24 \n\t" + "sll %[temp1], %[temp6], 3 \n\t" + "subu %[temp15], %[temp11], %[hstride] \n\t" + "addu %[temp2], %[temp6], %[temp1] \n\t" + "sll %[temp3], %[temp2], 1 \n\t" + "addu %[temp4], %[temp3], %[temp2] \n\t" + "addiu %[temp2], %[temp2], 63 \n\t" + "addiu %[temp3], %[temp3], 63 \n\t" + "addiu %[temp4], %[temp4], 63 \n\t" + "sra %[temp2], %[temp2], 7 \n\t" + "sra %[temp3], %[temp3], 7 \n\t" + "sra %[temp4], %[temp4], 7 \n\t" + "addu %[temp1], %[temp8], %[temp2] \n\t" + "addu %[temp5], %[temp9], %[temp3] \n\t" + "addu %[temp6], %[temp10], %[temp4] \n\t" + "subu %[temp8], %[temp7], %[temp4] \n\t" + "subu %[temp7], %[temp12], %[temp3] \n\t" + "addu %[temp10], %[p], %[hstride] \n\t" + "subu %[temp9], %[temp13], %[temp2] \n\t" + "addu %[temp12], %[temp10], %[hstride] \n\t" + "lbux %[temp2], %[temp1](%[VP8kclip1]) \n\t" + "lbux %[temp3], %[temp5](%[VP8kclip1]) \n\t" + "lbux %[temp4], %[temp6](%[VP8kclip1]) \n\t" + "lbux %[temp5], %[temp8](%[VP8kclip1]) \n\t" + "lbux %[temp6], %[temp7](%[VP8kclip1]) \n\t" + "lbux %[temp8], %[temp9](%[VP8kclip1]) \n\t" + "sb %[temp2], 0(%[temp15]) \n\t" + "sb %[temp3], 0(%[temp11]) \n\t" + "sb %[temp4], 0(%[temp14]) \n\t" + "sb %[temp5], 0(%[p]) \n\t" + "sb %[temp6], 0(%[temp10]) \n\t" + "sb %[temp8], 0(%[temp12]) \n\t" + "3: \n\t" + "bgtz %[size], 1b \n\t" + " addu %[p], %[p], %[vstride] \n\t" + ".set pop \n\t" + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2),[temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp6]"=&r"(temp6), + [temp7]"=&r"(temp7),[temp8]"=&r"(temp8),[temp9]"=&r"(temp9), + [temp10]"=&r"(temp10),[temp11]"=&r"(temp11),[temp12]"=&r"(temp12), + [temp13]"=&r"(temp13),[temp14]"=&r"(temp14),[temp15]"=&r"(temp15), + [size]"+&r"(size), [p]"+&r"(p) + : [hstride]"r"(hstride), [thresh2]"r"(thresh2), + [ithresh]"r"(ithresh),[vstride]"r"(vstride), [hev_thresh]"r"(hev_thresh), + [VP8kclip1]"r"(VP8kclip1) + : "memory" + ); +} + +static WEBP_INLINE void FilterLoop24(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, int hev_thresh) { + int p0, q0, p1, q1, p2, q2, p3, q3; + int step1, step2, temp1, temp2, temp3, temp4; + uint8_t* pTemp0; + uint8_t* pTemp1; + const int thresh2 = 2 * thresh + 1; + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "bltz %[size], 3f \n\t" + " nop \n\t" + "2: \n\t" + "negu %[step1], %[hstride] \n\t" + "lbu %[q0], 0(%[p]) \n\t" + "lbux %[p0], %[step1](%[p]) \n\t" + "subu %[step1], %[step1], %[hstride] \n\t" + "lbux %[q1], %[hstride](%[p]) \n\t" + "subu %[temp1], %[p0], %[q0] \n\t" + "lbux %[p1], %[step1](%[p]) \n\t" + "addu %[step2], %[hstride], %[hstride] \n\t" + "absq_s.w %[temp2], %[temp1] \n\t" + "subu %[temp3], %[p1], %[q1] \n\t" + "absq_s.w %[temp4], %[temp3] \n\t" + "sll %[temp2], %[temp2], 2 \n\t" + "addu %[temp2], %[temp2], %[temp4] \n\t" + "subu %[temp4], %[temp2], %[thresh2] \n\t" + "subu %[step1], %[step1], %[hstride] \n\t" + "bgtz %[temp4], 0f \n\t" + " lbux %[p2], %[step1](%[p]) \n\t" + "subu %[step1], %[step1], %[hstride] \n\t" + "lbux %[q2], %[step2](%[p]) \n\t" + "lbux %[p3], %[step1](%[p]) \n\t" + "subu %[temp4], %[p2], %[p1] \n\t" + "addu %[step2], %[step2], %[hstride] \n\t" + "subu %[temp2], %[p3], %[p2] \n\t" + "absq_s.w %[temp4], %[temp4] \n\t" + "absq_s.w %[temp2], %[temp2] \n\t" + "lbux %[q3], %[step2](%[p]) \n\t" + "subu %[temp4], %[temp4], %[ithresh] \n\t" + "negu %[temp1], %[temp1] \n\t" + "bgtz %[temp4], 0f \n\t" + " subu %[temp2], %[temp2], %[ithresh] \n\t" + "subu %[p3], %[p1], %[p0] \n\t" + "bgtz %[temp2], 0f \n\t" + " absq_s.w %[p3], %[p3] \n\t" + "subu %[temp4], %[q3], %[q2] \n\t" + "subu %[pTemp0], %[p], %[hstride] \n\t" + "absq_s.w %[temp4], %[temp4] \n\t" + "subu %[temp2], %[p3], %[ithresh] \n\t" + "sll %[step1], %[temp1], 1 \n\t" + "bgtz %[temp2], 0f \n\t" + " subu %[temp4], %[temp4], %[ithresh] \n\t" + "subu %[temp2], %[q2], %[q1] \n\t" + "bgtz %[temp4], 0f \n\t" + " absq_s.w %[temp2], %[temp2] \n\t" + "subu %[q3], %[q1], %[q0] \n\t" + "absq_s.w %[q3], %[q3] \n\t" + "subu %[temp2], %[temp2], %[ithresh] \n\t" + "addu %[temp1], %[temp1], %[step1] \n\t" + "bgtz %[temp2], 0f \n\t" + " subu %[temp4], %[q3], %[ithresh] \n\t" + "slt %[p3], %[hev_thresh], %[p3] \n\t" + "bgtz %[temp4], 0f \n\t" + " slt %[q3], %[hev_thresh], %[q3] \n\t" + "or %[q3], %[q3], %[p3] \n\t" + "bgtz %[q3], 1f \n\t" + " shra_r.w %[temp2], %[temp1], 3 \n\t" + "addiu %[temp1], %[temp1], 3 \n\t" + "sra %[temp1], %[temp1], 3 \n\t" + "shll_s.w %[temp2], %[temp2], 27 \n\t" + "shll_s.w %[temp1], %[temp1], 27 \n\t" + "addu %[pTemp1], %[p], %[hstride] \n\t" + "sra %[temp2], %[temp2], 27 \n\t" + "sra %[temp1], %[temp1], 27 \n\t" + "addiu %[step1], %[temp2], 1 \n\t" + "sra %[step1], %[step1], 1 \n\t" + "addu %[p0], %[p0], %[temp1] \n\t" + "addu %[p1], %[p1], %[step1] \n\t" + "subu %[q0], %[q0], %[temp2] \n\t" + "subu %[q1], %[q1], %[step1] \n\t" + "lbux %[temp2], %[p0](%[VP8kclip1]) \n\t" + "lbux %[temp3], %[q0](%[VP8kclip1]) \n\t" + "lbux %[temp4], %[q1](%[VP8kclip1]) \n\t" + "sb %[temp2], 0(%[pTemp0]) \n\t" + "lbux %[temp1], %[p1](%[VP8kclip1]) \n\t" + "subu %[pTemp0], %[pTemp0], %[hstride] \n\t" + "sb %[temp3], 0(%[p]) \n\t" + "sb %[temp4], 0(%[pTemp1]) \n\t" + "j 0f \n\t" + " sb %[temp1], 0(%[pTemp0]) \n\t" + "1: \n\t" + "shll_s.w %[temp3], %[temp3], 24 \n\t" + "sra %[temp3], %[temp3], 24 \n\t" + "addu %[temp1], %[temp1], %[temp3] \n\t" + "shra_r.w %[temp2], %[temp1], 3 \n\t" + "addiu %[temp1], %[temp1], 3 \n\t" + "shll_s.w %[temp2], %[temp2], 27 \n\t" + "sra %[temp1], %[temp1], 3 \n\t" + "shll_s.w %[temp1], %[temp1], 27 \n\t" + "sra %[temp2], %[temp2], 27 \n\t" + "sra %[temp1], %[temp1], 27 \n\t" + "addu %[p0], %[p0], %[temp1] \n\t" + "subu %[q0], %[q0], %[temp2] \n\t" + "lbux %[temp1], %[p0](%[VP8kclip1]) \n\t" + "lbux %[temp2], %[q0](%[VP8kclip1]) \n\t" + "sb %[temp2], 0(%[p]) \n\t" + "sb %[temp1], 0(%[pTemp0]) \n\t" + "0: \n\t" + "subu %[size], %[size], 1 \n\t" + "bgtz %[size], 2b \n\t" + " addu %[p], %[p], %[vstride] \n\t" + "3: \n\t" + ".set pop \n\t" + : [p0]"=&r"(p0), [q0]"=&r"(q0), [p1]"=&r"(p1), [q1]"=&r"(q1), + [p2]"=&r"(p2), [q2]"=&r"(q2), [p3]"=&r"(p3), [q3]"=&r"(q3), + [step2]"=&r"(step2), [step1]"=&r"(step1), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), + [pTemp0]"=&r"(pTemp0), [pTemp1]"=&r"(pTemp1), [p]"+&r"(p), + [size]"+&r"(size) + : [vstride]"r"(vstride), [ithresh]"r"(ithresh), + [hev_thresh]"r"(hev_thresh), [hstride]"r"(hstride), + [VP8kclip1]"r"(VP8kclip1), [thresh2]"r"(thresh2) + : "memory" + ); +} + +// on macroblock edges +static void VFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(p, stride, 1, 16, thresh, ithresh, hev_thresh); +} + +static void HFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(p, 1, stride, 16, thresh, ithresh, hev_thresh); +} + +// 8-pixels wide variant, for chroma filtering +static void VFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(u, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop26(v, stride, 1, 8, thresh, ithresh, hev_thresh); +} + +static void HFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(u, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop26(v, 1, stride, 8, thresh, ithresh, hev_thresh); +} + +// on three inner edges +static void VFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + FilterLoop24(p, stride, 1, 16, thresh, ithresh, hev_thresh); + } +} + +static void HFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + FilterLoop24(p, 1, stride, 16, thresh, ithresh, hev_thresh); + } +} + +static void VFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24(u + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop24(v + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); +} + +static void HFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24(u + 4, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop24(v + 4, 1, stride, 8, thresh, ithresh, hev_thresh); +} + +#undef MUL + +//------------------------------------------------------------------------------ +// Simple In-loop filtering (Paragraph 15.2) + +static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + uint8_t* p1 = p - stride; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "li %[i], 16 \n\t" + "0: \n\t" + "negu %[temp4], %[stride] \n\t" + "sll %[temp5], %[temp4], 1 \n\t" + "lbu %[temp2], 0(%[p]) \n\t" + "lbux %[temp3], %[stride](%[p]) \n\t" + "lbux %[temp1], %[temp4](%[p]) \n\t" + "lbux %[temp0], %[temp5](%[p]) \n\t" + "subu %[temp7], %[temp1], %[temp2] \n\t" + "subu %[temp6], %[temp0], %[temp3] \n\t" + "absq_s.w %[temp4], %[temp7] \n\t" + "absq_s.w %[temp5], %[temp6] \n\t" + "sll %[temp4], %[temp4], 2 \n\t" + "subu %[temp5], %[temp5], %[thresh2] \n\t" + "addu %[temp5], %[temp4], %[temp5] \n\t" + "negu %[temp8], %[temp7] \n\t" + "bgtz %[temp5], 1f \n\t" + " addiu %[i], %[i], -1 \n\t" + "sll %[temp4], %[temp8], 1 \n\t" + "shll_s.w %[temp5], %[temp6], 24 \n\t" + "addu %[temp3], %[temp4], %[temp8] \n\t" + "sra %[temp5], %[temp5], 24 \n\t" + "addu %[temp3], %[temp3], %[temp5] \n\t" + "addiu %[temp7], %[temp3], 3 \n\t" + "sra %[temp7], %[temp7], 3 \n\t" + "shra_r.w %[temp8], %[temp3], 3 \n\t" + "shll_s.w %[temp0], %[temp7], 27 \n\t" + "shll_s.w %[temp4], %[temp8], 27 \n\t" + "sra %[temp0], %[temp0], 27 \n\t" + "sra %[temp4], %[temp4], 27 \n\t" + "addu %[temp7], %[temp1], %[temp0] \n\t" + "subu %[temp2], %[temp2], %[temp4] \n\t" + "lbux %[temp3], %[temp7](%[VP8kclip1]) \n\t" + "lbux %[temp4], %[temp2](%[VP8kclip1]) \n\t" + "sb %[temp3], 0(%[p1]) \n\t" + "sb %[temp4], 0(%[p]) \n\t" + "1: \n\t" + "addiu %[p1], %[p1], 1 \n\t" + "bgtz %[i], 0b \n\t" + " addiu %[p], %[p], 1 \n\t" + " .set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [p]"+&r"(p), [i]"=&r"(i), [p1]"+&r"(p1) + : [stride]"r"(stride), [VP8kclip1]"r"(VP8kclip1), [thresh2]"r"(thresh2) + : "memory" + ); +} + +// TEMP0 = SRC[A + A1 * BPS] +// TEMP1 = SRC[B + B1 * BPS] +// TEMP2 = SRC[C + C1 * BPS] +// TEMP3 = SRC[D + D1 * BPS] +#define LOAD_4_BYTES(TEMP0, TEMP1, TEMP2, TEMP3, \ + A, A1, B, B1, C, C1, D, D1, SRC) \ + "lbu %[" #TEMP0 "], " #A "+" #A1 "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + "lbu %[" #TEMP1 "], " #B "+" #B1 "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + "lbu %[" #TEMP2 "], " #C "+" #C1 "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + "lbu %[" #TEMP3 "], " #D "+" #D1 "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + +static void SimpleHFilter16(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "li %[i], 16 \n\t" + "0: \n\t" + LOAD_4_BYTES(temp0, temp1, temp2, temp3, -2, 0, -1, 0, 0, 0, 1, 0, p) + "subu %[temp7], %[temp1], %[temp2] \n\t" + "subu %[temp6], %[temp0], %[temp3] \n\t" + "absq_s.w %[temp4], %[temp7] \n\t" + "absq_s.w %[temp5], %[temp6] \n\t" + "sll %[temp4], %[temp4], 2 \n\t" + "addu %[temp5], %[temp4], %[temp5] \n\t" + "subu %[temp5], %[temp5], %[thresh2] \n\t" + "negu %[temp8], %[temp7] \n\t" + "bgtz %[temp5], 1f \n\t" + " addiu %[i], %[i], -1 \n\t" + "sll %[temp4], %[temp8], 1 \n\t" + "shll_s.w %[temp5], %[temp6], 24 \n\t" + "addu %[temp3], %[temp4], %[temp8] \n\t" + "sra %[temp5], %[temp5], 24 \n\t" + "addu %[temp3], %[temp3], %[temp5] \n\t" + "addiu %[temp7], %[temp3], 3 \n\t" + "sra %[temp7], %[temp7], 3 \n\t" + "shra_r.w %[temp8], %[temp3], 3 \n\t" + "shll_s.w %[temp0], %[temp7], 27 \n\t" + "shll_s.w %[temp4], %[temp8], 27 \n\t" + "sra %[temp0], %[temp0], 27 \n\t" + "sra %[temp4], %[temp4], 27 \n\t" + "addu %[temp7], %[temp1], %[temp0] \n\t" + "subu %[temp2], %[temp2], %[temp4] \n\t" + "lbux %[temp3], %[temp7](%[VP8kclip1]) \n\t" + "lbux %[temp4], %[temp2](%[VP8kclip1]) \n\t" + "sb %[temp3], -1(%[p]) \n\t" + "sb %[temp4], 0(%[p]) \n\t" + "1: \n\t" + "bgtz %[i], 0b \n\t" + " addu %[p], %[p], %[stride] \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [p]"+&r"(p), [i]"=&r"(i) + : [stride]"r"(stride), [VP8kclip1]"r"(VP8kclip1), [thresh2]"r"(thresh2) + : "memory" + ); +} + +static void SimpleVFilter16i(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + SimpleVFilter16(p, stride, thresh); + } +} + +static void SimpleHFilter16i(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + SimpleHFilter16(p, stride, thresh); + } +} + +// DST[A * BPS] = TEMP0 +// DST[B + C * BPS] = TEMP1 +#define STORE_8_BYTES(TEMP0, TEMP1, A, B, C, DST) \ + "usw %[" #TEMP0 "], " #A "*" XSTR(BPS) "(%[" #DST "]) \n\t" \ + "usw %[" #TEMP1 "], " #B "+" #C "*" XSTR(BPS) "(%[" #DST "]) \n\t" + +static void VE4(uint8_t* dst) { // vertical + const uint8_t* top = dst - BPS; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + __asm__ volatile ( + "ulw %[temp0], -1(%[top]) \n\t" + "ulh %[temp1], 3(%[top]) \n\t" + "preceu.ph.qbr %[temp2], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "packrl.ph %[temp5], %[temp3], %[temp2] \n\t" + "packrl.ph %[temp6], %[temp4], %[temp3] \n\t" + "shll.ph %[temp5], %[temp5], 1 \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp2], %[temp5], %[temp2] \n\t" + "addq.ph %[temp6], %[temp6], %[temp4] \n\t" + "addq.ph %[temp2], %[temp2], %[temp3] \n\t" + "addq.ph %[temp6], %[temp6], %[temp3] \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precr.qb.ph %[temp4], %[temp6], %[temp2] \n\t" + STORE_8_BYTES(temp4, temp4, 0, 0, 1, dst) + STORE_8_BYTES(temp4, temp4, 2, 0, 3, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void DC4(uint8_t* dst) { // DC + int temp0, temp1, temp2, temp3, temp4; + __asm__ volatile ( + "ulw %[temp0], -1*" XSTR(BPS) "(%[dst]) \n\t" + LOAD_4_BYTES(temp1, temp2, temp3, temp4, -1, 0, -1, 1, -1, 2, -1, 3, dst) + "ins %[temp1], %[temp2], 8, 8 \n\t" + "ins %[temp1], %[temp3], 16, 8 \n\t" + "ins %[temp1], %[temp4], 24, 8 \n\t" + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "shra_r.w %[temp0], %[temp0], 3 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + STORE_8_BYTES(temp0, temp0, 0, 0, 1, dst) + STORE_8_BYTES(temp0, temp0, 2, 0, 3, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4) + : [dst]"r"(dst) + : "memory" + ); +} + +static void RD4(uint8_t* dst) { // Down-right + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8; + __asm__ volatile ( + LOAD_4_BYTES(temp0, temp1, temp2, temp3, -1, 0, -1, 1, -1, 2, -1, 3, dst) + "ulw %[temp7], -1-" XSTR(BPS) "(%[dst]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "preceu.ph.qbr %[temp5], %[temp7] \n\t" + "ins %[temp2], %[temp1], 16, 16 \n\t" + "preceu.ph.qbl %[temp4], %[temp7] \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "shll.ph %[temp2], %[temp2], 1 \n\t" + "addq.ph %[temp3], %[temp3], %[temp1] \n\t" + "packrl.ph %[temp6], %[temp5], %[temp1] \n\t" + "addq.ph %[temp3], %[temp3], %[temp2] \n\t" + "addq.ph %[temp1], %[temp1], %[temp5] \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp1], %[temp1], %[temp6] \n\t" + "packrl.ph %[temp0], %[temp4], %[temp5] \n\t" + "addq.ph %[temp8], %[temp5], %[temp4] \n\t" + "shra_r.ph %[temp3], %[temp3], 2 \n\t" + "shll.ph %[temp0], %[temp0], 1 \n\t" + "shra_r.ph %[temp1], %[temp1], 2 \n\t" + "addq.ph %[temp8], %[temp0], %[temp8] \n\t" + "lbu %[temp5], 3-" XSTR(BPS) "(%[dst]) \n\t" + "precrq.ph.w %[temp7], %[temp7], %[temp7] \n\t" + "shra_r.ph %[temp8], %[temp8], 2 \n\t" + "ins %[temp7], %[temp5], 0, 8 \n\t" + "precr.qb.ph %[temp2], %[temp1], %[temp3] \n\t" + "raddu.w.qb %[temp4], %[temp7] \n\t" + "precr.qb.ph %[temp6], %[temp8], %[temp1] \n\t" + "shra_r.w %[temp4], %[temp4], 2 \n\t" + STORE_8_BYTES(temp2, temp6, 3, 0, 1, dst) + "prepend %[temp2], %[temp8], 8 \n\t" + "prepend %[temp6], %[temp4], 8 \n\t" + STORE_8_BYTES(temp2, temp6, 2, 0, 0, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8) + : [dst]"r"(dst) + : "memory" + ); +} + +// TEMP0 = SRC[A * BPS] +// TEMP1 = SRC[B + C * BPS] +#define LOAD_8_BYTES(TEMP0, TEMP1, A, B, C, SRC) \ + "ulw %[" #TEMP0 "], " #A "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + "ulw %[" #TEMP1 "], " #B "+" #C "*" XSTR(BPS) "(%[" #SRC "]) \n\t" + +static void LD4(uint8_t* dst) { // Down-Left + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + LOAD_8_BYTES(temp0, temp1, -1, 4, -1, dst) + "preceu.ph.qbl %[temp2], %[temp0] \n\t" + "preceu.ph.qbr %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "preceu.ph.qbl %[temp5], %[temp1] \n\t" + "packrl.ph %[temp6], %[temp2], %[temp3] \n\t" + "packrl.ph %[temp7], %[temp4], %[temp2] \n\t" + "packrl.ph %[temp8], %[temp5], %[temp4] \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp9], %[temp2], %[temp6] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "addq.ph %[temp9], %[temp9], %[temp3] \n\t" + "shll.ph %[temp8], %[temp8], 1 \n\t" + "shra_r.ph %[temp9], %[temp9], 2 \n\t" + "addq.ph %[temp3], %[temp4], %[temp7] \n\t" + "addq.ph %[temp0], %[temp5], %[temp8] \n\t" + "addq.ph %[temp3], %[temp3], %[temp2] \n\t" + "addq.ph %[temp0], %[temp0], %[temp4] \n\t" + "shra_r.ph %[temp3], %[temp3], 2 \n\t" + "shra_r.ph %[temp0], %[temp0], 2 \n\t" + "srl %[temp1], %[temp1], 24 \n\t" + "sll %[temp1], %[temp1], 1 \n\t" + "raddu.w.qb %[temp5], %[temp5] \n\t" + "precr.qb.ph %[temp9], %[temp3], %[temp9] \n\t" + "precr.qb.ph %[temp3], %[temp0], %[temp3] \n\t" + "addu %[temp1], %[temp1], %[temp5] \n\t" + "shra_r.w %[temp1], %[temp1], 2 \n\t" + STORE_8_BYTES(temp9, temp3, 0, 0, 2, dst) + "prepend %[temp9], %[temp0], 8 \n\t" + "prepend %[temp3], %[temp1], 8 \n\t" + STORE_8_BYTES(temp9, temp3, 1, 0, 3, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [dst]"r"(dst) + : "memory" + ); +} + +//------------------------------------------------------------------------------ +// Chroma + +static void DC8uv(uint8_t* dst) { // DC + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + LOAD_8_BYTES(temp0, temp1, -1, 4, -1, dst) + LOAD_4_BYTES(temp2, temp3, temp4, temp5, -1, 0, -1, 1, -1, 2, -1, 3, dst) + LOAD_4_BYTES(temp6, temp7, temp8, temp9, -1, 4, -1, 5, -1, 6, -1, 7, dst) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[temp4], %[temp4], %[temp5] \n\t" + "addu %[temp6], %[temp6], %[temp7] \n\t" + "addu %[temp8], %[temp8], %[temp9] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp4] \n\t" + "addu %[temp6], %[temp6], %[temp8] \n\t" + "addu %[temp0], %[temp0], %[temp2] \n\t" + "addu %[temp0], %[temp0], %[temp6] \n\t" + "shra_r.w %[temp0], %[temp0], 4 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + STORE_8_BYTES(temp0, temp0, 0, 4, 0, dst) + STORE_8_BYTES(temp0, temp0, 1, 4, 1, dst) + STORE_8_BYTES(temp0, temp0, 2, 4, 2, dst) + STORE_8_BYTES(temp0, temp0, 3, 4, 3, dst) + STORE_8_BYTES(temp0, temp0, 4, 4, 4, dst) + STORE_8_BYTES(temp0, temp0, 5, 4, 5, dst) + STORE_8_BYTES(temp0, temp0, 6, 4, 6, dst) + STORE_8_BYTES(temp0, temp0, 7, 4, 7, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [dst]"r"(dst) + : "memory" + ); +} + +static void DC8uvNoLeft(uint8_t* dst) { // DC with no left samples + int temp0, temp1; + __asm__ volatile ( + LOAD_8_BYTES(temp0, temp1, -1, 4, -1, dst) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "shra_r.w %[temp0], %[temp0], 3 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + STORE_8_BYTES(temp0, temp0, 0, 4, 0, dst) + STORE_8_BYTES(temp0, temp0, 1, 4, 1, dst) + STORE_8_BYTES(temp0, temp0, 2, 4, 2, dst) + STORE_8_BYTES(temp0, temp0, 3, 4, 3, dst) + STORE_8_BYTES(temp0, temp0, 4, 4, 4, dst) + STORE_8_BYTES(temp0, temp0, 5, 4, 5, dst) + STORE_8_BYTES(temp0, temp0, 6, 4, 6, dst) + STORE_8_BYTES(temp0, temp0, 7, 4, 7, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1) + : [dst]"r"(dst) + : "memory" + ); +} + +static void DC8uvNoTop(uint8_t* dst) { // DC with no top samples + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8; + __asm__ volatile ( + LOAD_4_BYTES(temp2, temp3, temp4, temp5, -1, 0, -1, 1, -1, 2, -1, 3, dst) + LOAD_4_BYTES(temp6, temp7, temp8, temp1, -1, 4, -1, 5, -1, 6, -1, 7, dst) + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[temp4], %[temp4], %[temp5] \n\t" + "addu %[temp6], %[temp6], %[temp7] \n\t" + "addu %[temp8], %[temp8], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp4] \n\t" + "addu %[temp6], %[temp6], %[temp8] \n\t" + "addu %[temp0], %[temp6], %[temp2] \n\t" + "shra_r.w %[temp0], %[temp0], 3 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + STORE_8_BYTES(temp0, temp0, 0, 4, 0, dst) + STORE_8_BYTES(temp0, temp0, 1, 4, 1, dst) + STORE_8_BYTES(temp0, temp0, 2, 4, 2, dst) + STORE_8_BYTES(temp0, temp0, 3, 4, 3, dst) + STORE_8_BYTES(temp0, temp0, 4, 4, 4, dst) + STORE_8_BYTES(temp0, temp0, 5, 4, 5, dst) + STORE_8_BYTES(temp0, temp0, 6, 4, 6, dst) + STORE_8_BYTES(temp0, temp0, 7, 4, 7, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8) + : [dst]"r"(dst) + : "memory" + ); +} + +#undef LOAD_8_BYTES +#undef STORE_8_BYTES +#undef LOAD_4_BYTES + +#define CLIPPING(SIZE) \ + "preceu.ph.qbl %[temp2], %[temp0] \n\t" \ + "preceu.ph.qbr %[temp0], %[temp0] \n\t" \ +".if " #SIZE " == 8 \n\t" \ + "preceu.ph.qbl %[temp3], %[temp1] \n\t" \ + "preceu.ph.qbr %[temp1], %[temp1] \n\t" \ +".endif \n\t" \ + "addu.ph %[temp2], %[temp2], %[dst_1] \n\t" \ + "addu.ph %[temp0], %[temp0], %[dst_1] \n\t" \ +".if " #SIZE " == 8 \n\t" \ + "addu.ph %[temp3], %[temp3], %[dst_1] \n\t" \ + "addu.ph %[temp1], %[temp1], %[dst_1] \n\t" \ +".endif \n\t" \ + "shll_s.ph %[temp2], %[temp2], 7 \n\t" \ + "shll_s.ph %[temp0], %[temp0], 7 \n\t" \ +".if " #SIZE " == 8 \n\t" \ + "shll_s.ph %[temp3], %[temp3], 7 \n\t" \ + "shll_s.ph %[temp1], %[temp1], 7 \n\t" \ +".endif \n\t" \ + "precrqu_s.qb.ph %[temp0], %[temp2], %[temp0] \n\t" \ +".if " #SIZE " == 8 \n\t" \ + "precrqu_s.qb.ph %[temp1], %[temp3], %[temp1] \n\t" \ +".endif \n\t" + + +#define CLIP_8B_TO_DST(DST, TOP, SIZE) do { \ + int dst_1 = ((int)(DST)[-1] << 16) + (DST)[-1]; \ + int temp0, temp1, temp2, temp3; \ + __asm__ volatile ( \ + ".if " #SIZE " < 8 \n\t" \ + "ulw %[temp0], 0(%[top]) \n\t" \ + "subu.ph %[dst_1], %[dst_1], %[top_1] \n\t" \ + CLIPPING(4) \ + "usw %[temp0], 0(%[dst]) \n\t" \ + ".else \n\t" \ + "ulw %[temp0], 0(%[top]) \n\t" \ + "ulw %[temp1], 4(%[top]) \n\t" \ + "subu.ph %[dst_1], %[dst_1], %[top_1] \n\t" \ + CLIPPING(8) \ + "usw %[temp0], 0(%[dst]) \n\t" \ + "usw %[temp1], 4(%[dst]) \n\t" \ + ".if " #SIZE " == 16 \n\t" \ + "ulw %[temp0], 8(%[top]) \n\t" \ + "ulw %[temp1], 12(%[top]) \n\t" \ + CLIPPING(8) \ + "usw %[temp0], 8(%[dst]) \n\t" \ + "usw %[temp1], 12(%[dst]) \n\t" \ + ".endif \n\t" \ + ".endif \n\t" \ + : [dst_1]"+&r"(dst_1), [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), \ + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3) \ + : [top_1]"r"(top_1), [top]"r"((TOP)), [dst]"r"((DST)) \ + : "memory" \ + ); \ +} while (0) + +#define CLIP_TO_DST(DST, SIZE) do { \ + int y; \ + const uint8_t* top = (DST) - BPS; \ + const int top_1 = ((int)top[-1] << 16) + top[-1]; \ + for (y = 0; y < (SIZE); ++y) { \ + CLIP_8B_TO_DST((DST), top, (SIZE)); \ + (DST) += BPS; \ + } \ +} while (0) + +#define TRUE_MOTION(DST, SIZE) \ +static void TrueMotion##SIZE(uint8_t* (DST)) { \ + CLIP_TO_DST((DST), (SIZE)); \ +} + +TRUE_MOTION(dst, 4) +TRUE_MOTION(dst, 8) +TRUE_MOTION(dst, 16) + +#undef TRUE_MOTION +#undef CLIP_TO_DST +#undef CLIP_8B_TO_DST +#undef CLIPPING + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitMIPSdspR2(void) { + VP8TransformDC = TransformDC; + VP8TransformAC3 = TransformAC3; + VP8Transform = TransformTwo; + + VP8VFilter16 = VFilter16; + VP8HFilter16 = HFilter16; + VP8VFilter8 = VFilter8; + VP8HFilter8 = HFilter8; + VP8VFilter16i = VFilter16i; + VP8HFilter16i = HFilter16i; + VP8VFilter8i = VFilter8i; + VP8HFilter8i = HFilter8i; + VP8SimpleVFilter16 = SimpleVFilter16; + VP8SimpleHFilter16 = SimpleHFilter16; + VP8SimpleVFilter16i = SimpleVFilter16i; + VP8SimpleHFilter16i = SimpleHFilter16i; + + VP8PredLuma4[0] = DC4; + VP8PredLuma4[1] = TrueMotion4; + VP8PredLuma4[2] = VE4; + VP8PredLuma4[4] = RD4; + VP8PredLuma4[6] = LD4; + + VP8PredChroma8[0] = DC8uv; + VP8PredChroma8[1] = TrueMotion8; + VP8PredChroma8[4] = DC8uvNoTop; + VP8PredChroma8[5] = DC8uvNoLeft; + + VP8PredLuma16[1] = TrueMotion16; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8DspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/dec_msa.c b/3rdparty/libwebp/dsp/dec_msa.c new file mode 100644 index 0000000000..8d9c98c3cf --- /dev/null +++ b/3rdparty/libwebp/dsp/dec_msa.c @@ -0,0 +1,1019 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA version of dsp functions +// +// Author(s): Prashant Patil (prashant.patil@imgtec.com) + + +#include "./dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "./msa_macro.h" + +//------------------------------------------------------------------------------ +// Transforms + +#define IDCT_1D_W(in0, in1, in2, in3, out0, out1, out2, out3) { \ + v4i32 a1_m, b1_m, c1_m, d1_m; \ + v4i32 c_tmp1_m, c_tmp2_m, d_tmp1_m, d_tmp2_m; \ + const v4i32 cospi8sqrt2minus1 = __msa_fill_w(20091); \ + const v4i32 sinpi8sqrt2 = __msa_fill_w(35468); \ + \ + a1_m = in0 + in2; \ + b1_m = in0 - in2; \ + c_tmp1_m = (in1 * sinpi8sqrt2) >> 16; \ + c_tmp2_m = in3 + ((in3 * cospi8sqrt2minus1) >> 16); \ + c1_m = c_tmp1_m - c_tmp2_m; \ + d_tmp1_m = in1 + ((in1 * cospi8sqrt2minus1) >> 16); \ + d_tmp2_m = (in3 * sinpi8sqrt2) >> 16; \ + d1_m = d_tmp1_m + d_tmp2_m; \ + BUTTERFLY_4(a1_m, b1_m, c1_m, d1_m, out0, out1, out2, out3); \ +} +#define MULT1(a) ((((a) * 20091) >> 16) + (a)) +#define MULT2(a) (((a) * 35468) >> 16) + +static void TransformOne(const int16_t* in, uint8_t* dst) { + v8i16 input0, input1; + v4i32 in0, in1, in2, in3, hz0, hz1, hz2, hz3, vt0, vt1, vt2, vt3; + v4i32 res0, res1, res2, res3; + const v16i8 zero = { 0 }; + v16i8 dest0, dest1, dest2, dest3; + + LD_SH2(in, 8, input0, input1); + UNPCK_SH_SW(input0, in0, in1); + UNPCK_SH_SW(input1, in2, in3); + IDCT_1D_W(in0, in1, in2, in3, hz0, hz1, hz2, hz3); + TRANSPOSE4x4_SW_SW(hz0, hz1, hz2, hz3, hz0, hz1, hz2, hz3); + IDCT_1D_W(hz0, hz1, hz2, hz3, vt0, vt1, vt2, vt3); + SRARI_W4_SW(vt0, vt1, vt2, vt3, 3); + TRANSPOSE4x4_SW_SW(vt0, vt1, vt2, vt3, vt0, vt1, vt2, vt3); + LD_SB4(dst, BPS, dest0, dest1, dest2, dest3); + ILVR_B4_SW(zero, dest0, zero, dest1, zero, dest2, zero, dest3, + res0, res1, res2, res3); + ILVR_H4_SW(zero, res0, zero, res1, zero, res2, zero, res3, + res0, res1, res2, res3); + ADD4(res0, vt0, res1, vt1, res2, vt2, res3, vt3, res0, res1, res2, res3); + CLIP_SW4_0_255(res0, res1, res2, res3); + PCKEV_B2_SW(res0, res1, res2, res3, vt0, vt1); + res0 = (v4i32)__msa_pckev_b((v16i8)vt0, (v16i8)vt1); + ST4x4_UB(res0, res0, 3, 2, 1, 0, dst, BPS); +} + +static void TransformTwo(const int16_t* in, uint8_t* dst, int do_two) { + TransformOne(in, dst); + if (do_two) { + TransformOne(in + 16, dst + 4); + } +} + +static void TransformWHT(const int16_t* in, int16_t* out) { + v8i16 input0, input1; + const v8i16 mask0 = { 0, 1, 2, 3, 8, 9, 10, 11 }; + const v8i16 mask1 = { 4, 5, 6, 7, 12, 13, 14, 15 }; + const v8i16 mask2 = { 0, 4, 8, 12, 1, 5, 9, 13 }; + const v8i16 mask3 = { 3, 7, 11, 15, 2, 6, 10, 14 }; + v8i16 tmp0, tmp1, tmp2, tmp3; + v8i16 out0, out1; + + LD_SH2(in, 8, input0, input1); + input1 = SLDI_SH(input1, input1, 8); + tmp0 = input0 + input1; + tmp1 = input0 - input1; + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + out0 = tmp2 + tmp3; + out1 = tmp2 - tmp3; + VSHF_H2_SH(out0, out1, out0, out1, mask2, mask3, input0, input1); + tmp0 = input0 + input1; + tmp1 = input0 - input1; + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + tmp0 = tmp2 + tmp3; + tmp1 = tmp2 - tmp3; + ADDVI_H2_SH(tmp0, 3, tmp1, 3, out0, out1); + SRAI_H2_SH(out0, out1, 3); + out[0] = __msa_copy_s_h(out0, 0); + out[16] = __msa_copy_s_h(out0, 4); + out[32] = __msa_copy_s_h(out1, 0); + out[48] = __msa_copy_s_h(out1, 4); + out[64] = __msa_copy_s_h(out0, 1); + out[80] = __msa_copy_s_h(out0, 5); + out[96] = __msa_copy_s_h(out1, 1); + out[112] = __msa_copy_s_h(out1, 5); + out[128] = __msa_copy_s_h(out0, 2); + out[144] = __msa_copy_s_h(out0, 6); + out[160] = __msa_copy_s_h(out1, 2); + out[176] = __msa_copy_s_h(out1, 6); + out[192] = __msa_copy_s_h(out0, 3); + out[208] = __msa_copy_s_h(out0, 7); + out[224] = __msa_copy_s_h(out1, 3); + out[240] = __msa_copy_s_h(out1, 7); +} + +static void TransformDC(const int16_t* in, uint8_t* dst) { + const int DC = (in[0] + 4) >> 3; + const v8i16 tmp0 = __msa_fill_h(DC); + ADDBLK_ST4x4_UB(tmp0, tmp0, tmp0, tmp0, dst, BPS); +} + +static void TransformAC3(const int16_t* in, uint8_t* dst) { + const int a = in[0] + 4; + const int c4 = MULT2(in[4]); + const int d4 = MULT1(in[4]); + const int in2 = MULT2(in[1]); + const int in3 = MULT1(in[1]); + v4i32 tmp0 = { 0 }; + v4i32 out0 = __msa_fill_w(a + d4); + v4i32 out1 = __msa_fill_w(a + c4); + v4i32 out2 = __msa_fill_w(a - c4); + v4i32 out3 = __msa_fill_w(a - d4); + v4i32 res0, res1, res2, res3; + const v4i32 zero = { 0 }; + v16u8 dest0, dest1, dest2, dest3; + + INSERT_W4_SW(in3, in2, -in2, -in3, tmp0); + ADD4(out0, tmp0, out1, tmp0, out2, tmp0, out3, tmp0, + out0, out1, out2, out3); + SRAI_W4_SW(out0, out1, out2, out3, 3); + LD_UB4(dst, BPS, dest0, dest1, dest2, dest3); + ILVR_B4_SW(zero, dest0, zero, dest1, zero, dest2, zero, dest3, + res0, res1, res2, res3); + ILVR_H4_SW(zero, res0, zero, res1, zero, res2, zero, res3, + res0, res1, res2, res3); + ADD4(res0, out0, res1, out1, res2, out2, res3, out3, res0, res1, res2, res3); + CLIP_SW4_0_255(res0, res1, res2, res3); + PCKEV_B2_SW(res0, res1, res2, res3, out0, out1); + res0 = (v4i32)__msa_pckev_b((v16i8)out0, (v16i8)out1); + ST4x4_UB(res0, res0, 3, 2, 1, 0, dst, BPS); +} + +//------------------------------------------------------------------------------ +// Edge filtering functions + +#define FLIP_SIGN2(in0, in1, out0, out1) { \ + out0 = (v16i8)__msa_xori_b(in0, 0x80); \ + out1 = (v16i8)__msa_xori_b(in1, 0x80); \ +} + +#define FLIP_SIGN4(in0, in1, in2, in3, out0, out1, out2, out3) { \ + FLIP_SIGN2(in0, in1, out0, out1); \ + FLIP_SIGN2(in2, in3, out2, out3); \ +} + +#define FILT_VAL(q0_m, p0_m, mask, filt) do { \ + v16i8 q0_sub_p0; \ + q0_sub_p0 = __msa_subs_s_b(q0_m, p0_m); \ + filt = __msa_adds_s_b(filt, q0_sub_p0); \ + filt = __msa_adds_s_b(filt, q0_sub_p0); \ + filt = __msa_adds_s_b(filt, q0_sub_p0); \ + filt = filt & mask; \ +} while (0) + +#define FILT2(q_m, p_m, q, p) do { \ + u_r = SRAI_H(temp1, 7); \ + u_r = __msa_sat_s_h(u_r, 7); \ + u_l = SRAI_H(temp3, 7); \ + u_l = __msa_sat_s_h(u_l, 7); \ + u = __msa_pckev_b((v16i8)u_l, (v16i8)u_r); \ + q_m = __msa_subs_s_b(q_m, u); \ + p_m = __msa_adds_s_b(p_m, u); \ + q = __msa_xori_b((v16u8)q_m, 0x80); \ + p = __msa_xori_b((v16u8)p_m, 0x80); \ +} while (0) + +#define LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev) do { \ + v16i8 p1_m, p0_m, q0_m, q1_m; \ + v16i8 filt, t1, t2; \ + const v16i8 cnst4b = __msa_ldi_b(4); \ + const v16i8 cnst3b = __msa_ldi_b(3); \ + \ + FLIP_SIGN4(p1, p0, q0, q1, p1_m, p0_m, q0_m, q1_m); \ + filt = __msa_subs_s_b(p1_m, q1_m); \ + filt = filt & hev; \ + FILT_VAL(q0_m, p0_m, mask, filt); \ + t1 = __msa_adds_s_b(filt, cnst4b); \ + t1 = SRAI_B(t1, 3); \ + t2 = __msa_adds_s_b(filt, cnst3b); \ + t2 = SRAI_B(t2, 3); \ + q0_m = __msa_subs_s_b(q0_m, t1); \ + q0 = __msa_xori_b((v16u8)q0_m, 0x80); \ + p0_m = __msa_adds_s_b(p0_m, t2); \ + p0 = __msa_xori_b((v16u8)p0_m, 0x80); \ + filt = __msa_srari_b(t1, 1); \ + hev = __msa_xori_b(hev, 0xff); \ + filt = filt & hev; \ + q1_m = __msa_subs_s_b(q1_m, filt); \ + q1 = __msa_xori_b((v16u8)q1_m, 0x80); \ + p1_m = __msa_adds_s_b(p1_m, filt); \ + p1 = __msa_xori_b((v16u8)p1_m, 0x80); \ +} while (0) + +#define LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev) do { \ + v16i8 p2_m, p1_m, p0_m, q2_m, q1_m, q0_m; \ + v16i8 u, filt, t1, t2, filt_sign; \ + v8i16 filt_r, filt_l, u_r, u_l; \ + v8i16 temp0, temp1, temp2, temp3; \ + const v16i8 cnst4b = __msa_ldi_b(4); \ + const v16i8 cnst3b = __msa_ldi_b(3); \ + const v8i16 cnst9h = __msa_ldi_h(9); \ + \ + FLIP_SIGN4(p1, p0, q0, q1, p1_m, p0_m, q0_m, q1_m); \ + filt = __msa_subs_s_b(p1_m, q1_m); \ + FILT_VAL(q0_m, p0_m, mask, filt); \ + FLIP_SIGN2(p2, q2, p2_m, q2_m); \ + t2 = filt & hev; \ + /* filt_val &= ~hev */ \ + hev = __msa_xori_b(hev, 0xff); \ + filt = filt & hev; \ + t1 = __msa_adds_s_b(t2, cnst4b); \ + t1 = SRAI_B(t1, 3); \ + t2 = __msa_adds_s_b(t2, cnst3b); \ + t2 = SRAI_B(t2, 3); \ + q0_m = __msa_subs_s_b(q0_m, t1); \ + p0_m = __msa_adds_s_b(p0_m, t2); \ + filt_sign = __msa_clti_s_b(filt, 0); \ + ILVRL_B2_SH(filt_sign, filt, filt_r, filt_l); \ + /* update q2/p2 */ \ + temp0 = filt_r * cnst9h; \ + temp1 = ADDVI_H(temp0, 63); \ + temp2 = filt_l * cnst9h; \ + temp3 = ADDVI_H(temp2, 63); \ + FILT2(q2_m, p2_m, q2, p2); \ + /* update q1/p1 */ \ + temp1 = temp1 + temp0; \ + temp3 = temp3 + temp2; \ + FILT2(q1_m, p1_m, q1, p1); \ + /* update q0/p0 */ \ + temp1 = temp1 + temp0; \ + temp3 = temp3 + temp2; \ + FILT2(q0_m, p0_m, q0, p0); \ +} while (0) + +#define LPF_MASK_HEV(p3_in, p2_in, p1_in, p0_in, \ + q0_in, q1_in, q2_in, q3_in, \ + limit_in, b_limit_in, thresh_in, \ + hev_out, mask_out) do { \ + v16u8 p3_asub_p2_m, p2_asub_p1_m, p1_asub_p0_m, q1_asub_q0_m; \ + v16u8 p1_asub_q1_m, p0_asub_q0_m, q3_asub_q2_m, q2_asub_q1_m; \ + v16u8 flat_out; \ + \ + /* absolute subtraction of pixel values */ \ + p3_asub_p2_m = __msa_asub_u_b(p3_in, p2_in); \ + p2_asub_p1_m = __msa_asub_u_b(p2_in, p1_in); \ + p1_asub_p0_m = __msa_asub_u_b(p1_in, p0_in); \ + q1_asub_q0_m = __msa_asub_u_b(q1_in, q0_in); \ + q2_asub_q1_m = __msa_asub_u_b(q2_in, q1_in); \ + q3_asub_q2_m = __msa_asub_u_b(q3_in, q2_in); \ + p0_asub_q0_m = __msa_asub_u_b(p0_in, q0_in); \ + p1_asub_q1_m = __msa_asub_u_b(p1_in, q1_in); \ + /* calculation of hev */ \ + flat_out = __msa_max_u_b(p1_asub_p0_m, q1_asub_q0_m); \ + hev_out = (thresh_in < flat_out); \ + /* calculation of mask */ \ + p0_asub_q0_m = __msa_adds_u_b(p0_asub_q0_m, p0_asub_q0_m); \ + p1_asub_q1_m = SRAI_B(p1_asub_q1_m, 1); \ + p0_asub_q0_m = __msa_adds_u_b(p0_asub_q0_m, p1_asub_q1_m); \ + mask_out = (b_limit_in < p0_asub_q0_m); \ + mask_out = __msa_max_u_b(flat_out, mask_out); \ + p3_asub_p2_m = __msa_max_u_b(p3_asub_p2_m, p2_asub_p1_m); \ + mask_out = __msa_max_u_b(p3_asub_p2_m, mask_out); \ + q2_asub_q1_m = __msa_max_u_b(q2_asub_q1_m, q3_asub_q2_m); \ + mask_out = __msa_max_u_b(q2_asub_q1_m, mask_out); \ + mask_out = (limit_in < mask_out); \ + mask_out = __msa_xori_b(mask_out, 0xff); \ +} while (0) + +#define ST6x1_UB(in0, in0_idx, in1, in1_idx, pdst, stride) do { \ + const uint16_t tmp0_h = __msa_copy_s_h((v8i16)in1, in1_idx); \ + const uint32_t tmp0_w = __msa_copy_s_w((v4i32)in0, in0_idx); \ + SW(tmp0_w, pdst); \ + SH(tmp0_h, pdst + stride); \ +} while (0) + +#define ST6x4_UB(in0, start_in0_idx, in1, start_in1_idx, pdst, stride) do { \ + uint8_t* ptmp1 = (uint8_t*)pdst; \ + ST6x1_UB(in0, start_in0_idx, in1, start_in1_idx, ptmp1, 4); \ + ptmp1 += stride; \ + ST6x1_UB(in0, start_in0_idx + 1, in1, start_in1_idx + 1, ptmp1, 4); \ + ptmp1 += stride; \ + ST6x1_UB(in0, start_in0_idx + 2, in1, start_in1_idx + 2, ptmp1, 4); \ + ptmp1 += stride; \ + ST6x1_UB(in0, start_in0_idx + 3, in1, start_in1_idx + 3, ptmp1, 4); \ +} while (0) + +#define LPF_SIMPLE_FILT(p1_in, p0_in, q0_in, q1_in, mask) do { \ + v16i8 p1_m, p0_m, q0_m, q1_m, filt, filt1, filt2; \ + const v16i8 cnst4b = __msa_ldi_b(4); \ + const v16i8 cnst3b = __msa_ldi_b(3); \ + \ + FLIP_SIGN4(p1_in, p0_in, q0_in, q1_in, p1_m, p0_m, q0_m, q1_m); \ + filt = __msa_subs_s_b(p1_m, q1_m); \ + FILT_VAL(q0_m, p0_m, mask, filt); \ + filt1 = __msa_adds_s_b(filt, cnst4b); \ + filt1 = SRAI_B(filt1, 3); \ + filt2 = __msa_adds_s_b(filt, cnst3b); \ + filt2 = SRAI_B(filt2, 3); \ + q0_m = __msa_subs_s_b(q0_m, filt1); \ + p0_m = __msa_adds_s_b(p0_m, filt2); \ + q0_in = __msa_xori_b((v16u8)q0_m, 0x80); \ + p0_in = __msa_xori_b((v16u8)p0_m, 0x80); \ +} while (0) + +#define LPF_SIMPLE_MASK(p1, p0, q0, q1, b_limit, mask) do { \ + v16u8 p1_a_sub_q1, p0_a_sub_q0; \ + \ + p0_a_sub_q0 = __msa_asub_u_b(p0, q0); \ + p1_a_sub_q1 = __msa_asub_u_b(p1, q1); \ + p1_a_sub_q1 = (v16u8)__msa_srli_b((v16i8)p1_a_sub_q1, 1); \ + p0_a_sub_q0 = __msa_adds_u_b(p0_a_sub_q0, p0_a_sub_q0); \ + mask = __msa_adds_u_b(p0_a_sub_q0, p1_a_sub_q1); \ + mask = (mask <= b_limit); \ +} while (0) + +static void VFilter16(uint8_t* src, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint8_t* ptemp = src - 4 * stride; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0; + v16u8 mask, hev; + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + + LD_UB8(ptemp, stride, p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev); + ptemp = src - 3 * stride; + ST_UB4(p2, p1, p0, q0, ptemp, stride); + ptemp += (4 * stride); + ST_UB2(q1, q2, ptemp, stride); +} + +static void HFilter16(uint8_t* src, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint8_t* ptmp = src - 4; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0; + v16u8 mask, hev; + v16u8 row0, row1, row2, row3, row4, row5, row6, row7, row8; + v16u8 row9, row10, row11, row12, row13, row14, row15; + v8i16 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + + LD_UB8(ptmp, stride, row0, row1, row2, row3, row4, row5, row6, row7); + ptmp += (8 * stride); + LD_UB8(ptmp, stride, row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x8_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev); + ILVR_B2_SH(p1, p2, q0, p0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp3, tmp4); + ILVL_B2_SH(p1, p2, q0, p0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp6, tmp7); + ILVRL_B2_SH(q2, q1, tmp2, tmp5); + ptmp = src - 3; + ST6x1_UB(tmp3, 0, tmp2, 0, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp3, 1, tmp2, 1, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp3, 2, tmp2, 2, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp3, 3, tmp2, 3, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp4, 0, tmp2, 4, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp4, 1, tmp2, 5, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp4, 2, tmp2, 6, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp4, 3, tmp2, 7, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp6, 0, tmp5, 0, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp6, 1, tmp5, 1, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp6, 2, tmp5, 2, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp6, 3, tmp5, 3, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp7, 0, tmp5, 4, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp7, 1, tmp5, 5, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp7, 2, tmp5, 6, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp7, 3, tmp5, 7, ptmp, 4); +} + +// on three inner edges +static void VFilterHorEdge16i(uint8_t* src, int stride, + int b_limit, int limit, int thresh) { + v16u8 mask, hev; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0; + const v16u8 thresh0 = (v16u8)__msa_fill_b(thresh); + const v16u8 b_limit0 = (v16u8)__msa_fill_b(b_limit); + const v16u8 limit0 = (v16u8)__msa_fill_b(limit); + + LD_UB8((src - 4 * stride), stride, p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit0, b_limit0, thresh0, + hev, mask); + LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev); + ST_UB4(p1, p0, q0, q1, (src - 2 * stride), stride); +} + +static void VFilter16i(uint8_t* src_y, int stride, + int b_limit, int limit, int thresh) { + VFilterHorEdge16i(src_y + 4 * stride, stride, b_limit, limit, thresh); + VFilterHorEdge16i(src_y + 8 * stride, stride, b_limit, limit, thresh); + VFilterHorEdge16i(src_y + 12 * stride, stride, b_limit, limit, thresh); +} + +static void HFilterVertEdge16i(uint8_t* src, int stride, + int b_limit, int limit, int thresh) { + v16u8 mask, hev; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0; + v16u8 row0, row1, row2, row3, row4, row5, row6, row7; + v16u8 row8, row9, row10, row11, row12, row13, row14, row15; + v8i16 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + const v16u8 thresh0 = (v16u8)__msa_fill_b(thresh); + const v16u8 b_limit0 = (v16u8)__msa_fill_b(b_limit); + const v16u8 limit0 = (v16u8)__msa_fill_b(limit); + + LD_UB8(src - 4, stride, row0, row1, row2, row3, row4, row5, row6, row7); + LD_UB8(src - 4 + (8 * stride), stride, + row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x8_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit0, b_limit0, thresh0, + hev, mask); + LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev); + ILVR_B2_SH(p0, p1, q1, q0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp2, tmp3); + ILVL_B2_SH(p0, p1, q1, q0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp4, tmp5); + src -= 2; + ST4x8_UB(tmp2, tmp3, src, stride); + src += (8 * stride); + ST4x8_UB(tmp4, tmp5, src, stride); +} + +static void HFilter16i(uint8_t* src_y, int stride, + int b_limit, int limit, int thresh) { + HFilterVertEdge16i(src_y + 4, stride, b_limit, limit, thresh); + HFilterVertEdge16i(src_y + 8, stride, b_limit, limit, thresh); + HFilterVertEdge16i(src_y + 12, stride, b_limit, limit, thresh); +} + +// 8-pixels wide variants, for chroma filtering +static void VFilter8(uint8_t* src_u, uint8_t* src_v, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint8_t* ptmp_src_u = src_u - 4 * stride; + uint8_t* ptmp_src_v = src_v - 4 * stride; + uint64_t p2_d, p1_d, p0_d, q0_d, q1_d, q2_d; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0, mask, hev; + v16u8 p3_u, p2_u, p1_u, p0_u, q3_u, q2_u, q1_u, q0_u; + v16u8 p3_v, p2_v, p1_v, p0_v, q3_v, q2_v, q1_v, q0_v; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + + LD_UB8(ptmp_src_u, stride, p3_u, p2_u, p1_u, p0_u, q0_u, q1_u, q2_u, q3_u); + LD_UB8(ptmp_src_v, stride, p3_v, p2_v, p1_v, p0_v, q0_v, q1_v, q2_v, q3_v); + ILVR_D4_UB(p3_v, p3_u, p2_v, p2_u, p1_v, p1_u, p0_v, p0_u, p3, p2, p1, p0); + ILVR_D4_UB(q0_v, q0_u, q1_v, q1_u, q2_v, q2_u, q3_v, q3_u, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev); + p2_d = __msa_copy_s_d((v2i64)p2, 0); + p1_d = __msa_copy_s_d((v2i64)p1, 0); + p0_d = __msa_copy_s_d((v2i64)p0, 0); + q0_d = __msa_copy_s_d((v2i64)q0, 0); + q1_d = __msa_copy_s_d((v2i64)q1, 0); + q2_d = __msa_copy_s_d((v2i64)q2, 0); + ptmp_src_u += stride; + SD4(p2_d, p1_d, p0_d, q0_d, ptmp_src_u, stride); + ptmp_src_u += (4 * stride); + SD(q1_d, ptmp_src_u); + ptmp_src_u += stride; + SD(q2_d, ptmp_src_u); + p2_d = __msa_copy_s_d((v2i64)p2, 1); + p1_d = __msa_copy_s_d((v2i64)p1, 1); + p0_d = __msa_copy_s_d((v2i64)p0, 1); + q0_d = __msa_copy_s_d((v2i64)q0, 1); + q1_d = __msa_copy_s_d((v2i64)q1, 1); + q2_d = __msa_copy_s_d((v2i64)q2, 1); + ptmp_src_v += stride; + SD4(p2_d, p1_d, p0_d, q0_d, ptmp_src_v, stride); + ptmp_src_v += (4 * stride); + SD(q1_d, ptmp_src_v); + ptmp_src_v += stride; + SD(q2_d, ptmp_src_v); +} + +static void HFilter8(uint8_t* src_u, uint8_t* src_v, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint8_t* ptmp_src_u = src_u - 4; + uint8_t* ptmp_src_v = src_v - 4; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0, mask, hev; + v16u8 row0, row1, row2, row3, row4, row5, row6, row7, row8; + v16u8 row9, row10, row11, row12, row13, row14, row15; + v8i16 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + + LD_UB8(ptmp_src_u, stride, row0, row1, row2, row3, row4, row5, row6, row7); + LD_UB8(ptmp_src_v, stride, + row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x8_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev); + ILVR_B2_SH(p1, p2, q0, p0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp3, tmp4); + ILVL_B2_SH(p1, p2, q0, p0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp6, tmp7); + ILVRL_B2_SH(q2, q1, tmp2, tmp5); + ptmp_src_u += 1; + ST6x4_UB(tmp3, 0, tmp2, 0, ptmp_src_u, stride); + ptmp_src_u += 4 * stride; + ST6x4_UB(tmp4, 0, tmp2, 4, ptmp_src_u, stride); + ptmp_src_v += 1; + ST6x4_UB(tmp6, 0, tmp5, 0, ptmp_src_v, stride); + ptmp_src_v += 4 * stride; + ST6x4_UB(tmp7, 0, tmp5, 4, ptmp_src_v, stride); +} + +static void VFilter8i(uint8_t* src_u, uint8_t* src_v, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint64_t p1_d, p0_d, q0_d, q1_d; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0, mask, hev; + v16u8 p3_u, p2_u, p1_u, p0_u, q3_u, q2_u, q1_u, q0_u; + v16u8 p3_v, p2_v, p1_v, p0_v, q3_v, q2_v, q1_v, q0_v; + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + + LD_UB8(src_u, stride, p3_u, p2_u, p1_u, p0_u, q0_u, q1_u, q2_u, q3_u); + src_u += (5 * stride); + LD_UB8(src_v, stride, p3_v, p2_v, p1_v, p0_v, q0_v, q1_v, q2_v, q3_v); + src_v += (5 * stride); + ILVR_D4_UB(p3_v, p3_u, p2_v, p2_u, p1_v, p1_u, p0_v, p0_u, p3, p2, p1, p0); + ILVR_D4_UB(q0_v, q0_u, q1_v, q1_u, q2_v, q2_u, q3_v, q3_u, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev); + p1_d = __msa_copy_s_d((v2i64)p1, 0); + p0_d = __msa_copy_s_d((v2i64)p0, 0); + q0_d = __msa_copy_s_d((v2i64)q0, 0); + q1_d = __msa_copy_s_d((v2i64)q1, 0); + SD4(q1_d, q0_d, p0_d, p1_d, src_u, -stride); + p1_d = __msa_copy_s_d((v2i64)p1, 1); + p0_d = __msa_copy_s_d((v2i64)p0, 1); + q0_d = __msa_copy_s_d((v2i64)q0, 1); + q1_d = __msa_copy_s_d((v2i64)q1, 1); + SD4(q1_d, q0_d, p0_d, p1_d, src_v, -stride); +} + +static void HFilter8i(uint8_t* src_u, uint8_t* src_v, int stride, + int b_limit_in, int limit_in, int thresh_in) { + v16u8 p3, p2, p1, p0, q3, q2, q1, q0, mask, hev; + v16u8 row0, row1, row2, row3, row4, row5, row6, row7, row8; + v16u8 row9, row10, row11, row12, row13, row14, row15; + v4i32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + + LD_UB8(src_u, stride, row0, row1, row2, row3, row4, row5, row6, row7); + LD_UB8(src_v, stride, + row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x8_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev); + ILVR_B2_SW(p0, p1, q1, q0, tmp0, tmp1); + ILVRL_H2_SW(tmp1, tmp0, tmp2, tmp3); + ILVL_B2_SW(p0, p1, q1, q0, tmp0, tmp1); + ILVRL_H2_SW(tmp1, tmp0, tmp4, tmp5); + src_u += 2; + ST4x4_UB(tmp2, tmp2, 0, 1, 2, 3, src_u, stride); + src_u += 4 * stride; + ST4x4_UB(tmp3, tmp3, 0, 1, 2, 3, src_u, stride); + src_v += 2; + ST4x4_UB(tmp4, tmp4, 0, 1, 2, 3, src_v, stride); + src_v += 4 * stride; + ST4x4_UB(tmp5, tmp5, 0, 1, 2, 3, src_v, stride); +} + +static void SimpleVFilter16(uint8_t* src, int stride, int b_limit_in) { + v16u8 p1, p0, q1, q0, mask; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + + LD_UB4(src - 2 * stride, stride, p1, p0, q0, q1); + LPF_SIMPLE_MASK(p1, p0, q0, q1, b_limit, mask); + LPF_SIMPLE_FILT(p1, p0, q0, q1, mask); + ST_UB2(p0, q0, src - stride, stride); +} + +static void SimpleHFilter16(uint8_t* src, int stride, int b_limit_in) { + v16u8 p1, p0, q1, q0, mask, row0, row1, row2, row3, row4, row5, row6, row7; + v16u8 row8, row9, row10, row11, row12, row13, row14, row15; + v8i16 tmp0, tmp1; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + uint8_t* ptemp_src = src - 2; + + LD_UB8(ptemp_src, stride, row0, row1, row2, row3, row4, row5, row6, row7); + LD_UB8(ptemp_src + 8 * stride, stride, + row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x4_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p1, p0, q0, q1); + LPF_SIMPLE_MASK(p1, p0, q0, q1, b_limit, mask); + LPF_SIMPLE_FILT(p1, p0, q0, q1, mask); + ILVRL_B2_SH(q0, p0, tmp1, tmp0); + ptemp_src += 1; + ST2x4_UB(tmp1, 0, ptemp_src, stride); + ptemp_src += 4 * stride; + ST2x4_UB(tmp1, 4, ptemp_src, stride); + ptemp_src += 4 * stride; + ST2x4_UB(tmp0, 0, ptemp_src, stride); + ptemp_src += 4 * stride; + ST2x4_UB(tmp0, 4, ptemp_src, stride); + ptemp_src += 4 * stride; +} + +static void SimpleVFilter16i(uint8_t* src_y, int stride, int b_limit_in) { + SimpleVFilter16(src_y + 4 * stride, stride, b_limit_in); + SimpleVFilter16(src_y + 8 * stride, stride, b_limit_in); + SimpleVFilter16(src_y + 12 * stride, stride, b_limit_in); +} + +static void SimpleHFilter16i(uint8_t* src_y, int stride, int b_limit_in) { + SimpleHFilter16(src_y + 4, stride, b_limit_in); + SimpleHFilter16(src_y + 8, stride, b_limit_in); + SimpleHFilter16(src_y + 12, stride, b_limit_in); +} + +//------------------------------------------------------------------------------ +// Intra predictions +//------------------------------------------------------------------------------ + +// 4x4 + +static void DC4(uint8_t* dst) { // DC + uint32_t dc = 4; + int i; + for (i = 0; i < 4; ++i) dc += dst[i - BPS] + dst[-1 + i * BPS]; + dc >>= 3; + dc = dc | (dc << 8) | (dc << 16) | (dc << 24); + SW4(dc, dc, dc, dc, dst, BPS); +} + +static void TM4(uint8_t* dst) { + const uint8_t* const ptemp = dst - BPS - 1; + v8i16 T, d, r0, r1, r2, r3; + const v16i8 zero = { 0 }; + const v8i16 TL = (v8i16)__msa_fill_h(ptemp[0 * BPS]); + const v8i16 L0 = (v8i16)__msa_fill_h(ptemp[1 * BPS]); + const v8i16 L1 = (v8i16)__msa_fill_h(ptemp[2 * BPS]); + const v8i16 L2 = (v8i16)__msa_fill_h(ptemp[3 * BPS]); + const v8i16 L3 = (v8i16)__msa_fill_h(ptemp[4 * BPS]); + const v16u8 T1 = LD_UB(ptemp + 1); + + T = (v8i16)__msa_ilvr_b(zero, (v16i8)T1); + d = T - TL; + ADD4(d, L0, d, L1, d, L2, d, L3, r0, r1, r2, r3); + CLIP_SH4_0_255(r0, r1, r2, r3); + PCKEV_ST4x4_UB(r0, r1, r2, r3, dst, BPS); +} + +static void VE4(uint8_t* dst) { // vertical + const uint8_t* const ptop = dst - BPS - 1; + const uint32_t val0 = LW(ptop + 0); + const uint32_t val1 = LW(ptop + 4); + uint32_t out; + v16u8 A, B, C, AC, B2, R; + + INSERT_W2_UB(val0, val1, A); + B = SLDI_UB(A, A, 1); + C = SLDI_UB(A, A, 2); + AC = __msa_ave_u_b(A, C); + B2 = __msa_ave_u_b(B, B); + R = __msa_aver_u_b(AC, B2); + out = __msa_copy_s_w((v4i32)R, 0); + SW4(out, out, out, out, dst, BPS); +} + +static void RD4(uint8_t* dst) { // Down-right + const uint8_t* const ptop = dst - 1 - BPS; + uint32_t val0 = LW(ptop + 0); + uint32_t val1 = LW(ptop + 4); + uint32_t val2, val3; + v16u8 A, B, C, AC, B2, R, A1; + + INSERT_W2_UB(val0, val1, A1); + A = SLDI_UB(A1, A1, 12); + A = (v16u8)__msa_insert_b((v16i8)A, 3, ptop[1 * BPS]); + A = (v16u8)__msa_insert_b((v16i8)A, 2, ptop[2 * BPS]); + A = (v16u8)__msa_insert_b((v16i8)A, 1, ptop[3 * BPS]); + A = (v16u8)__msa_insert_b((v16i8)A, 0, ptop[4 * BPS]); + B = SLDI_UB(A, A, 1); + C = SLDI_UB(A, A, 2); + AC = __msa_ave_u_b(A, C); + B2 = __msa_ave_u_b(B, B); + R = __msa_aver_u_b(AC, B2); + val3 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val2 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val1 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val0 = __msa_copy_s_w((v4i32)R, 0); + SW4(val0, val1, val2, val3, dst, BPS); +} + +static void LD4(uint8_t* dst) { // Down-Left + const uint8_t* const ptop = dst - BPS; + uint32_t val0 = LW(ptop + 0); + uint32_t val1 = LW(ptop + 4); + uint32_t val2, val3; + v16u8 A, B, C, AC, B2, R; + + INSERT_W2_UB(val0, val1, A); + B = SLDI_UB(A, A, 1); + C = SLDI_UB(A, A, 2); + C = (v16u8)__msa_insert_b((v16i8)C, 6, ptop[7]); + AC = __msa_ave_u_b(A, C); + B2 = __msa_ave_u_b(B, B); + R = __msa_aver_u_b(AC, B2); + val0 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val1 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val2 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val3 = __msa_copy_s_w((v4i32)R, 0); + SW4(val0, val1, val2, val3, dst, BPS); +} + +// 16x16 + +static void DC16(uint8_t* dst) { // DC + uint32_t dc = 16; + int i; + const v16u8 rtop = LD_UB(dst - BPS); + const v8u16 dctop = __msa_hadd_u_h(rtop, rtop); + v16u8 out; + + for (i = 0; i < 16; ++i) { + dc += dst[-1 + i * BPS]; + } + dc += HADD_UH_U32(dctop); + out = (v16u8)__msa_fill_b(dc >> 5); + ST_UB8(out, out, out, out, out, out, out, out, dst, BPS); + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); +} + +static void TM16(uint8_t* dst) { + int j; + v8i16 d1, d2; + const v16i8 zero = { 0 }; + const v8i16 TL = (v8i16)__msa_fill_h(dst[-1 - BPS]); + const v16i8 T = LD_SB(dst - BPS); + + ILVRL_B2_SH(zero, T, d1, d2); + SUB2(d1, TL, d2, TL, d1, d2); + for (j = 0; j < 16; j += 4) { + v16i8 t0, t1, t2, t3; + v8i16 r0, r1, r2, r3, r4, r5, r6, r7; + const v8i16 L0 = (v8i16)__msa_fill_h(dst[-1 + 0 * BPS]); + const v8i16 L1 = (v8i16)__msa_fill_h(dst[-1 + 1 * BPS]); + const v8i16 L2 = (v8i16)__msa_fill_h(dst[-1 + 2 * BPS]); + const v8i16 L3 = (v8i16)__msa_fill_h(dst[-1 + 3 * BPS]); + ADD4(d1, L0, d1, L1, d1, L2, d1, L3, r0, r1, r2, r3); + ADD4(d2, L0, d2, L1, d2, L2, d2, L3, r4, r5, r6, r7); + CLIP_SH4_0_255(r0, r1, r2, r3); + CLIP_SH4_0_255(r4, r5, r6, r7); + PCKEV_B4_SB(r4, r0, r5, r1, r6, r2, r7, r3, t0, t1, t2, t3); + ST_SB4(t0, t1, t2, t3, dst, BPS); + dst += 4 * BPS; + } +} + +static void VE16(uint8_t* dst) { // vertical + const v16u8 rtop = LD_UB(dst - BPS); + ST_UB8(rtop, rtop, rtop, rtop, rtop, rtop, rtop, rtop, dst, BPS); + ST_UB8(rtop, rtop, rtop, rtop, rtop, rtop, rtop, rtop, dst + 8 * BPS, BPS); +} + +static void HE16(uint8_t* dst) { // horizontal + int j; + for (j = 16; j > 0; j -= 4) { + const v16u8 L0 = (v16u8)__msa_fill_b(dst[-1 + 0 * BPS]); + const v16u8 L1 = (v16u8)__msa_fill_b(dst[-1 + 1 * BPS]); + const v16u8 L2 = (v16u8)__msa_fill_b(dst[-1 + 2 * BPS]); + const v16u8 L3 = (v16u8)__msa_fill_b(dst[-1 + 3 * BPS]); + ST_UB4(L0, L1, L2, L3, dst, BPS); + dst += 4 * BPS; + } +} + +static void DC16NoTop(uint8_t* dst) { // DC with top samples not available + int j; + uint32_t dc = 8; + v16u8 out; + + for (j = 0; j < 16; ++j) { + dc += dst[-1 + j * BPS]; + } + out = (v16u8)__msa_fill_b(dc >> 4); + ST_UB8(out, out, out, out, out, out, out, out, dst, BPS); + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); +} + +static void DC16NoLeft(uint8_t* dst) { // DC with left samples not available + uint32_t dc = 8; + const v16u8 rtop = LD_UB(dst - BPS); + const v8u16 dctop = __msa_hadd_u_h(rtop, rtop); + v16u8 out; + + dc += HADD_UH_U32(dctop); + out = (v16u8)__msa_fill_b(dc >> 4); + ST_UB8(out, out, out, out, out, out, out, out, dst, BPS); + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); +} + +static void DC16NoTopLeft(uint8_t* dst) { // DC with nothing + const v16u8 out = (v16u8)__msa_fill_b(0x80); + ST_UB8(out, out, out, out, out, out, out, out, dst, BPS); + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); +} + +// Chroma + +#define STORE8x8(out, dst) do { \ + SD4(out, out, out, out, dst + 0 * BPS, BPS); \ + SD4(out, out, out, out, dst + 4 * BPS, BPS); \ +} while (0) + +static void DC8uv(uint8_t* dst) { // DC + uint32_t dc = 8; + int i; + uint64_t out; + const v16u8 rtop = LD_UB(dst - BPS); + const v8u16 temp0 = __msa_hadd_u_h(rtop, rtop); + const v4u32 temp1 = __msa_hadd_u_w(temp0, temp0); + const v2u64 temp2 = __msa_hadd_u_d(temp1, temp1); + v16u8 dctemp; + + for (i = 0; i < 8; ++i) { + dc += dst[-1 + i * BPS]; + } + dc += __msa_copy_s_w((v4i32)temp2, 0); + dctemp = (v16u8)__msa_fill_b(dc >> 4); + out = __msa_copy_s_d((v2i64)dctemp, 0); + STORE8x8(out, dst); +} + +static void TM8uv(uint8_t* dst) { + int j; + const v16i8 T1 = LD_SB(dst - BPS); + const v16i8 zero = { 0 }; + const v8i16 T = (v8i16)__msa_ilvr_b(zero, T1); + const v8i16 TL = (v8i16)__msa_fill_h(dst[-1 - BPS]); + const v8i16 d = T - TL; + + for (j = 0; j < 8; j += 4) { + v16i8 t0, t1; + v8i16 r0 = (v8i16)__msa_fill_h(dst[-1 + 0 * BPS]); + v8i16 r1 = (v8i16)__msa_fill_h(dst[-1 + 1 * BPS]); + v8i16 r2 = (v8i16)__msa_fill_h(dst[-1 + 2 * BPS]); + v8i16 r3 = (v8i16)__msa_fill_h(dst[-1 + 3 * BPS]); + ADD4(d, r0, d, r1, d, r2, d, r3, r0, r1, r2, r3); + CLIP_SH4_0_255(r0, r1, r2, r3); + PCKEV_B2_SB(r1, r0, r3, r2, t0, t1); + ST4x4_UB(t0, t1, 0, 2, 0, 2, dst, BPS); + ST4x4_UB(t0, t1, 1, 3, 1, 3, dst + 4, BPS); + dst += 4 * BPS; + } +} + +static void VE8uv(uint8_t* dst) { // vertical + const v16u8 rtop = LD_UB(dst - BPS); + const uint64_t out = __msa_copy_s_d((v2i64)rtop, 0); + STORE8x8(out, dst); +} + +static void HE8uv(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 8; j += 4) { + const v16u8 L0 = (v16u8)__msa_fill_b(dst[-1 + 0 * BPS]); + const v16u8 L1 = (v16u8)__msa_fill_b(dst[-1 + 1 * BPS]); + const v16u8 L2 = (v16u8)__msa_fill_b(dst[-1 + 2 * BPS]); + const v16u8 L3 = (v16u8)__msa_fill_b(dst[-1 + 3 * BPS]); + const uint64_t out0 = __msa_copy_s_d((v2i64)L0, 0); + const uint64_t out1 = __msa_copy_s_d((v2i64)L1, 0); + const uint64_t out2 = __msa_copy_s_d((v2i64)L2, 0); + const uint64_t out3 = __msa_copy_s_d((v2i64)L3, 0); + SD4(out0, out1, out2, out3, dst, BPS); + dst += 4 * BPS; + } +} + +static void DC8uvNoLeft(uint8_t* dst) { // DC with no left samples + const uint32_t dc = 4; + const v16u8 rtop = LD_UB(dst - BPS); + const v8u16 temp0 = __msa_hadd_u_h(rtop, rtop); + const v4u32 temp1 = __msa_hadd_u_w(temp0, temp0); + const v2u64 temp2 = __msa_hadd_u_d(temp1, temp1); + const uint32_t sum_m = __msa_copy_s_w((v4i32)temp2, 0); + const v16u8 dcval = (v16u8)__msa_fill_b((dc + sum_m) >> 3); + const uint64_t out = __msa_copy_s_d((v2i64)dcval, 0); + STORE8x8(out, dst); +} + +static void DC8uvNoTop(uint8_t* dst) { // DC with no top samples + uint32_t dc = 4; + int i; + uint64_t out; + v16u8 dctemp; + + for (i = 0; i < 8; ++i) { + dc += dst[-1 + i * BPS]; + } + dctemp = (v16u8)__msa_fill_b(dc >> 3); + out = __msa_copy_s_d((v2i64)dctemp, 0); + STORE8x8(out, dst); +} + +static void DC8uvNoTopLeft(uint8_t* dst) { // DC with nothing + const uint64_t out = 0x8080808080808080ULL; + STORE8x8(out, dst); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitMSA(void) { + VP8TransformWHT = TransformWHT; + VP8Transform = TransformTwo; + VP8TransformDC = TransformDC; + VP8TransformAC3 = TransformAC3; + + VP8VFilter16 = VFilter16; + VP8HFilter16 = HFilter16; + VP8VFilter16i = VFilter16i; + VP8HFilter16i = HFilter16i; + VP8VFilter8 = VFilter8; + VP8HFilter8 = HFilter8; + VP8VFilter8i = VFilter8i; + VP8HFilter8i = HFilter8i; + VP8SimpleVFilter16 = SimpleVFilter16; + VP8SimpleHFilter16 = SimpleHFilter16; + VP8SimpleVFilter16i = SimpleVFilter16i; + VP8SimpleHFilter16i = SimpleHFilter16i; + + VP8PredLuma4[0] = DC4; + VP8PredLuma4[1] = TM4; + VP8PredLuma4[2] = VE4; + VP8PredLuma4[4] = RD4; + VP8PredLuma4[6] = LD4; + VP8PredLuma16[0] = DC16; + VP8PredLuma16[1] = TM16; + VP8PredLuma16[2] = VE16; + VP8PredLuma16[3] = HE16; + VP8PredLuma16[4] = DC16NoTop; + VP8PredLuma16[5] = DC16NoLeft; + VP8PredLuma16[6] = DC16NoTopLeft; + VP8PredChroma8[0] = DC8uv; + VP8PredChroma8[1] = TM8uv; + VP8PredChroma8[2] = VE8uv; + VP8PredChroma8[3] = HE8uv; + VP8PredChroma8[4] = DC8uvNoTop; + VP8PredChroma8[5] = DC8uvNoLeft; + VP8PredChroma8[6] = DC8uvNoTopLeft; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8DspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/3rdparty/libwebp/dsp/dec_neon.c b/3rdparty/libwebp/dsp/dec_neon.c index 5dcd3b7e5e..34796cf4a2 100644 --- a/3rdparty/libwebp/dsp/dec_neon.c +++ b/3rdparty/libwebp/dsp/dec_neon.c @@ -14,15 +14,537 @@ #include "./dsp.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - #if defined(WEBP_USE_NEON) -#include "../dec/vp8i.h" +#include "./neon.h" +#include "../dec/vp8i_dec.h" -#define QRegs "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", \ +//------------------------------------------------------------------------------ +// NxM Loading functions + +// Load/Store vertical edge +#define LOAD8x4(c1, c2, c3, c4, b1, b2, stride) \ + "vld4.8 {" #c1 "[0]," #c2 "[0]," #c3 "[0]," #c4 "[0]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[1]," #c2 "[1]," #c3 "[1]," #c4 "[1]}," #b2 "," #stride "\n" \ + "vld4.8 {" #c1 "[2]," #c2 "[2]," #c3 "[2]," #c4 "[2]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[3]," #c2 "[3]," #c3 "[3]," #c4 "[3]}," #b2 "," #stride "\n" \ + "vld4.8 {" #c1 "[4]," #c2 "[4]," #c3 "[4]," #c4 "[4]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[5]," #c2 "[5]," #c3 "[5]," #c4 "[5]}," #b2 "," #stride "\n" \ + "vld4.8 {" #c1 "[6]," #c2 "[6]," #c3 "[6]," #c4 "[6]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[7]," #c2 "[7]," #c3 "[7]," #c4 "[7]}," #b2 "," #stride "\n" + +#define STORE8x2(c1, c2, p, stride) \ + "vst2.8 {" #c1 "[0], " #c2 "[0]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[1], " #c2 "[1]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[2], " #c2 "[2]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[3], " #c2 "[3]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[4], " #c2 "[4]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[5], " #c2 "[5]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[6], " #c2 "[6]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[7], " #c2 "[7]}," #p "," #stride " \n" + +#if !defined(WORK_AROUND_GCC) + +// This intrinsics version makes gcc-4.6.3 crash during Load4x??() compilation +// (register alloc, probably). The variants somewhat mitigate the problem, but +// not quite. HFilter16i() remains problematic. +static WEBP_INLINE uint8x8x4_t Load4x8(const uint8_t* const src, int stride) { + const uint8x8_t zero = vdup_n_u8(0); + uint8x8x4_t out; + INIT_VECTOR4(out, zero, zero, zero, zero); + out = vld4_lane_u8(src + 0 * stride, out, 0); + out = vld4_lane_u8(src + 1 * stride, out, 1); + out = vld4_lane_u8(src + 2 * stride, out, 2); + out = vld4_lane_u8(src + 3 * stride, out, 3); + out = vld4_lane_u8(src + 4 * stride, out, 4); + out = vld4_lane_u8(src + 5 * stride, out, 5); + out = vld4_lane_u8(src + 6 * stride, out, 6); + out = vld4_lane_u8(src + 7 * stride, out, 7); + return out; +} + +static WEBP_INLINE void Load4x16(const uint8_t* const src, int stride, + uint8x16_t* const p1, uint8x16_t* const p0, + uint8x16_t* const q0, uint8x16_t* const q1) { + // row0 = p1[0..7]|p0[0..7]|q0[0..7]|q1[0..7] + // row8 = p1[8..15]|p0[8..15]|q0[8..15]|q1[8..15] + const uint8x8x4_t row0 = Load4x8(src - 2 + 0 * stride, stride); + const uint8x8x4_t row8 = Load4x8(src - 2 + 8 * stride, stride); + *p1 = vcombine_u8(row0.val[0], row8.val[0]); + *p0 = vcombine_u8(row0.val[1], row8.val[1]); + *q0 = vcombine_u8(row0.val[2], row8.val[2]); + *q1 = vcombine_u8(row0.val[3], row8.val[3]); +} + +#else // WORK_AROUND_GCC + +#define LOADQ_LANE_32b(VALUE, LANE) do { \ + (VALUE) = vld1q_lane_u32((const uint32_t*)src, (VALUE), (LANE)); \ + src += stride; \ +} while (0) + +static WEBP_INLINE void Load4x16(const uint8_t* src, int stride, + uint8x16_t* const p1, uint8x16_t* const p0, + uint8x16_t* const q0, uint8x16_t* const q1) { + const uint32x4_t zero = vdupq_n_u32(0); + uint32x4x4_t in; + INIT_VECTOR4(in, zero, zero, zero, zero); + src -= 2; + LOADQ_LANE_32b(in.val[0], 0); + LOADQ_LANE_32b(in.val[1], 0); + LOADQ_LANE_32b(in.val[2], 0); + LOADQ_LANE_32b(in.val[3], 0); + LOADQ_LANE_32b(in.val[0], 1); + LOADQ_LANE_32b(in.val[1], 1); + LOADQ_LANE_32b(in.val[2], 1); + LOADQ_LANE_32b(in.val[3], 1); + LOADQ_LANE_32b(in.val[0], 2); + LOADQ_LANE_32b(in.val[1], 2); + LOADQ_LANE_32b(in.val[2], 2); + LOADQ_LANE_32b(in.val[3], 2); + LOADQ_LANE_32b(in.val[0], 3); + LOADQ_LANE_32b(in.val[1], 3); + LOADQ_LANE_32b(in.val[2], 3); + LOADQ_LANE_32b(in.val[3], 3); + // Transpose four 4x4 parts: + { + const uint8x16x2_t row01 = vtrnq_u8(vreinterpretq_u8_u32(in.val[0]), + vreinterpretq_u8_u32(in.val[1])); + const uint8x16x2_t row23 = vtrnq_u8(vreinterpretq_u8_u32(in.val[2]), + vreinterpretq_u8_u32(in.val[3])); + const uint16x8x2_t row02 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[0]), + vreinterpretq_u16_u8(row23.val[0])); + const uint16x8x2_t row13 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[1]), + vreinterpretq_u16_u8(row23.val[1])); + *p1 = vreinterpretq_u8_u16(row02.val[0]); + *p0 = vreinterpretq_u8_u16(row13.val[0]); + *q0 = vreinterpretq_u8_u16(row02.val[1]); + *q1 = vreinterpretq_u8_u16(row13.val[1]); + } +} +#undef LOADQ_LANE_32b + +#endif // !WORK_AROUND_GCC + +static WEBP_INLINE void Load8x16(const uint8_t* const src, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, + uint8x16_t* const p1, uint8x16_t* const p0, + uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + Load4x16(src - 2, stride, p3, p2, p1, p0); + Load4x16(src + 2, stride, q0, q1, q2, q3); +} + +static WEBP_INLINE void Load16x4(const uint8_t* const src, int stride, + uint8x16_t* const p1, uint8x16_t* const p0, + uint8x16_t* const q0, uint8x16_t* const q1) { + *p1 = vld1q_u8(src - 2 * stride); + *p0 = vld1q_u8(src - 1 * stride); + *q0 = vld1q_u8(src + 0 * stride); + *q1 = vld1q_u8(src + 1 * stride); +} + +static WEBP_INLINE void Load16x8(const uint8_t* const src, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, + uint8x16_t* const p1, uint8x16_t* const p0, + uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + Load16x4(src - 2 * stride, stride, p3, p2, p1, p0); + Load16x4(src + 2 * stride, stride, q0, q1, q2, q3); +} + +static WEBP_INLINE void Load8x8x2(const uint8_t* const u, + const uint8_t* const v, + int stride, + uint8x16_t* const p3, uint8x16_t* const p2, + uint8x16_t* const p1, uint8x16_t* const p0, + uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + // We pack the 8x8 u-samples in the lower half of the uint8x16_t destination + // and the v-samples on the higher half. + *p3 = vcombine_u8(vld1_u8(u - 4 * stride), vld1_u8(v - 4 * stride)); + *p2 = vcombine_u8(vld1_u8(u - 3 * stride), vld1_u8(v - 3 * stride)); + *p1 = vcombine_u8(vld1_u8(u - 2 * stride), vld1_u8(v - 2 * stride)); + *p0 = vcombine_u8(vld1_u8(u - 1 * stride), vld1_u8(v - 1 * stride)); + *q0 = vcombine_u8(vld1_u8(u + 0 * stride), vld1_u8(v + 0 * stride)); + *q1 = vcombine_u8(vld1_u8(u + 1 * stride), vld1_u8(v + 1 * stride)); + *q2 = vcombine_u8(vld1_u8(u + 2 * stride), vld1_u8(v + 2 * stride)); + *q3 = vcombine_u8(vld1_u8(u + 3 * stride), vld1_u8(v + 3 * stride)); +} + +#if !defined(WORK_AROUND_GCC) + +#define LOAD_UV_8(ROW) \ + vcombine_u8(vld1_u8(u - 4 + (ROW) * stride), vld1_u8(v - 4 + (ROW) * stride)) + +static WEBP_INLINE void Load8x8x2T(const uint8_t* const u, + const uint8_t* const v, + int stride, + uint8x16_t* const p3, uint8x16_t* const p2, + uint8x16_t* const p1, uint8x16_t* const p0, + uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + // We pack the 8x8 u-samples in the lower half of the uint8x16_t destination + // and the v-samples on the higher half. + const uint8x16_t row0 = LOAD_UV_8(0); + const uint8x16_t row1 = LOAD_UV_8(1); + const uint8x16_t row2 = LOAD_UV_8(2); + const uint8x16_t row3 = LOAD_UV_8(3); + const uint8x16_t row4 = LOAD_UV_8(4); + const uint8x16_t row5 = LOAD_UV_8(5); + const uint8x16_t row6 = LOAD_UV_8(6); + const uint8x16_t row7 = LOAD_UV_8(7); + // Perform two side-by-side 8x8 transposes + // u00 u01 u02 u03 u04 u05 u06 u07 | v00 v01 v02 v03 v04 v05 v06 v07 + // u10 u11 u12 u13 u14 u15 u16 u17 | v10 v11 v12 ... + // u20 u21 u22 u23 u24 u25 u26 u27 | v20 v21 ... + // u30 u31 u32 u33 u34 u35 u36 u37 | ... + // u40 u41 u42 u43 u44 u45 u46 u47 | ... + // u50 u51 u52 u53 u54 u55 u56 u57 | ... + // u60 u61 u62 u63 u64 u65 u66 u67 | v60 ... + // u70 u71 u72 u73 u74 u75 u76 u77 | v70 v71 v72 ... + const uint8x16x2_t row01 = vtrnq_u8(row0, row1); // u00 u10 u02 u12 ... + // u01 u11 u03 u13 ... + const uint8x16x2_t row23 = vtrnq_u8(row2, row3); // u20 u30 u22 u32 ... + // u21 u31 u23 u33 ... + const uint8x16x2_t row45 = vtrnq_u8(row4, row5); // ... + const uint8x16x2_t row67 = vtrnq_u8(row6, row7); // ... + const uint16x8x2_t row02 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[0]), + vreinterpretq_u16_u8(row23.val[0])); + const uint16x8x2_t row13 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[1]), + vreinterpretq_u16_u8(row23.val[1])); + const uint16x8x2_t row46 = vtrnq_u16(vreinterpretq_u16_u8(row45.val[0]), + vreinterpretq_u16_u8(row67.val[0])); + const uint16x8x2_t row57 = vtrnq_u16(vreinterpretq_u16_u8(row45.val[1]), + vreinterpretq_u16_u8(row67.val[1])); + const uint32x4x2_t row04 = vtrnq_u32(vreinterpretq_u32_u16(row02.val[0]), + vreinterpretq_u32_u16(row46.val[0])); + const uint32x4x2_t row26 = vtrnq_u32(vreinterpretq_u32_u16(row02.val[1]), + vreinterpretq_u32_u16(row46.val[1])); + const uint32x4x2_t row15 = vtrnq_u32(vreinterpretq_u32_u16(row13.val[0]), + vreinterpretq_u32_u16(row57.val[0])); + const uint32x4x2_t row37 = vtrnq_u32(vreinterpretq_u32_u16(row13.val[1]), + vreinterpretq_u32_u16(row57.val[1])); + *p3 = vreinterpretq_u8_u32(row04.val[0]); + *p2 = vreinterpretq_u8_u32(row15.val[0]); + *p1 = vreinterpretq_u8_u32(row26.val[0]); + *p0 = vreinterpretq_u8_u32(row37.val[0]); + *q0 = vreinterpretq_u8_u32(row04.val[1]); + *q1 = vreinterpretq_u8_u32(row15.val[1]); + *q2 = vreinterpretq_u8_u32(row26.val[1]); + *q3 = vreinterpretq_u8_u32(row37.val[1]); +} +#undef LOAD_UV_8 + +#endif // !WORK_AROUND_GCC + +static WEBP_INLINE void Store2x8(const uint8x8x2_t v, + uint8_t* const dst, int stride) { + vst2_lane_u8(dst + 0 * stride, v, 0); + vst2_lane_u8(dst + 1 * stride, v, 1); + vst2_lane_u8(dst + 2 * stride, v, 2); + vst2_lane_u8(dst + 3 * stride, v, 3); + vst2_lane_u8(dst + 4 * stride, v, 4); + vst2_lane_u8(dst + 5 * stride, v, 5); + vst2_lane_u8(dst + 6 * stride, v, 6); + vst2_lane_u8(dst + 7 * stride, v, 7); +} + +static WEBP_INLINE void Store2x16(const uint8x16_t p0, const uint8x16_t q0, + uint8_t* const dst, int stride) { + uint8x8x2_t lo, hi; + lo.val[0] = vget_low_u8(p0); + lo.val[1] = vget_low_u8(q0); + hi.val[0] = vget_high_u8(p0); + hi.val[1] = vget_high_u8(q0); + Store2x8(lo, dst - 1 + 0 * stride, stride); + Store2x8(hi, dst - 1 + 8 * stride, stride); +} + +#if !defined(WORK_AROUND_GCC) +static WEBP_INLINE void Store4x8(const uint8x8x4_t v, + uint8_t* const dst, int stride) { + vst4_lane_u8(dst + 0 * stride, v, 0); + vst4_lane_u8(dst + 1 * stride, v, 1); + vst4_lane_u8(dst + 2 * stride, v, 2); + vst4_lane_u8(dst + 3 * stride, v, 3); + vst4_lane_u8(dst + 4 * stride, v, 4); + vst4_lane_u8(dst + 5 * stride, v, 5); + vst4_lane_u8(dst + 6 * stride, v, 6); + vst4_lane_u8(dst + 7 * stride, v, 7); +} + +static WEBP_INLINE void Store4x16(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + uint8_t* const dst, int stride) { + uint8x8x4_t lo, hi; + INIT_VECTOR4(lo, + vget_low_u8(p1), vget_low_u8(p0), + vget_low_u8(q0), vget_low_u8(q1)); + INIT_VECTOR4(hi, + vget_high_u8(p1), vget_high_u8(p0), + vget_high_u8(q0), vget_high_u8(q1)); + Store4x8(lo, dst - 2 + 0 * stride, stride); + Store4x8(hi, dst - 2 + 8 * stride, stride); +} +#endif // !WORK_AROUND_GCC + +static WEBP_INLINE void Store16x2(const uint8x16_t p0, const uint8x16_t q0, + uint8_t* const dst, int stride) { + vst1q_u8(dst - stride, p0); + vst1q_u8(dst, q0); +} + +static WEBP_INLINE void Store16x4(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + uint8_t* const dst, int stride) { + Store16x2(p1, p0, dst - stride, stride); + Store16x2(q0, q1, dst + stride, stride); +} + +static WEBP_INLINE void Store8x2x2(const uint8x16_t p0, const uint8x16_t q0, + uint8_t* const u, uint8_t* const v, + int stride) { + // p0 and q0 contain the u+v samples packed in low/high halves. + vst1_u8(u - stride, vget_low_u8(p0)); + vst1_u8(u, vget_low_u8(q0)); + vst1_u8(v - stride, vget_high_u8(p0)); + vst1_u8(v, vget_high_u8(q0)); +} + +static WEBP_INLINE void Store8x4x2(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + uint8_t* const u, uint8_t* const v, + int stride) { + // The p1...q1 registers contain the u+v samples packed in low/high halves. + Store8x2x2(p1, p0, u - stride, v - stride, stride); + Store8x2x2(q0, q1, u + stride, v + stride, stride); +} + +#if !defined(WORK_AROUND_GCC) + +#define STORE6_LANE(DST, VAL0, VAL1, LANE) do { \ + vst3_lane_u8((DST) - 3, (VAL0), (LANE)); \ + vst3_lane_u8((DST) + 0, (VAL1), (LANE)); \ + (DST) += stride; \ +} while (0) + +static WEBP_INLINE void Store6x8x2(const uint8x16_t p2, const uint8x16_t p1, + const uint8x16_t p0, const uint8x16_t q0, + const uint8x16_t q1, const uint8x16_t q2, + uint8_t* u, uint8_t* v, + int stride) { + uint8x8x3_t u0, u1, v0, v1; + INIT_VECTOR3(u0, vget_low_u8(p2), vget_low_u8(p1), vget_low_u8(p0)); + INIT_VECTOR3(u1, vget_low_u8(q0), vget_low_u8(q1), vget_low_u8(q2)); + INIT_VECTOR3(v0, vget_high_u8(p2), vget_high_u8(p1), vget_high_u8(p0)); + INIT_VECTOR3(v1, vget_high_u8(q0), vget_high_u8(q1), vget_high_u8(q2)); + STORE6_LANE(u, u0, u1, 0); + STORE6_LANE(u, u0, u1, 1); + STORE6_LANE(u, u0, u1, 2); + STORE6_LANE(u, u0, u1, 3); + STORE6_LANE(u, u0, u1, 4); + STORE6_LANE(u, u0, u1, 5); + STORE6_LANE(u, u0, u1, 6); + STORE6_LANE(u, u0, u1, 7); + STORE6_LANE(v, v0, v1, 0); + STORE6_LANE(v, v0, v1, 1); + STORE6_LANE(v, v0, v1, 2); + STORE6_LANE(v, v0, v1, 3); + STORE6_LANE(v, v0, v1, 4); + STORE6_LANE(v, v0, v1, 5); + STORE6_LANE(v, v0, v1, 6); + STORE6_LANE(v, v0, v1, 7); +} +#undef STORE6_LANE + +static WEBP_INLINE void Store4x8x2(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + uint8_t* const u, uint8_t* const v, + int stride) { + uint8x8x4_t u0, v0; + INIT_VECTOR4(u0, + vget_low_u8(p1), vget_low_u8(p0), + vget_low_u8(q0), vget_low_u8(q1)); + INIT_VECTOR4(v0, + vget_high_u8(p1), vget_high_u8(p0), + vget_high_u8(q0), vget_high_u8(q1)); + vst4_lane_u8(u - 2 + 0 * stride, u0, 0); + vst4_lane_u8(u - 2 + 1 * stride, u0, 1); + vst4_lane_u8(u - 2 + 2 * stride, u0, 2); + vst4_lane_u8(u - 2 + 3 * stride, u0, 3); + vst4_lane_u8(u - 2 + 4 * stride, u0, 4); + vst4_lane_u8(u - 2 + 5 * stride, u0, 5); + vst4_lane_u8(u - 2 + 6 * stride, u0, 6); + vst4_lane_u8(u - 2 + 7 * stride, u0, 7); + vst4_lane_u8(v - 2 + 0 * stride, v0, 0); + vst4_lane_u8(v - 2 + 1 * stride, v0, 1); + vst4_lane_u8(v - 2 + 2 * stride, v0, 2); + vst4_lane_u8(v - 2 + 3 * stride, v0, 3); + vst4_lane_u8(v - 2 + 4 * stride, v0, 4); + vst4_lane_u8(v - 2 + 5 * stride, v0, 5); + vst4_lane_u8(v - 2 + 6 * stride, v0, 6); + vst4_lane_u8(v - 2 + 7 * stride, v0, 7); +} + +#endif // !WORK_AROUND_GCC + +// Zero extend 'v' to an int16x8_t. +static WEBP_INLINE int16x8_t ConvertU8ToS16(uint8x8_t v) { + return vreinterpretq_s16_u16(vmovl_u8(v)); +} + +// Performs unsigned 8b saturation on 'dst01' and 'dst23' storing the result +// to the corresponding rows of 'dst'. +static WEBP_INLINE void SaturateAndStore4x4(uint8_t* const dst, + const int16x8_t dst01, + const int16x8_t dst23) { + // Unsigned saturate to 8b. + const uint8x8_t dst01_u8 = vqmovun_s16(dst01); + const uint8x8_t dst23_u8 = vqmovun_s16(dst23); + + // Store the results. + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), vreinterpret_u32_u8(dst01_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), vreinterpret_u32_u8(dst01_u8), 1); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), vreinterpret_u32_u8(dst23_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), vreinterpret_u32_u8(dst23_u8), 1); +} + +static WEBP_INLINE void Add4x4(const int16x8_t row01, const int16x8_t row23, + uint8_t* const dst) { + uint32x2_t dst01 = vdup_n_u32(0); + uint32x2_t dst23 = vdup_n_u32(0); + + // Load the source pixels. + dst01 = vld1_lane_u32((uint32_t*)(dst + 0 * BPS), dst01, 0); + dst23 = vld1_lane_u32((uint32_t*)(dst + 2 * BPS), dst23, 0); + dst01 = vld1_lane_u32((uint32_t*)(dst + 1 * BPS), dst01, 1); + dst23 = vld1_lane_u32((uint32_t*)(dst + 3 * BPS), dst23, 1); + + { + // Convert to 16b. + const int16x8_t dst01_s16 = ConvertU8ToS16(vreinterpret_u8_u32(dst01)); + const int16x8_t dst23_s16 = ConvertU8ToS16(vreinterpret_u8_u32(dst23)); + + // Descale with rounding. + const int16x8_t out01 = vrsraq_n_s16(dst01_s16, row01, 3); + const int16x8_t out23 = vrsraq_n_s16(dst23_s16, row23, 3); + // Add the inverse transform. + SaturateAndStore4x4(dst, out01, out23); + } +} + +//----------------------------------------------------------------------------- +// Simple In-loop filtering (Paragraph 15.2) + +static uint8x16_t NeedsFilter(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + int thresh) { + const uint8x16_t thresh_v = vdupq_n_u8((uint8_t)thresh); + const uint8x16_t a_p0_q0 = vabdq_u8(p0, q0); // abs(p0-q0) + const uint8x16_t a_p1_q1 = vabdq_u8(p1, q1); // abs(p1-q1) + const uint8x16_t a_p0_q0_2 = vqaddq_u8(a_p0_q0, a_p0_q0); // 2 * abs(p0-q0) + const uint8x16_t a_p1_q1_2 = vshrq_n_u8(a_p1_q1, 1); // abs(p1-q1) / 2 + const uint8x16_t sum = vqaddq_u8(a_p0_q0_2, a_p1_q1_2); + const uint8x16_t mask = vcgeq_u8(thresh_v, sum); + return mask; +} + +static int8x16_t FlipSign(const uint8x16_t v) { + const uint8x16_t sign_bit = vdupq_n_u8(0x80); + return vreinterpretq_s8_u8(veorq_u8(v, sign_bit)); +} + +static uint8x16_t FlipSignBack(const int8x16_t v) { + const int8x16_t sign_bit = vdupq_n_s8(0x80); + return vreinterpretq_u8_s8(veorq_s8(v, sign_bit)); +} + +static int8x16_t GetBaseDelta(const int8x16_t p1, const int8x16_t p0, + const int8x16_t q0, const int8x16_t q1) { + const int8x16_t q0_p0 = vqsubq_s8(q0, p0); // (q0-p0) + const int8x16_t p1_q1 = vqsubq_s8(p1, q1); // (p1-q1) + const int8x16_t s1 = vqaddq_s8(p1_q1, q0_p0); // (p1-q1) + 1 * (q0 - p0) + const int8x16_t s2 = vqaddq_s8(q0_p0, s1); // (p1-q1) + 2 * (q0 - p0) + const int8x16_t s3 = vqaddq_s8(q0_p0, s2); // (p1-q1) + 3 * (q0 - p0) + return s3; +} + +static int8x16_t GetBaseDelta0(const int8x16_t p0, const int8x16_t q0) { + const int8x16_t q0_p0 = vqsubq_s8(q0, p0); // (q0-p0) + const int8x16_t s1 = vqaddq_s8(q0_p0, q0_p0); // 2 * (q0 - p0) + const int8x16_t s2 = vqaddq_s8(q0_p0, s1); // 3 * (q0 - p0) + return s2; +} + +//------------------------------------------------------------------------------ + +static void ApplyFilter2NoFlip(const int8x16_t p0s, const int8x16_t q0s, + const int8x16_t delta, + int8x16_t* const op0, int8x16_t* const oq0) { + const int8x16_t kCst3 = vdupq_n_s8(0x03); + const int8x16_t kCst4 = vdupq_n_s8(0x04); + const int8x16_t delta_p3 = vqaddq_s8(delta, kCst3); + const int8x16_t delta_p4 = vqaddq_s8(delta, kCst4); + const int8x16_t delta3 = vshrq_n_s8(delta_p3, 3); + const int8x16_t delta4 = vshrq_n_s8(delta_p4, 3); + *op0 = vqaddq_s8(p0s, delta3); + *oq0 = vqsubq_s8(q0s, delta4); +} + +#if defined(WEBP_USE_INTRINSICS) + +static void ApplyFilter2(const int8x16_t p0s, const int8x16_t q0s, + const int8x16_t delta, + uint8x16_t* const op0, uint8x16_t* const oq0) { + const int8x16_t kCst3 = vdupq_n_s8(0x03); + const int8x16_t kCst4 = vdupq_n_s8(0x04); + const int8x16_t delta_p3 = vqaddq_s8(delta, kCst3); + const int8x16_t delta_p4 = vqaddq_s8(delta, kCst4); + const int8x16_t delta3 = vshrq_n_s8(delta_p3, 3); + const int8x16_t delta4 = vshrq_n_s8(delta_p4, 3); + const int8x16_t sp0 = vqaddq_s8(p0s, delta3); + const int8x16_t sq0 = vqsubq_s8(q0s, delta4); + *op0 = FlipSignBack(sp0); + *oq0 = FlipSignBack(sq0); +} + +static void DoFilter2(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + const uint8x16_t mask, + uint8x16_t* const op0, uint8x16_t* const oq0) { + const int8x16_t p1s = FlipSign(p1); + const int8x16_t p0s = FlipSign(p0); + const int8x16_t q0s = FlipSign(q0); + const int8x16_t q1s = FlipSign(q1); + const int8x16_t delta0 = GetBaseDelta(p1s, p0s, q0s, q1s); + const int8x16_t delta1 = vandq_s8(delta0, vreinterpretq_s8_u8(mask)); + ApplyFilter2(p0s, q0s, delta1, op0, oq0); +} + +static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { + uint8x16_t p1, p0, q0, q1, op0, oq0; + Load16x4(p, stride, &p1, &p0, &q0, &q1); + { + const uint8x16_t mask = NeedsFilter(p1, p0, q0, q1, thresh); + DoFilter2(p1, p0, q0, q1, mask, &op0, &oq0); + } + Store16x2(op0, oq0, p, stride); +} + +static void SimpleHFilter16(uint8_t* p, int stride, int thresh) { + uint8x16_t p1, p0, q0, q1, oq0, op0; + Load4x16(p, stride, &p1, &p0, &q0, &q1); + { + const uint8x16_t mask = NeedsFilter(p1, p0, q0, q1, thresh); + DoFilter2(p1, p0, q0, q1, mask, &op0, &oq0); + } + Store2x16(op0, oq0, p, stride); +} + +#else + +#define QRegs "q0", "q1", "q2", "q3", \ "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" #define FLIP_SIGN_BIT2(a, b, s) \ @@ -70,40 +592,16 @@ extern "C" { DO_SIMPLE_FILTER(p0, q0, q9) /* apply filter */ \ FLIP_SIGN_BIT2(p0, q0, q10) -// Load/Store vertical edge -#define LOAD8x4(c1, c2, c3, c4, b1, b2, stride) \ - "vld4.8 {" #c1"[0], " #c2"[0], " #c3"[0], " #c4"[0]}," #b1 "," #stride"\n" \ - "vld4.8 {" #c1"[1], " #c2"[1], " #c3"[1], " #c4"[1]}," #b2 "," #stride"\n" \ - "vld4.8 {" #c1"[2], " #c2"[2], " #c3"[2], " #c4"[2]}," #b1 "," #stride"\n" \ - "vld4.8 {" #c1"[3], " #c2"[3], " #c3"[3], " #c4"[3]}," #b2 "," #stride"\n" \ - "vld4.8 {" #c1"[4], " #c2"[4], " #c3"[4], " #c4"[4]}," #b1 "," #stride"\n" \ - "vld4.8 {" #c1"[5], " #c2"[5], " #c3"[5], " #c4"[5]}," #b2 "," #stride"\n" \ - "vld4.8 {" #c1"[6], " #c2"[6], " #c3"[6], " #c4"[6]}," #b1 "," #stride"\n" \ - "vld4.8 {" #c1"[7], " #c2"[7], " #c3"[7], " #c4"[7]}," #b2 "," #stride"\n" - -#define STORE8x2(c1, c2, p, stride) \ - "vst2.8 {" #c1"[0], " #c2"[0]}," #p "," #stride " \n" \ - "vst2.8 {" #c1"[1], " #c2"[1]}," #p "," #stride " \n" \ - "vst2.8 {" #c1"[2], " #c2"[2]}," #p "," #stride " \n" \ - "vst2.8 {" #c1"[3], " #c2"[3]}," #p "," #stride " \n" \ - "vst2.8 {" #c1"[4], " #c2"[4]}," #p "," #stride " \n" \ - "vst2.8 {" #c1"[5], " #c2"[5]}," #p "," #stride " \n" \ - "vst2.8 {" #c1"[6], " #c2"[6]}," #p "," #stride " \n" \ - "vst2.8 {" #c1"[7], " #c2"[7]}," #p "," #stride " \n" - -//----------------------------------------------------------------------------- -// Simple In-loop filtering (Paragraph 15.2) - -static void SimpleVFilter16NEON(uint8_t* p, int stride, int thresh) { +static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { __asm__ volatile ( "sub %[p], %[p], %[stride], lsl #1 \n" // p -= 2 * stride "vld1.u8 {q1}, [%[p]], %[stride] \n" // p1 "vld1.u8 {q2}, [%[p]], %[stride] \n" // p0 "vld1.u8 {q3}, [%[p]], %[stride] \n" // q0 - "vld1.u8 {q4}, [%[p]] \n" // q1 + "vld1.u8 {q12}, [%[p]] \n" // q1 - DO_FILTER2(q1, q2, q3, q4, %[thresh]) + DO_FILTER2(q1, q2, q3, q12, %[thresh]) "sub %[p], %[p], %[stride], lsl #1 \n" // p -= 2 * stride @@ -115,25 +613,25 @@ static void SimpleVFilter16NEON(uint8_t* p, int stride, int thresh) { ); } -static void SimpleHFilter16NEON(uint8_t* p, int stride, int thresh) { +static void SimpleHFilter16(uint8_t* p, int stride, int thresh) { __asm__ volatile ( "sub r4, %[p], #2 \n" // base1 = p - 2 "lsl r6, %[stride], #1 \n" // r6 = 2 * stride "add r5, r4, %[stride] \n" // base2 = base1 + stride LOAD8x4(d2, d3, d4, d5, [r4], [r5], r6) - LOAD8x4(d6, d7, d8, d9, [r4], [r5], r6) - "vswp d3, d6 \n" // p1:q1 p0:q3 - "vswp d5, d8 \n" // q0:q2 q1:q4 - "vswp q2, q3 \n" // p1:q1 p0:q2 q0:q3 q1:q4 + LOAD8x4(d24, d25, d26, d27, [r4], [r5], r6) + "vswp d3, d24 \n" // p1:q1 p0:q3 + "vswp d5, d26 \n" // q0:q2 q1:q4 + "vswp q2, q12 \n" // p1:q1 p0:q2 q0:q3 q1:q4 - DO_FILTER2(q1, q2, q3, q4, %[thresh]) + DO_FILTER2(q1, q2, q12, q13, %[thresh]) "sub %[p], %[p], #1 \n" // p - 1 - "vswp d5, d6 \n" + "vswp d5, d24 \n" STORE8x2(d4, d5, [%[p]], %[stride]) - STORE8x2(d6, d7, [%[p]], %[stride]) + STORE8x2(d24, d25, [%[p]], %[stride]) : [p] "+r"(p) : [stride] "r"(stride), [thresh] "r"(thresh) @@ -141,47 +639,408 @@ static void SimpleHFilter16NEON(uint8_t* p, int stride, int thresh) { ); } -static void SimpleVFilter16iNEON(uint8_t* p, int stride, int thresh) { - int k; - for (k = 3; k > 0; --k) { +#endif // WEBP_USE_INTRINSICS + +static void SimpleVFilter16i(uint8_t* p, int stride, int thresh) { + uint32_t k; + for (k = 3; k != 0; --k) { p += 4 * stride; - SimpleVFilter16NEON(p, stride, thresh); + SimpleVFilter16(p, stride, thresh); } } -static void SimpleHFilter16iNEON(uint8_t* p, int stride, int thresh) { - int k; - for (k = 3; k > 0; --k) { +static void SimpleHFilter16i(uint8_t* p, int stride, int thresh) { + uint32_t k; + for (k = 3; k != 0; --k) { p += 4; - SimpleHFilter16NEON(p, stride, thresh); + SimpleHFilter16(p, stride, thresh); } } +//------------------------------------------------------------------------------ +// Complex In-loop filtering (Paragraph 15.3) + +static uint8x16_t NeedsHev(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + int hev_thresh) { + const uint8x16_t hev_thresh_v = vdupq_n_u8((uint8_t)hev_thresh); + const uint8x16_t a_p1_p0 = vabdq_u8(p1, p0); // abs(p1 - p0) + const uint8x16_t a_q1_q0 = vabdq_u8(q1, q0); // abs(q1 - q0) + const uint8x16_t a_max = vmaxq_u8(a_p1_p0, a_q1_q0); + const uint8x16_t mask = vcgtq_u8(a_max, hev_thresh_v); + return mask; +} + +static uint8x16_t NeedsFilter2(const uint8x16_t p3, const uint8x16_t p2, + const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + const uint8x16_t q2, const uint8x16_t q3, + int ithresh, int thresh) { + const uint8x16_t ithresh_v = vdupq_n_u8((uint8_t)ithresh); + const uint8x16_t a_p3_p2 = vabdq_u8(p3, p2); // abs(p3 - p2) + const uint8x16_t a_p2_p1 = vabdq_u8(p2, p1); // abs(p2 - p1) + const uint8x16_t a_p1_p0 = vabdq_u8(p1, p0); // abs(p1 - p0) + const uint8x16_t a_q3_q2 = vabdq_u8(q3, q2); // abs(q3 - q2) + const uint8x16_t a_q2_q1 = vabdq_u8(q2, q1); // abs(q2 - q1) + const uint8x16_t a_q1_q0 = vabdq_u8(q1, q0); // abs(q1 - q0) + const uint8x16_t max1 = vmaxq_u8(a_p3_p2, a_p2_p1); + const uint8x16_t max2 = vmaxq_u8(a_p1_p0, a_q3_q2); + const uint8x16_t max3 = vmaxq_u8(a_q2_q1, a_q1_q0); + const uint8x16_t max12 = vmaxq_u8(max1, max2); + const uint8x16_t max123 = vmaxq_u8(max12, max3); + const uint8x16_t mask2 = vcgeq_u8(ithresh_v, max123); + const uint8x16_t mask1 = NeedsFilter(p1, p0, q0, q1, thresh); + const uint8x16_t mask = vandq_u8(mask1, mask2); + return mask; +} + +// 4-points filter + +static void ApplyFilter4( + const int8x16_t p1, const int8x16_t p0, + const int8x16_t q0, const int8x16_t q1, + const int8x16_t delta0, + uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1) { + const int8x16_t kCst3 = vdupq_n_s8(0x03); + const int8x16_t kCst4 = vdupq_n_s8(0x04); + const int8x16_t delta1 = vqaddq_s8(delta0, kCst4); + const int8x16_t delta2 = vqaddq_s8(delta0, kCst3); + const int8x16_t a1 = vshrq_n_s8(delta1, 3); + const int8x16_t a2 = vshrq_n_s8(delta2, 3); + const int8x16_t a3 = vrshrq_n_s8(a1, 1); // a3 = (a1 + 1) >> 1 + *op0 = FlipSignBack(vqaddq_s8(p0, a2)); // clip(p0 + a2) + *oq0 = FlipSignBack(vqsubq_s8(q0, a1)); // clip(q0 - a1) + *op1 = FlipSignBack(vqaddq_s8(p1, a3)); // clip(p1 + a3) + *oq1 = FlipSignBack(vqsubq_s8(q1, a3)); // clip(q1 - a3) +} + +static void DoFilter4( + const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + const uint8x16_t mask, const uint8x16_t hev_mask, + uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1) { + // This is a fused version of DoFilter2() calling ApplyFilter2 directly + const int8x16_t p1s = FlipSign(p1); + int8x16_t p0s = FlipSign(p0); + int8x16_t q0s = FlipSign(q0); + const int8x16_t q1s = FlipSign(q1); + const uint8x16_t simple_lf_mask = vandq_u8(mask, hev_mask); + + // do_filter2 part (simple loopfilter on pixels with hev) + { + const int8x16_t delta = GetBaseDelta(p1s, p0s, q0s, q1s); + const int8x16_t simple_lf_delta = + vandq_s8(delta, vreinterpretq_s8_u8(simple_lf_mask)); + ApplyFilter2NoFlip(p0s, q0s, simple_lf_delta, &p0s, &q0s); + } + + // do_filter4 part (complex loopfilter on pixels without hev) + { + const int8x16_t delta0 = GetBaseDelta0(p0s, q0s); + // we use: (mask & hev_mask) ^ mask = mask & !hev_mask + const uint8x16_t complex_lf_mask = veorq_u8(simple_lf_mask, mask); + const int8x16_t complex_lf_delta = + vandq_s8(delta0, vreinterpretq_s8_u8(complex_lf_mask)); + ApplyFilter4(p1s, p0s, q0s, q1s, complex_lf_delta, op1, op0, oq0, oq1); + } +} + +// 6-points filter + +static void ApplyFilter6( + const int8x16_t p2, const int8x16_t p1, const int8x16_t p0, + const int8x16_t q0, const int8x16_t q1, const int8x16_t q2, + const int8x16_t delta, + uint8x16_t* const op2, uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1, uint8x16_t* const oq2) { + // We have to compute: X = (9*a+63) >> 7, Y = (18*a+63)>>7, Z = (27*a+63) >> 7 + // Turns out, there's a common sub-expression S=9 * a - 1 that can be used + // with the special vqrshrn_n_s16 rounding-shift-and-narrow instruction: + // X = (S + 64) >> 7, Y = (S + 32) >> 6, Z = (18 * a + S + 64) >> 7 + const int8x8_t delta_lo = vget_low_s8(delta); + const int8x8_t delta_hi = vget_high_s8(delta); + const int8x8_t kCst9 = vdup_n_s8(9); + const int16x8_t kCstm1 = vdupq_n_s16(-1); + const int8x8_t kCst18 = vdup_n_s8(18); + const int16x8_t S_lo = vmlal_s8(kCstm1, kCst9, delta_lo); // S = 9 * a - 1 + const int16x8_t S_hi = vmlal_s8(kCstm1, kCst9, delta_hi); + const int16x8_t Z_lo = vmlal_s8(S_lo, kCst18, delta_lo); // S + 18 * a + const int16x8_t Z_hi = vmlal_s8(S_hi, kCst18, delta_hi); + const int8x8_t a3_lo = vqrshrn_n_s16(S_lo, 7); // (9 * a + 63) >> 7 + const int8x8_t a3_hi = vqrshrn_n_s16(S_hi, 7); + const int8x8_t a2_lo = vqrshrn_n_s16(S_lo, 6); // (9 * a + 31) >> 6 + const int8x8_t a2_hi = vqrshrn_n_s16(S_hi, 6); + const int8x8_t a1_lo = vqrshrn_n_s16(Z_lo, 7); // (27 * a + 63) >> 7 + const int8x8_t a1_hi = vqrshrn_n_s16(Z_hi, 7); + const int8x16_t a1 = vcombine_s8(a1_lo, a1_hi); + const int8x16_t a2 = vcombine_s8(a2_lo, a2_hi); + const int8x16_t a3 = vcombine_s8(a3_lo, a3_hi); + + *op0 = FlipSignBack(vqaddq_s8(p0, a1)); // clip(p0 + a1) + *oq0 = FlipSignBack(vqsubq_s8(q0, a1)); // clip(q0 - q1) + *oq1 = FlipSignBack(vqsubq_s8(q1, a2)); // clip(q1 - a2) + *op1 = FlipSignBack(vqaddq_s8(p1, a2)); // clip(p1 + a2) + *oq2 = FlipSignBack(vqsubq_s8(q2, a3)); // clip(q2 - a3) + *op2 = FlipSignBack(vqaddq_s8(p2, a3)); // clip(p2 + a3) +} + +static void DoFilter6( + const uint8x16_t p2, const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, const uint8x16_t q2, + const uint8x16_t mask, const uint8x16_t hev_mask, + uint8x16_t* const op2, uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1, uint8x16_t* const oq2) { + // This is a fused version of DoFilter2() calling ApplyFilter2 directly + const int8x16_t p2s = FlipSign(p2); + const int8x16_t p1s = FlipSign(p1); + int8x16_t p0s = FlipSign(p0); + int8x16_t q0s = FlipSign(q0); + const int8x16_t q1s = FlipSign(q1); + const int8x16_t q2s = FlipSign(q2); + const uint8x16_t simple_lf_mask = vandq_u8(mask, hev_mask); + const int8x16_t delta0 = GetBaseDelta(p1s, p0s, q0s, q1s); + + // do_filter2 part (simple loopfilter on pixels with hev) + { + const int8x16_t simple_lf_delta = + vandq_s8(delta0, vreinterpretq_s8_u8(simple_lf_mask)); + ApplyFilter2NoFlip(p0s, q0s, simple_lf_delta, &p0s, &q0s); + } + + // do_filter6 part (complex loopfilter on pixels without hev) + { + // we use: (mask & hev_mask) ^ mask = mask & !hev_mask + const uint8x16_t complex_lf_mask = veorq_u8(simple_lf_mask, mask); + const int8x16_t complex_lf_delta = + vandq_s8(delta0, vreinterpretq_s8_u8(complex_lf_mask)); + ApplyFilter6(p2s, p1s, p0s, q0s, q1s, q2s, complex_lf_delta, + op2, op1, op0, oq0, oq1, oq2); + } +} + +// on macroblock edges + +static void VFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load16x8(p, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store16x2(op2, op1, p - 2 * stride, stride); + Store16x2(op0, oq0, p + 0 * stride, stride); + Store16x2(oq1, oq2, p + 2 * stride, stride); + } +} + +static void HFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load8x16(p, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store2x16(op2, op1, p - 2, stride); + Store2x16(op0, oq0, p + 0, stride); + Store2x16(oq1, oq2, p + 2, stride); + } +} + +// on three inner edges +static void VFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint32_t k; + uint8x16_t p3, p2, p1, p0; + Load16x4(p + 2 * stride, stride, &p3, &p2, &p1, &p0); + for (k = 3; k != 0; --k) { + uint8x16_t q0, q1, q2, q3; + p += 4 * stride; + Load16x4(p + 2 * stride, stride, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = + NeedsFilter2(p3, p2, p1, p0, q0, q1, q2, q3, ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev(p1, p0, q0, q1, hev_thresh); + // p3 and p2 are not just temporary variables here: they will be + // re-used for next span. And q2/q3 will become p1/p0 accordingly. + DoFilter4(p1, p0, q0, q1, mask, hev_mask, &p1, &p0, &p3, &p2); + Store16x4(p1, p0, p3, p2, p, stride); + p1 = q2; + p0 = q3; + } + } +} + +#if !defined(WORK_AROUND_GCC) +static void HFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint32_t k; + uint8x16_t p3, p2, p1, p0; + Load4x16(p + 2, stride, &p3, &p2, &p1, &p0); + for (k = 3; k != 0; --k) { + uint8x16_t q0, q1, q2, q3; + p += 4; + Load4x16(p + 2, stride, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = + NeedsFilter2(p3, p2, p1, p0, q0, q1, q2, q3, ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev(p1, p0, q0, q1, hev_thresh); + DoFilter4(p1, p0, q0, q1, mask, hev_mask, &p1, &p0, &p3, &p2); + Store4x16(p1, p0, p3, p2, p, stride); + p1 = q2; + p0 = q3; + } + } +} +#endif // !WORK_AROUND_GCC + +// 8-pixels wide variant, for chroma filtering +static void VFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load8x8x2(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store8x2x2(op2, op1, u - 2 * stride, v - 2 * stride, stride); + Store8x2x2(op0, oq0, u + 0 * stride, v + 0 * stride, stride); + Store8x2x2(oq1, oq2, u + 2 * stride, v + 2 * stride, stride); + } +} +static void VFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + u += 4 * stride; + v += 4 * stride; + Load8x8x2(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev(p1, p0, q0, q1, hev_thresh); + uint8x16_t op1, op0, oq0, oq1; + DoFilter4(p1, p0, q0, q1, mask, hev_mask, &op1, &op0, &oq0, &oq1); + Store8x4x2(op1, op0, oq0, oq1, u, v, stride); + } +} + +#if !defined(WORK_AROUND_GCC) +static void HFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load8x8x2T(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store6x8x2(op2, op1, op0, oq0, oq1, oq2, u, v, stride); + } +} + +static void HFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + u += 4; + v += 4; + Load8x8x2T(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev(p1, p0, q0, q1, hev_thresh); + uint8x16_t op1, op0, oq0, oq1; + DoFilter4(p1, p0, q0, q1, mask, hev_mask, &op1, &op0, &oq0, &oq1); + Store4x8x2(op1, op0, oq0, oq1, u, v, stride); + } +} +#endif // !WORK_AROUND_GCC + //----------------------------------------------------------------------------- // Inverse transforms (Paragraph 14.4) -static void TransformOneNEON(const int16_t *in, uint8_t *dst) { +// Technically these are unsigned but vqdmulh is only available in signed. +// vqdmulh returns high half (effectively >> 16) but also doubles the value, +// changing the >> 16 to >> 15 and requiring an additional >> 1. +// We use this to our advantage with kC2. The canonical value is 35468. +// However, the high bit is set so treating it as signed will give incorrect +// results. We avoid this by down shifting by 1 here to clear the highest bit. +// Combined with the doubling effect of vqdmulh we get >> 16. +// This can not be applied to kC1 because the lowest bit is set. Down shifting +// the constant would reduce precision. + +// libwebp uses a trick to avoid some extra addition that libvpx does. +// Instead of: +// temp2 = ip[12] + ((ip[12] * cospi8sqrt2minus1) >> 16); +// libwebp adds 1 << 16 to cospi8sqrt2minus1 (kC1). However, this causes the +// same issue with kC1 and vqdmulh that we work around by down shifting kC2 + +static const int16_t kC1 = 20091; +static const int16_t kC2 = 17734; // half of kC2, actually. See comment above. + +#if defined(WEBP_USE_INTRINSICS) +static WEBP_INLINE void Transpose8x2(const int16x8_t in0, const int16x8_t in1, + int16x8x2_t* const out) { + // a0 a1 a2 a3 | b0 b1 b2 b3 => a0 b0 c0 d0 | a1 b1 c1 d1 + // c0 c1 c2 c3 | d0 d1 d2 d3 a2 b2 c2 d2 | a3 b3 c3 d3 + const int16x8x2_t tmp0 = vzipq_s16(in0, in1); // a0 c0 a1 c1 a2 c2 ... + // b0 d0 b1 d1 b2 d2 ... + *out = vzipq_s16(tmp0.val[0], tmp0.val[1]); +} + +static WEBP_INLINE void TransformPass(int16x8x2_t* const rows) { + // {rows} = in0 | in4 + // in8 | in12 + // B1 = in4 | in12 + const int16x8_t B1 = + vcombine_s16(vget_high_s16(rows->val[0]), vget_high_s16(rows->val[1])); + // C0 = kC1 * in4 | kC1 * in12 + // C1 = kC2 * in4 | kC2 * in12 + const int16x8_t C0 = vsraq_n_s16(B1, vqdmulhq_n_s16(B1, kC1), 1); + const int16x8_t C1 = vqdmulhq_n_s16(B1, kC2); + const int16x4_t a = vqadd_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 + in8 + const int16x4_t b = vqsub_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 - in8 + // c = kC2 * in4 - kC1 * in12 + // d = kC1 * in4 + kC2 * in12 + const int16x4_t c = vqsub_s16(vget_low_s16(C1), vget_high_s16(C0)); + const int16x4_t d = vqadd_s16(vget_low_s16(C0), vget_high_s16(C1)); + const int16x8_t D0 = vcombine_s16(a, b); // D0 = a | b + const int16x8_t D1 = vcombine_s16(d, c); // D1 = d | c + const int16x8_t E0 = vqaddq_s16(D0, D1); // a+d | b+c + const int16x8_t E_tmp = vqsubq_s16(D0, D1); // a-d | b-c + const int16x8_t E1 = vcombine_s16(vget_high_s16(E_tmp), vget_low_s16(E_tmp)); + Transpose8x2(E0, E1, rows); +} + +static void TransformOne(const int16_t* in, uint8_t* dst) { + int16x8x2_t rows; + INIT_VECTOR2(rows, vld1q_s16(in + 0), vld1q_s16(in + 8)); + TransformPass(&rows); + TransformPass(&rows); + Add4x4(rows.val[0], rows.val[1], dst); +} + +#else + +static void TransformOne(const int16_t* in, uint8_t* dst) { const int kBPS = BPS; - const int16_t constants[] = {20091, 17734, 0, 0}; - /* kC1, kC2. Padded because vld1.16 loads 8 bytes - * Technically these are unsigned but vqdmulh is only available in signed. - * vqdmulh returns high half (effectively >> 16) but also doubles the value, - * changing the >> 16 to >> 15 and requiring an additional >> 1. - * We use this to our advantage with kC2. The canonical value is 35468. - * However, the high bit is set so treating it as signed will give incorrect - * results. We avoid this by down shifting by 1 here to clear the highest bit. - * Combined with the doubling effect of vqdmulh we get >> 16. - * This can not be applied to kC1 because the lowest bit is set. Down shifting - * the constant would reduce precision. - */ - - /* libwebp uses a trick to avoid some extra addition that libvpx does. - * Instead of: - * temp2 = ip[12] + ((ip[12] * cospi8sqrt2minus1) >> 16); - * libwebp adds 1 << 16 to cospi8sqrt2minus1 (kC1). However, this causes the - * same issue with kC1 and vqdmulh that we work around by down shifting kC2 - */ - + // kC1, kC2. Padded because vld1.16 loads 8 bytes + const int16_t constants[4] = { kC1, kC2, 0, 0 }; /* Adapted from libvpx: vp8/common/arm/neon/shortidct4x4llm_neon.asm */ __asm__ volatile ( "vld1.16 {q1, q2}, [%[in]] \n" @@ -309,99 +1168,472 @@ static void TransformOneNEON(const int16_t *in, uint8_t *dst) { ); } -static void TransformTwoNEON(const int16_t* in, uint8_t* dst, int do_two) { - TransformOneNEON(in, dst); +#endif // WEBP_USE_INTRINSICS + +static void TransformTwo(const int16_t* in, uint8_t* dst, int do_two) { + TransformOne(in, dst); if (do_two) { - TransformOneNEON(in + 16, dst + 4); + TransformOne(in + 16, dst + 4); } } -static void TransformWHT(const int16_t* in, int16_t* out) { - const int kStep = 32; // The store is only incrementing the pointer as if we - // had stored a single byte. - __asm__ volatile ( - // part 1 - // load data into q0, q1 - "vld1.16 {q0, q1}, [%[in]] \n" - - "vaddl.s16 q2, d0, d3 \n" // a0 = in[0] + in[12] - "vaddl.s16 q3, d1, d2 \n" // a1 = in[4] + in[8] - "vsubl.s16 q4, d1, d2 \n" // a2 = in[4] - in[8] - "vsubl.s16 q5, d0, d3 \n" // a3 = in[0] - in[12] - - "vadd.s32 q0, q2, q3 \n" // tmp[0] = a0 + a1 - "vsub.s32 q2, q2, q3 \n" // tmp[8] = a0 - a1 - "vadd.s32 q1, q5, q4 \n" // tmp[4] = a3 + a2 - "vsub.s32 q3, q5, q4 \n" // tmp[12] = a3 - a2 - - // Transpose - // q0 = tmp[0, 4, 8, 12], q1 = tmp[2, 6, 10, 14] - // q2 = tmp[1, 5, 9, 13], q3 = tmp[3, 7, 11, 15] - "vswp d1, d4 \n" // vtrn.64 q0, q2 - "vswp d3, d6 \n" // vtrn.64 q1, q3 - "vtrn.32 q0, q1 \n" - "vtrn.32 q2, q3 \n" - - "vmov.s32 q4, #3 \n" // dc = 3 - "vadd.s32 q0, q0, q4 \n" // dc = tmp[0] + 3 - "vadd.s32 q6, q0, q3 \n" // a0 = dc + tmp[3] - "vadd.s32 q7, q1, q2 \n" // a1 = tmp[1] + tmp[2] - "vsub.s32 q8, q1, q2 \n" // a2 = tmp[1] - tmp[2] - "vsub.s32 q9, q0, q3 \n" // a3 = dc - tmp[3] - - "vadd.s32 q0, q6, q7 \n" - "vshrn.s32 d0, q0, #3 \n" // (a0 + a1) >> 3 - "vadd.s32 q1, q9, q8 \n" - "vshrn.s32 d1, q1, #3 \n" // (a3 + a2) >> 3 - "vsub.s32 q2, q6, q7 \n" - "vshrn.s32 d2, q2, #3 \n" // (a0 - a1) >> 3 - "vsub.s32 q3, q9, q8 \n" - "vshrn.s32 d3, q3, #3 \n" // (a3 - a2) >> 3 - - // set the results to output - "vst1.16 d0[0], [%[out]], %[kStep] \n" - "vst1.16 d1[0], [%[out]], %[kStep] \n" - "vst1.16 d2[0], [%[out]], %[kStep] \n" - "vst1.16 d3[0], [%[out]], %[kStep] \n" - "vst1.16 d0[1], [%[out]], %[kStep] \n" - "vst1.16 d1[1], [%[out]], %[kStep] \n" - "vst1.16 d2[1], [%[out]], %[kStep] \n" - "vst1.16 d3[1], [%[out]], %[kStep] \n" - "vst1.16 d0[2], [%[out]], %[kStep] \n" - "vst1.16 d1[2], [%[out]], %[kStep] \n" - "vst1.16 d2[2], [%[out]], %[kStep] \n" - "vst1.16 d3[2], [%[out]], %[kStep] \n" - "vst1.16 d0[3], [%[out]], %[kStep] \n" - "vst1.16 d1[3], [%[out]], %[kStep] \n" - "vst1.16 d2[3], [%[out]], %[kStep] \n" - "vst1.16 d3[3], [%[out]], %[kStep] \n" - - : [out] "+r"(out) // modified registers - : [in] "r"(in), [kStep] "r"(kStep) // constants - : "memory", "q0", "q1", "q2", "q3", "q4", - "q5", "q6", "q7", "q8", "q9" // clobbered - ); +static void TransformDC(const int16_t* in, uint8_t* dst) { + const int16x8_t DC = vdupq_n_s16(in[0]); + Add4x4(DC, DC, dst); } -#endif // WEBP_USE_NEON +//------------------------------------------------------------------------------ + +#define STORE_WHT(dst, col, rows) do { \ + *dst = vgetq_lane_s32(rows.val[0], col); (dst) += 16; \ + *dst = vgetq_lane_s32(rows.val[1], col); (dst) += 16; \ + *dst = vgetq_lane_s32(rows.val[2], col); (dst) += 16; \ + *dst = vgetq_lane_s32(rows.val[3], col); (dst) += 16; \ +} while (0) + +static void TransformWHT(const int16_t* in, int16_t* out) { + int32x4x4_t tmp; + + { + // Load the source. + const int16x4_t in00_03 = vld1_s16(in + 0); + const int16x4_t in04_07 = vld1_s16(in + 4); + const int16x4_t in08_11 = vld1_s16(in + 8); + const int16x4_t in12_15 = vld1_s16(in + 12); + const int32x4_t a0 = vaddl_s16(in00_03, in12_15); // in[0..3] + in[12..15] + const int32x4_t a1 = vaddl_s16(in04_07, in08_11); // in[4..7] + in[8..11] + const int32x4_t a2 = vsubl_s16(in04_07, in08_11); // in[4..7] - in[8..11] + const int32x4_t a3 = vsubl_s16(in00_03, in12_15); // in[0..3] - in[12..15] + tmp.val[0] = vaddq_s32(a0, a1); + tmp.val[1] = vaddq_s32(a3, a2); + tmp.val[2] = vsubq_s32(a0, a1); + tmp.val[3] = vsubq_s32(a3, a2); + // Arrange the temporary results column-wise. + tmp = Transpose4x4(tmp); + } + + { + const int32x4_t kCst3 = vdupq_n_s32(3); + const int32x4_t dc = vaddq_s32(tmp.val[0], kCst3); // add rounder + const int32x4_t a0 = vaddq_s32(dc, tmp.val[3]); + const int32x4_t a1 = vaddq_s32(tmp.val[1], tmp.val[2]); + const int32x4_t a2 = vsubq_s32(tmp.val[1], tmp.val[2]); + const int32x4_t a3 = vsubq_s32(dc, tmp.val[3]); + + tmp.val[0] = vaddq_s32(a0, a1); + tmp.val[1] = vaddq_s32(a3, a2); + tmp.val[2] = vsubq_s32(a0, a1); + tmp.val[3] = vsubq_s32(a3, a2); + + // right shift the results by 3. + tmp.val[0] = vshrq_n_s32(tmp.val[0], 3); + tmp.val[1] = vshrq_n_s32(tmp.val[1], 3); + tmp.val[2] = vshrq_n_s32(tmp.val[2], 3); + tmp.val[3] = vshrq_n_s32(tmp.val[3], 3); + + STORE_WHT(out, 0, tmp); + STORE_WHT(out, 1, tmp); + STORE_WHT(out, 2, tmp); + STORE_WHT(out, 3, tmp); + } +} + +#undef STORE_WHT + +//------------------------------------------------------------------------------ + +#define MUL(a, b) (((a) * (b)) >> 16) +static void TransformAC3(const int16_t* in, uint8_t* dst) { + static const int kC1_full = 20091 + (1 << 16); + static const int kC2_full = 35468; + const int16x4_t A = vld1_dup_s16(in); + const int16x4_t c4 = vdup_n_s16(MUL(in[4], kC2_full)); + const int16x4_t d4 = vdup_n_s16(MUL(in[4], kC1_full)); + const int c1 = MUL(in[1], kC2_full); + const int d1 = MUL(in[1], kC1_full); + const uint64_t cd = (uint64_t)( d1 & 0xffff) << 0 | + (uint64_t)( c1 & 0xffff) << 16 | + (uint64_t)(-c1 & 0xffff) << 32 | + (uint64_t)(-d1 & 0xffff) << 48; + const int16x4_t CD = vcreate_s16(cd); + const int16x4_t B = vqadd_s16(A, CD); + const int16x8_t m0_m1 = vcombine_s16(vqadd_s16(B, d4), vqadd_s16(B, c4)); + const int16x8_t m2_m3 = vcombine_s16(vqsub_s16(B, c4), vqsub_s16(B, d4)); + Add4x4(m0_m1, m2_m3, dst); +} +#undef MUL + +//------------------------------------------------------------------------------ +// 4x4 + +static void DC4(uint8_t* dst) { // DC + const uint8x8_t A = vld1_u8(dst - BPS); // top row + const uint16x4_t p0 = vpaddl_u8(A); // cascading summation of the top + const uint16x4_t p1 = vpadd_u16(p0, p0); + const uint16x8_t L0 = vmovl_u8(vld1_u8(dst + 0 * BPS - 1)); + const uint16x8_t L1 = vmovl_u8(vld1_u8(dst + 1 * BPS - 1)); + const uint16x8_t L2 = vmovl_u8(vld1_u8(dst + 2 * BPS - 1)); + const uint16x8_t L3 = vmovl_u8(vld1_u8(dst + 3 * BPS - 1)); + const uint16x8_t s0 = vaddq_u16(L0, L1); + const uint16x8_t s1 = vaddq_u16(L2, L3); + const uint16x8_t s01 = vaddq_u16(s0, s1); + const uint16x8_t sum = vaddq_u16(s01, vcombine_u16(p1, p1)); + const uint8x8_t dc0 = vrshrn_n_u16(sum, 3); // (sum + 4) >> 3 + const uint8x8_t dc = vdup_lane_u8(dc0, 0); + int i; + for (i = 0; i < 4; ++i) { + vst1_lane_u32((uint32_t*)(dst + i * BPS), vreinterpret_u32_u8(dc), 0); + } +} + +// TrueMotion (4x4 + 8x8) +static WEBP_INLINE void TrueMotion(uint8_t* dst, int size) { + const uint8x8_t TL = vld1_dup_u8(dst - BPS - 1); // top-left pixel 'A[-1]' + const uint8x8_t T = vld1_u8(dst - BPS); // top row 'A[0..3]' + const int16x8_t d = vreinterpretq_s16_u16(vsubl_u8(T, TL)); // A[c] - A[-1] + int y; + for (y = 0; y < size; y += 4) { + // left edge + const int16x8_t L0 = ConvertU8ToS16(vld1_dup_u8(dst + 0 * BPS - 1)); + const int16x8_t L1 = ConvertU8ToS16(vld1_dup_u8(dst + 1 * BPS - 1)); + const int16x8_t L2 = ConvertU8ToS16(vld1_dup_u8(dst + 2 * BPS - 1)); + const int16x8_t L3 = ConvertU8ToS16(vld1_dup_u8(dst + 3 * BPS - 1)); + const int16x8_t r0 = vaddq_s16(L0, d); // L[r] + A[c] - A[-1] + const int16x8_t r1 = vaddq_s16(L1, d); + const int16x8_t r2 = vaddq_s16(L2, d); + const int16x8_t r3 = vaddq_s16(L3, d); + // Saturate and store the result. + const uint32x2_t r0_u32 = vreinterpret_u32_u8(vqmovun_s16(r0)); + const uint32x2_t r1_u32 = vreinterpret_u32_u8(vqmovun_s16(r1)); + const uint32x2_t r2_u32 = vreinterpret_u32_u8(vqmovun_s16(r2)); + const uint32x2_t r3_u32 = vreinterpret_u32_u8(vqmovun_s16(r3)); + if (size == 4) { + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), r0_u32, 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), r1_u32, 0); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), r2_u32, 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), r3_u32, 0); + } else { + vst1_u32((uint32_t*)(dst + 0 * BPS), r0_u32); + vst1_u32((uint32_t*)(dst + 1 * BPS), r1_u32); + vst1_u32((uint32_t*)(dst + 2 * BPS), r2_u32); + vst1_u32((uint32_t*)(dst + 3 * BPS), r3_u32); + } + dst += 4 * BPS; + } +} + +static void TM4(uint8_t* dst) { TrueMotion(dst, 4); } + +static void VE4(uint8_t* dst) { // vertical + // NB: avoid vld1_u64 here as an alignment hint may be added -> SIGBUS. + const uint64x1_t A0 = vreinterpret_u64_u8(vld1_u8(dst - BPS - 1)); // top row + const uint64x1_t A1 = vshr_n_u64(A0, 8); + const uint64x1_t A2 = vshr_n_u64(A0, 16); + const uint8x8_t ABCDEFGH = vreinterpret_u8_u64(A0); + const uint8x8_t BCDEFGH0 = vreinterpret_u8_u64(A1); + const uint8x8_t CDEFGH00 = vreinterpret_u8_u64(A2); + const uint8x8_t b = vhadd_u8(ABCDEFGH, CDEFGH00); + const uint8x8_t avg = vrhadd_u8(b, BCDEFGH0); + int i; + for (i = 0; i < 4; ++i) { + vst1_lane_u32((uint32_t*)(dst + i * BPS), vreinterpret_u32_u8(avg), 0); + } +} + +static void RD4(uint8_t* dst) { // Down-right + const uint8x8_t XABCD_u8 = vld1_u8(dst - BPS - 1); + const uint64x1_t XABCD = vreinterpret_u64_u8(XABCD_u8); + const uint64x1_t ____XABC = vshl_n_u64(XABCD, 32); + const uint32_t I = dst[-1 + 0 * BPS]; + const uint32_t J = dst[-1 + 1 * BPS]; + const uint32_t K = dst[-1 + 2 * BPS]; + const uint32_t L = dst[-1 + 3 * BPS]; + const uint64x1_t LKJI____ = vcreate_u64(L | (K << 8) | (J << 16) | (I << 24)); + const uint64x1_t LKJIXABC = vorr_u64(LKJI____, ____XABC); + const uint8x8_t KJIXABC_ = vreinterpret_u8_u64(vshr_n_u64(LKJIXABC, 8)); + const uint8x8_t JIXABC__ = vreinterpret_u8_u64(vshr_n_u64(LKJIXABC, 16)); + const uint8_t D = vget_lane_u8(XABCD_u8, 4); + const uint8x8_t JIXABCD_ = vset_lane_u8(D, JIXABC__, 6); + const uint8x8_t LKJIXABC_u8 = vreinterpret_u8_u64(LKJIXABC); + const uint8x8_t avg1 = vhadd_u8(JIXABCD_, LKJIXABC_u8); + const uint8x8_t avg2 = vrhadd_u8(avg1, KJIXABC_); + const uint64x1_t avg2_u64 = vreinterpret_u64_u8(avg2); + const uint32x2_t r3 = vreinterpret_u32_u8(avg2); + const uint32x2_t r2 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 8)); + const uint32x2_t r1 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 16)); + const uint32x2_t r0 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 24)); + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), r0, 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), r1, 0); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), r2, 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), r3, 0); +} + +static void LD4(uint8_t* dst) { // Down-left + // Note using the same shift trick as VE4() is slower here. + const uint8x8_t ABCDEFGH = vld1_u8(dst - BPS + 0); + const uint8x8_t BCDEFGH0 = vld1_u8(dst - BPS + 1); + const uint8x8_t CDEFGH00 = vld1_u8(dst - BPS + 2); + const uint8x8_t CDEFGHH0 = vset_lane_u8(dst[-BPS + 7], CDEFGH00, 6); + const uint8x8_t avg1 = vhadd_u8(ABCDEFGH, CDEFGHH0); + const uint8x8_t avg2 = vrhadd_u8(avg1, BCDEFGH0); + const uint64x1_t avg2_u64 = vreinterpret_u64_u8(avg2); + const uint32x2_t r0 = vreinterpret_u32_u8(avg2); + const uint32x2_t r1 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 8)); + const uint32x2_t r2 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 16)); + const uint32x2_t r3 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 24)); + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), r0, 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), r1, 0); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), r2, 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), r3, 0); +} + +//------------------------------------------------------------------------------ +// Chroma + +static void VE8uv(uint8_t* dst) { // vertical + const uint8x8_t top = vld1_u8(dst - BPS); + int j; + for (j = 0; j < 8; ++j) { + vst1_u8(dst + j * BPS, top); + } +} + +static void HE8uv(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 8; ++j) { + const uint8x8_t left = vld1_dup_u8(dst - 1); + vst1_u8(dst, left); + dst += BPS; + } +} + +static WEBP_INLINE void DC8(uint8_t* dst, int do_top, int do_left) { + uint16x8_t sum_top; + uint16x8_t sum_left; + uint8x8_t dc0; + + if (do_top) { + const uint8x8_t A = vld1_u8(dst - BPS); // top row + const uint16x4_t p0 = vpaddl_u8(A); // cascading summation of the top + const uint16x4_t p1 = vpadd_u16(p0, p0); + const uint16x4_t p2 = vpadd_u16(p1, p1); + sum_top = vcombine_u16(p2, p2); + } + + if (do_left) { + const uint16x8_t L0 = vmovl_u8(vld1_u8(dst + 0 * BPS - 1)); + const uint16x8_t L1 = vmovl_u8(vld1_u8(dst + 1 * BPS - 1)); + const uint16x8_t L2 = vmovl_u8(vld1_u8(dst + 2 * BPS - 1)); + const uint16x8_t L3 = vmovl_u8(vld1_u8(dst + 3 * BPS - 1)); + const uint16x8_t L4 = vmovl_u8(vld1_u8(dst + 4 * BPS - 1)); + const uint16x8_t L5 = vmovl_u8(vld1_u8(dst + 5 * BPS - 1)); + const uint16x8_t L6 = vmovl_u8(vld1_u8(dst + 6 * BPS - 1)); + const uint16x8_t L7 = vmovl_u8(vld1_u8(dst + 7 * BPS - 1)); + const uint16x8_t s0 = vaddq_u16(L0, L1); + const uint16x8_t s1 = vaddq_u16(L2, L3); + const uint16x8_t s2 = vaddq_u16(L4, L5); + const uint16x8_t s3 = vaddq_u16(L6, L7); + const uint16x8_t s01 = vaddq_u16(s0, s1); + const uint16x8_t s23 = vaddq_u16(s2, s3); + sum_left = vaddq_u16(s01, s23); + } + + if (do_top && do_left) { + const uint16x8_t sum = vaddq_u16(sum_left, sum_top); + dc0 = vrshrn_n_u16(sum, 4); + } else if (do_top) { + dc0 = vrshrn_n_u16(sum_top, 3); + } else if (do_left) { + dc0 = vrshrn_n_u16(sum_left, 3); + } else { + dc0 = vdup_n_u8(0x80); + } + + { + const uint8x8_t dc = vdup_lane_u8(dc0, 0); + int i; + for (i = 0; i < 8; ++i) { + vst1_u32((uint32_t*)(dst + i * BPS), vreinterpret_u32_u8(dc)); + } + } +} + +static void DC8uv(uint8_t* dst) { DC8(dst, 1, 1); } +static void DC8uvNoTop(uint8_t* dst) { DC8(dst, 0, 1); } +static void DC8uvNoLeft(uint8_t* dst) { DC8(dst, 1, 0); } +static void DC8uvNoTopLeft(uint8_t* dst) { DC8(dst, 0, 0); } + +static void TM8uv(uint8_t* dst) { TrueMotion(dst, 8); } + +//------------------------------------------------------------------------------ +// 16x16 + +static void VE16(uint8_t* dst) { // vertical + const uint8x16_t top = vld1q_u8(dst - BPS); + int j; + for (j = 0; j < 16; ++j) { + vst1q_u8(dst + j * BPS, top); + } +} + +static void HE16(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 16; ++j) { + const uint8x16_t left = vld1q_dup_u8(dst - 1); + vst1q_u8(dst, left); + dst += BPS; + } +} + +static WEBP_INLINE void DC16(uint8_t* dst, int do_top, int do_left) { + uint16x8_t sum_top; + uint16x8_t sum_left; + uint8x8_t dc0; + + if (do_top) { + const uint8x16_t A = vld1q_u8(dst - BPS); // top row + const uint16x8_t p0 = vpaddlq_u8(A); // cascading summation of the top + const uint16x4_t p1 = vadd_u16(vget_low_u16(p0), vget_high_u16(p0)); + const uint16x4_t p2 = vpadd_u16(p1, p1); + const uint16x4_t p3 = vpadd_u16(p2, p2); + sum_top = vcombine_u16(p3, p3); + } + + if (do_left) { + int i; + sum_left = vdupq_n_u16(0); + for (i = 0; i < 16; i += 8) { + const uint16x8_t L0 = vmovl_u8(vld1_u8(dst + (i + 0) * BPS - 1)); + const uint16x8_t L1 = vmovl_u8(vld1_u8(dst + (i + 1) * BPS - 1)); + const uint16x8_t L2 = vmovl_u8(vld1_u8(dst + (i + 2) * BPS - 1)); + const uint16x8_t L3 = vmovl_u8(vld1_u8(dst + (i + 3) * BPS - 1)); + const uint16x8_t L4 = vmovl_u8(vld1_u8(dst + (i + 4) * BPS - 1)); + const uint16x8_t L5 = vmovl_u8(vld1_u8(dst + (i + 5) * BPS - 1)); + const uint16x8_t L6 = vmovl_u8(vld1_u8(dst + (i + 6) * BPS - 1)); + const uint16x8_t L7 = vmovl_u8(vld1_u8(dst + (i + 7) * BPS - 1)); + const uint16x8_t s0 = vaddq_u16(L0, L1); + const uint16x8_t s1 = vaddq_u16(L2, L3); + const uint16x8_t s2 = vaddq_u16(L4, L5); + const uint16x8_t s3 = vaddq_u16(L6, L7); + const uint16x8_t s01 = vaddq_u16(s0, s1); + const uint16x8_t s23 = vaddq_u16(s2, s3); + const uint16x8_t sum = vaddq_u16(s01, s23); + sum_left = vaddq_u16(sum_left, sum); + } + } + + if (do_top && do_left) { + const uint16x8_t sum = vaddq_u16(sum_left, sum_top); + dc0 = vrshrn_n_u16(sum, 5); + } else if (do_top) { + dc0 = vrshrn_n_u16(sum_top, 4); + } else if (do_left) { + dc0 = vrshrn_n_u16(sum_left, 4); + } else { + dc0 = vdup_n_u8(0x80); + } + + { + const uint8x16_t dc = vdupq_lane_u8(dc0, 0); + int i; + for (i = 0; i < 16; ++i) { + vst1q_u8(dst + i * BPS, dc); + } + } +} + +static void DC16TopLeft(uint8_t* dst) { DC16(dst, 1, 1); } +static void DC16NoTop(uint8_t* dst) { DC16(dst, 0, 1); } +static void DC16NoLeft(uint8_t* dst) { DC16(dst, 1, 0); } +static void DC16NoTopLeft(uint8_t* dst) { DC16(dst, 0, 0); } + +static void TM16(uint8_t* dst) { + const uint8x8_t TL = vld1_dup_u8(dst - BPS - 1); // top-left pixel 'A[-1]' + const uint8x16_t T = vld1q_u8(dst - BPS); // top row 'A[0..15]' + // A[c] - A[-1] + const int16x8_t d_lo = vreinterpretq_s16_u16(vsubl_u8(vget_low_u8(T), TL)); + const int16x8_t d_hi = vreinterpretq_s16_u16(vsubl_u8(vget_high_u8(T), TL)); + int y; + for (y = 0; y < 16; y += 4) { + // left edge + const int16x8_t L0 = ConvertU8ToS16(vld1_dup_u8(dst + 0 * BPS - 1)); + const int16x8_t L1 = ConvertU8ToS16(vld1_dup_u8(dst + 1 * BPS - 1)); + const int16x8_t L2 = ConvertU8ToS16(vld1_dup_u8(dst + 2 * BPS - 1)); + const int16x8_t L3 = ConvertU8ToS16(vld1_dup_u8(dst + 3 * BPS - 1)); + const int16x8_t r0_lo = vaddq_s16(L0, d_lo); // L[r] + A[c] - A[-1] + const int16x8_t r1_lo = vaddq_s16(L1, d_lo); + const int16x8_t r2_lo = vaddq_s16(L2, d_lo); + const int16x8_t r3_lo = vaddq_s16(L3, d_lo); + const int16x8_t r0_hi = vaddq_s16(L0, d_hi); + const int16x8_t r1_hi = vaddq_s16(L1, d_hi); + const int16x8_t r2_hi = vaddq_s16(L2, d_hi); + const int16x8_t r3_hi = vaddq_s16(L3, d_hi); + // Saturate and store the result. + const uint8x16_t row0 = vcombine_u8(vqmovun_s16(r0_lo), vqmovun_s16(r0_hi)); + const uint8x16_t row1 = vcombine_u8(vqmovun_s16(r1_lo), vqmovun_s16(r1_hi)); + const uint8x16_t row2 = vcombine_u8(vqmovun_s16(r2_lo), vqmovun_s16(r2_hi)); + const uint8x16_t row3 = vcombine_u8(vqmovun_s16(r3_lo), vqmovun_s16(r3_hi)); + vst1q_u8(dst + 0 * BPS, row0); + vst1q_u8(dst + 1 * BPS, row1); + vst1q_u8(dst + 2 * BPS, row2); + vst1q_u8(dst + 3 * BPS, row3); + dst += 4 * BPS; + } +} //------------------------------------------------------------------------------ // Entry point extern void VP8DspInitNEON(void); -void VP8DspInitNEON(void) { -#if defined(WEBP_USE_NEON) - VP8Transform = TransformTwoNEON; +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitNEON(void) { + VP8Transform = TransformTwo; + VP8TransformAC3 = TransformAC3; + VP8TransformDC = TransformDC; VP8TransformWHT = TransformWHT; - VP8SimpleVFilter16 = SimpleVFilter16NEON; - VP8SimpleHFilter16 = SimpleHFilter16NEON; - VP8SimpleVFilter16i = SimpleVFilter16iNEON; - VP8SimpleHFilter16i = SimpleHFilter16iNEON; -#endif // WEBP_USE_NEON + VP8VFilter16 = VFilter16; + VP8VFilter16i = VFilter16i; + VP8HFilter16 = HFilter16; +#if !defined(WORK_AROUND_GCC) + VP8HFilter16i = HFilter16i; +#endif + VP8VFilter8 = VFilter8; + VP8VFilter8i = VFilter8i; +#if !defined(WORK_AROUND_GCC) + VP8HFilter8 = HFilter8; + VP8HFilter8i = HFilter8i; +#endif + VP8SimpleVFilter16 = SimpleVFilter16; + VP8SimpleHFilter16 = SimpleHFilter16; + VP8SimpleVFilter16i = SimpleVFilter16i; + VP8SimpleHFilter16i = SimpleHFilter16i; + + VP8PredLuma4[0] = DC4; + VP8PredLuma4[1] = TM4; + VP8PredLuma4[2] = VE4; + VP8PredLuma4[4] = RD4; + VP8PredLuma4[6] = LD4; + + VP8PredLuma16[0] = DC16TopLeft; + VP8PredLuma16[1] = TM16; + VP8PredLuma16[2] = VE16; + VP8PredLuma16[3] = HE16; + VP8PredLuma16[4] = DC16NoTop; + VP8PredLuma16[5] = DC16NoLeft; + VP8PredLuma16[6] = DC16NoTopLeft; + + VP8PredChroma8[0] = DC8uv; + VP8PredChroma8[1] = TM8uv; + VP8PredChroma8[2] = VE8uv; + VP8PredChroma8[3] = HE8uv; + VP8PredChroma8[4] = DC8uvNoTop; + VP8PredChroma8[5] = DC8uvNoLeft; + VP8PredChroma8[6] = DC8uvNoTopLeft; } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8DspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/3rdparty/libwebp/dsp/dec_sse2.c b/3rdparty/libwebp/dsp/dec_sse2.c index 6be94678ef..411fb02768 100644 --- a/3rdparty/libwebp/dsp/dec_sse2.c +++ b/3rdparty/libwebp/dsp/dec_sse2.c @@ -14,19 +14,21 @@ #include "./dsp.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - #if defined(WEBP_USE_SSE2) +// The 3-coeff sparse transform in SSE2 is not really faster than the plain-C +// one it seems => disable it by default. Uncomment the following to enable: +// #define USE_TRANSFORM_AC3 + #include -#include "../dec/vp8i.h" +#include "./common_sse2.h" +#include "../dec/vp8i_dec.h" +#include "../utils/utils.h" //------------------------------------------------------------------------------ // Transforms (Paragraph 14.4) -static void TransformSSE2(const int16_t* in, uint8_t* dst, int do_two) { +static void Transform(const int16_t* in, uint8_t* dst, int do_two) { // This implementation makes use of 16-bit fixed point versions of two // multiply constants: // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 @@ -52,19 +54,19 @@ static void TransformSSE2(const int16_t* in, uint8_t* dst, int do_two) { // vectors will just contain random value we'll never use nor store. __m128i in0, in1, in2, in3; { - in0 = _mm_loadl_epi64((__m128i*)&in[0]); - in1 = _mm_loadl_epi64((__m128i*)&in[4]); - in2 = _mm_loadl_epi64((__m128i*)&in[8]); - in3 = _mm_loadl_epi64((__m128i*)&in[12]); + in0 = _mm_loadl_epi64((const __m128i*)&in[0]); + in1 = _mm_loadl_epi64((const __m128i*)&in[4]); + in2 = _mm_loadl_epi64((const __m128i*)&in[8]); + in3 = _mm_loadl_epi64((const __m128i*)&in[12]); // a00 a10 a20 a30 x x x x // a01 a11 a21 a31 x x x x // a02 a12 a22 a32 x x x x // a03 a13 a23 a33 x x x x if (do_two) { - const __m128i inB0 = _mm_loadl_epi64((__m128i*)&in[16]); - const __m128i inB1 = _mm_loadl_epi64((__m128i*)&in[20]); - const __m128i inB2 = _mm_loadl_epi64((__m128i*)&in[24]); - const __m128i inB3 = _mm_loadl_epi64((__m128i*)&in[28]); + const __m128i inB0 = _mm_loadl_epi64((const __m128i*)&in[16]); + const __m128i inB1 = _mm_loadl_epi64((const __m128i*)&in[20]); + const __m128i inB2 = _mm_loadl_epi64((const __m128i*)&in[24]); + const __m128i inB3 = _mm_loadl_epi64((const __m128i*)&in[28]); in0 = _mm_unpacklo_epi64(in0, inB0); in1 = _mm_unpacklo_epi64(in1, inB1); in2 = _mm_unpacklo_epi64(in2, inB2); @@ -102,34 +104,7 @@ static void TransformSSE2(const int16_t* in, uint8_t* dst, int do_two) { const __m128i tmp3 = _mm_sub_epi16(a, d); // Transpose the two 4x4. - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - const __m128i transpose0_0 = _mm_unpacklo_epi16(tmp0, tmp1); - const __m128i transpose0_1 = _mm_unpacklo_epi16(tmp2, tmp3); - const __m128i transpose0_2 = _mm_unpackhi_epi16(tmp0, tmp1); - const __m128i transpose0_3 = _mm_unpackhi_epi16(tmp2, tmp3); - // a00 a10 a01 a11 a02 a12 a03 a13 - // a20 a30 a21 a31 a22 a32 a23 a33 - // b00 b10 b01 b11 b02 b12 b03 b13 - // b20 b30 b21 b31 b22 b32 b23 b33 - const __m128i transpose1_0 = _mm_unpacklo_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_1 = _mm_unpacklo_epi32(transpose0_2, transpose0_3); - const __m128i transpose1_2 = _mm_unpackhi_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_3 = _mm_unpackhi_epi32(transpose0_2, transpose0_3); - // a00 a10 a20 a30 a01 a11 a21 a31 - // b00 b10 b20 b30 b01 b11 b21 b31 - // a02 a12 a22 a32 a03 a13 a23 a33 - // b02 b12 a22 b32 b03 b13 b23 b33 - T0 = _mm_unpacklo_epi64(transpose1_0, transpose1_1); - T1 = _mm_unpackhi_epi64(transpose1_0, transpose1_1); - T2 = _mm_unpacklo_epi64(transpose1_2, transpose1_3); - T3 = _mm_unpackhi_epi64(transpose1_2, transpose1_3); - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 + VP8Transpose_2_4x4_16b(&tmp0, &tmp1, &tmp2, &tmp3, &T0, &T1, &T2, &T3); } // Horizontal pass and subsequent transpose. @@ -164,34 +139,8 @@ static void TransformSSE2(const int16_t* in, uint8_t* dst, int do_two) { const __m128i shifted3 = _mm_srai_epi16(tmp3, 3); // Transpose the two 4x4. - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - const __m128i transpose0_0 = _mm_unpacklo_epi16(shifted0, shifted1); - const __m128i transpose0_1 = _mm_unpacklo_epi16(shifted2, shifted3); - const __m128i transpose0_2 = _mm_unpackhi_epi16(shifted0, shifted1); - const __m128i transpose0_3 = _mm_unpackhi_epi16(shifted2, shifted3); - // a00 a10 a01 a11 a02 a12 a03 a13 - // a20 a30 a21 a31 a22 a32 a23 a33 - // b00 b10 b01 b11 b02 b12 b03 b13 - // b20 b30 b21 b31 b22 b32 b23 b33 - const __m128i transpose1_0 = _mm_unpacklo_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_1 = _mm_unpacklo_epi32(transpose0_2, transpose0_3); - const __m128i transpose1_2 = _mm_unpackhi_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_3 = _mm_unpackhi_epi32(transpose0_2, transpose0_3); - // a00 a10 a20 a30 a01 a11 a21 a31 - // b00 b10 b20 b30 b01 b11 b21 b31 - // a02 a12 a22 a32 a03 a13 a23 a33 - // b02 b12 a22 b32 b03 b13 b23 b33 - T0 = _mm_unpacklo_epi64(transpose1_0, transpose1_1); - T1 = _mm_unpackhi_epi64(transpose1_0, transpose1_1); - T2 = _mm_unpacklo_epi64(transpose1_2, transpose1_3); - T3 = _mm_unpackhi_epi64(transpose1_2, transpose1_3); - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 + VP8Transpose_2_4x4_16b(&shifted0, &shifted1, &shifted2, &shifted3, &T0, &T1, + &T2, &T3); } // Add inverse transform to 'dst' and store. @@ -201,16 +150,16 @@ static void TransformSSE2(const int16_t* in, uint8_t* dst, int do_two) { __m128i dst0, dst1, dst2, dst3; if (do_two) { // Load eight bytes/pixels per line. - dst0 = _mm_loadl_epi64((__m128i*)&dst[0 * BPS]); - dst1 = _mm_loadl_epi64((__m128i*)&dst[1 * BPS]); - dst2 = _mm_loadl_epi64((__m128i*)&dst[2 * BPS]); - dst3 = _mm_loadl_epi64((__m128i*)&dst[3 * BPS]); + dst0 = _mm_loadl_epi64((__m128i*)(dst + 0 * BPS)); + dst1 = _mm_loadl_epi64((__m128i*)(dst + 1 * BPS)); + dst2 = _mm_loadl_epi64((__m128i*)(dst + 2 * BPS)); + dst3 = _mm_loadl_epi64((__m128i*)(dst + 3 * BPS)); } else { // Load four bytes/pixels per line. - dst0 = _mm_cvtsi32_si128(*(int*)&dst[0 * BPS]); - dst1 = _mm_cvtsi32_si128(*(int*)&dst[1 * BPS]); - dst2 = _mm_cvtsi32_si128(*(int*)&dst[2 * BPS]); - dst3 = _mm_cvtsi32_si128(*(int*)&dst[3 * BPS]); + dst0 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 0 * BPS)); + dst1 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 1 * BPS)); + dst2 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 2 * BPS)); + dst3 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 3 * BPS)); } // Convert to 16b. dst0 = _mm_unpacklo_epi8(dst0, zero); @@ -230,20 +179,66 @@ static void TransformSSE2(const int16_t* in, uint8_t* dst, int do_two) { // Store the results. if (do_two) { // Store eight bytes/pixels per line. - _mm_storel_epi64((__m128i*)&dst[0 * BPS], dst0); - _mm_storel_epi64((__m128i*)&dst[1 * BPS], dst1); - _mm_storel_epi64((__m128i*)&dst[2 * BPS], dst2); - _mm_storel_epi64((__m128i*)&dst[3 * BPS], dst3); + _mm_storel_epi64((__m128i*)(dst + 0 * BPS), dst0); + _mm_storel_epi64((__m128i*)(dst + 1 * BPS), dst1); + _mm_storel_epi64((__m128i*)(dst + 2 * BPS), dst2); + _mm_storel_epi64((__m128i*)(dst + 3 * BPS), dst3); } else { // Store four bytes/pixels per line. - *((int32_t *)&dst[0 * BPS]) = _mm_cvtsi128_si32(dst0); - *((int32_t *)&dst[1 * BPS]) = _mm_cvtsi128_si32(dst1); - *((int32_t *)&dst[2 * BPS]) = _mm_cvtsi128_si32(dst2); - *((int32_t *)&dst[3 * BPS]) = _mm_cvtsi128_si32(dst3); + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); } } } +#if defined(USE_TRANSFORM_AC3) +#define MUL(a, b) (((a) * (b)) >> 16) +static void TransformAC3(const int16_t* in, uint8_t* dst) { + static const int kC1 = 20091 + (1 << 16); + static const int kC2 = 35468; + const __m128i A = _mm_set1_epi16(in[0] + 4); + const __m128i c4 = _mm_set1_epi16(MUL(in[4], kC2)); + const __m128i d4 = _mm_set1_epi16(MUL(in[4], kC1)); + const int c1 = MUL(in[1], kC2); + const int d1 = MUL(in[1], kC1); + const __m128i CD = _mm_set_epi16(0, 0, 0, 0, -d1, -c1, c1, d1); + const __m128i B = _mm_adds_epi16(A, CD); + const __m128i m0 = _mm_adds_epi16(B, d4); + const __m128i m1 = _mm_adds_epi16(B, c4); + const __m128i m2 = _mm_subs_epi16(B, c4); + const __m128i m3 = _mm_subs_epi16(B, d4); + const __m128i zero = _mm_setzero_si128(); + // Load the source pixels. + __m128i dst0 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 0 * BPS)); + __m128i dst1 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 1 * BPS)); + __m128i dst2 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 2 * BPS)); + __m128i dst3 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 3 * BPS)); + // Convert to 16b. + dst0 = _mm_unpacklo_epi8(dst0, zero); + dst1 = _mm_unpacklo_epi8(dst1, zero); + dst2 = _mm_unpacklo_epi8(dst2, zero); + dst3 = _mm_unpacklo_epi8(dst3, zero); + // Add the inverse transform. + dst0 = _mm_adds_epi16(dst0, _mm_srai_epi16(m0, 3)); + dst1 = _mm_adds_epi16(dst1, _mm_srai_epi16(m1, 3)); + dst2 = _mm_adds_epi16(dst2, _mm_srai_epi16(m2, 3)); + dst3 = _mm_adds_epi16(dst3, _mm_srai_epi16(m3, 3)); + // Unsigned saturate to 8b. + dst0 = _mm_packus_epi16(dst0, dst0); + dst1 = _mm_packus_epi16(dst1, dst1); + dst2 = _mm_packus_epi16(dst2, dst2); + dst3 = _mm_packus_epi16(dst3, dst3); + // Store the results. + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); +} +#undef MUL +#endif // USE_TRANSFORM_AC3 + //------------------------------------------------------------------------------ // Loop Filter (Paragraph 15) @@ -252,20 +247,14 @@ static void TransformSSE2(const int16_t* in, uint8_t* dst, int do_two) { _mm_subs_epu8((q), (p)), \ _mm_subs_epu8((p), (q))) -// Shift each byte of "a" by N bits while preserving by the sign bit. -// -// It first shifts the lower bytes of the words and then the upper bytes and -// then merges the results together. -#define SIGNED_SHIFT_N(a, N) { \ - __m128i t = a; \ - t = _mm_slli_epi16(t, 8); \ - t = _mm_srai_epi16(t, N); \ - t = _mm_srli_epi16(t, 8); \ - \ - a = _mm_srai_epi16(a, N + 8); \ - a = _mm_slli_epi16(a, 8); \ - \ - a = _mm_or_si128(t, a); \ +// Shift each byte of "x" by 3 bits while preserving by the sign bit. +static WEBP_INLINE void SignedShift8b(__m128i* const x) { + const __m128i zero = _mm_setzero_si128(); + const __m128i lo_0 = _mm_unpacklo_epi8(zero, *x); + const __m128i hi_0 = _mm_unpackhi_epi8(zero, *x); + const __m128i lo_1 = _mm_srai_epi16(lo_0, 3 + 8); + const __m128i hi_1 = _mm_srai_epi16(hi_0, 3 + 8); + *x = _mm_packs_epi16(lo_1, hi_1); } #define FLIP_SIGN_BIT2(a, b) { \ @@ -278,103 +267,124 @@ static void TransformSSE2(const int16_t* in, uint8_t* dst, int do_two) { FLIP_SIGN_BIT2(c, d); \ } -#define GET_NOTHEV(p1, p0, q0, q1, hev_thresh, not_hev) { \ - const __m128i zero = _mm_setzero_si128(); \ - const __m128i t_1 = MM_ABS(p1, p0); \ - const __m128i t_2 = MM_ABS(q1, q0); \ - \ - const __m128i h = _mm_set1_epi8(hev_thresh); \ - const __m128i t_3 = _mm_subs_epu8(t_1, h); /* abs(p1 - p0) - hev_tresh */ \ - const __m128i t_4 = _mm_subs_epu8(t_2, h); /* abs(q1 - q0) - hev_tresh */ \ - \ - not_hev = _mm_or_si128(t_3, t_4); \ - not_hev = _mm_cmpeq_epi8(not_hev, zero); /* not_hev <= t1 && not_hev <= t2 */\ +// input/output is uint8_t +static WEBP_INLINE void GetNotHEV(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + int hev_thresh, __m128i* const not_hev) { + const __m128i zero = _mm_setzero_si128(); + const __m128i t_1 = MM_ABS(*p1, *p0); + const __m128i t_2 = MM_ABS(*q1, *q0); + + const __m128i h = _mm_set1_epi8(hev_thresh); + const __m128i t_max = _mm_max_epu8(t_1, t_2); + + const __m128i t_max_h = _mm_subs_epu8(t_max, h); + *not_hev = _mm_cmpeq_epi8(t_max_h, zero); // not_hev <= t1 && not_hev <= t2 } -#define GET_BASE_DELTA(p1, p0, q0, q1, o) { \ - const __m128i qp0 = _mm_subs_epi8(q0, p0); /* q0 - p0 */ \ - o = _mm_subs_epi8(p1, q1); /* p1 - q1 */ \ - o = _mm_adds_epi8(o, qp0); /* p1 - q1 + 1 * (q0 - p0) */ \ - o = _mm_adds_epi8(o, qp0); /* p1 - q1 + 2 * (q0 - p0) */ \ - o = _mm_adds_epi8(o, qp0); /* p1 - q1 + 3 * (q0 - p0) */ \ +// input pixels are int8_t +static WEBP_INLINE void GetBaseDelta(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + __m128i* const delta) { + // beware of addition order, for saturation! + const __m128i p1_q1 = _mm_subs_epi8(*p1, *q1); // p1 - q1 + const __m128i q0_p0 = _mm_subs_epi8(*q0, *p0); // q0 - p0 + const __m128i s1 = _mm_adds_epi8(p1_q1, q0_p0); // p1 - q1 + 1 * (q0 - p0) + const __m128i s2 = _mm_adds_epi8(q0_p0, s1); // p1 - q1 + 2 * (q0 - p0) + const __m128i s3 = _mm_adds_epi8(q0_p0, s2); // p1 - q1 + 3 * (q0 - p0) + *delta = s3; } -#define DO_SIMPLE_FILTER(p0, q0, fl) { \ - const __m128i three = _mm_set1_epi8(3); \ - const __m128i four = _mm_set1_epi8(4); \ - __m128i v3 = _mm_adds_epi8(fl, three); \ - __m128i v4 = _mm_adds_epi8(fl, four); \ - \ - /* Do +4 side */ \ - SIGNED_SHIFT_N(v4, 3); /* v4 >> 3 */ \ - q0 = _mm_subs_epi8(q0, v4); /* q0 -= v4 */ \ - \ - /* Now do +3 side */ \ - SIGNED_SHIFT_N(v3, 3); /* v3 >> 3 */ \ - p0 = _mm_adds_epi8(p0, v3); /* p0 += v3 */ \ +// input and output are int8_t +static WEBP_INLINE void DoSimpleFilter(__m128i* const p0, __m128i* const q0, + const __m128i* const fl) { + const __m128i k3 = _mm_set1_epi8(3); + const __m128i k4 = _mm_set1_epi8(4); + __m128i v3 = _mm_adds_epi8(*fl, k3); + __m128i v4 = _mm_adds_epi8(*fl, k4); + + SignedShift8b(&v4); // v4 >> 3 + SignedShift8b(&v3); // v3 >> 3 + *q0 = _mm_subs_epi8(*q0, v4); // q0 -= v4 + *p0 = _mm_adds_epi8(*p0, v3); // p0 += v3 } // Updates values of 2 pixels at MB edge during complex filtering. // Update operations: // q = q - delta and p = p + delta; where delta = [(a_hi >> 7), (a_lo >> 7)] -#define UPDATE_2PIXELS(pi, qi, a_lo, a_hi) { \ - const __m128i a_lo7 = _mm_srai_epi16(a_lo, 7); \ - const __m128i a_hi7 = _mm_srai_epi16(a_hi, 7); \ - const __m128i delta = _mm_packs_epi16(a_lo7, a_hi7); \ - pi = _mm_adds_epi8(pi, delta); \ - qi = _mm_subs_epi8(qi, delta); \ +// Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip). +static WEBP_INLINE void Update2Pixels(__m128i* const pi, __m128i* const qi, + const __m128i* const a0_lo, + const __m128i* const a0_hi) { + const __m128i a1_lo = _mm_srai_epi16(*a0_lo, 7); + const __m128i a1_hi = _mm_srai_epi16(*a0_hi, 7); + const __m128i delta = _mm_packs_epi16(a1_lo, a1_hi); + const __m128i sign_bit = _mm_set1_epi8(0x80); + *pi = _mm_adds_epi8(*pi, delta); + *qi = _mm_subs_epi8(*qi, delta); + FLIP_SIGN_BIT2(*pi, *qi); } -static void NeedsFilter(const __m128i* p1, const __m128i* p0, const __m128i* q0, - const __m128i* q1, int thresh, __m128i *mask) { - __m128i t1 = MM_ABS(*p1, *q1); // abs(p1 - q1) - *mask = _mm_set1_epi8(0xFE); - t1 = _mm_and_si128(t1, *mask); // set lsb of each byte to zero - t1 = _mm_srli_epi16(t1, 1); // abs(p1 - q1) / 2 +// input pixels are uint8_t +static WEBP_INLINE void NeedsFilter(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + int thresh, __m128i* const mask) { + const __m128i m_thresh = _mm_set1_epi8(thresh); + const __m128i t1 = MM_ABS(*p1, *q1); // abs(p1 - q1) + const __m128i kFE = _mm_set1_epi8(0xFE); + const __m128i t2 = _mm_and_si128(t1, kFE); // set lsb of each byte to zero + const __m128i t3 = _mm_srli_epi16(t2, 1); // abs(p1 - q1) / 2 - *mask = MM_ABS(*p0, *q0); // abs(p0 - q0) - *mask = _mm_adds_epu8(*mask, *mask); // abs(p0 - q0) * 2 - *mask = _mm_adds_epu8(*mask, t1); // abs(p0 - q0) * 2 + abs(p1 - q1) / 2 + const __m128i t4 = MM_ABS(*p0, *q0); // abs(p0 - q0) + const __m128i t5 = _mm_adds_epu8(t4, t4); // abs(p0 - q0) * 2 + const __m128i t6 = _mm_adds_epu8(t5, t3); // abs(p0-q0)*2 + abs(p1-q1)/2 - t1 = _mm_set1_epi8(thresh); - *mask = _mm_subs_epu8(*mask, t1); // mask <= thresh - *mask = _mm_cmpeq_epi8(*mask, _mm_setzero_si128()); + const __m128i t7 = _mm_subs_epu8(t6, m_thresh); // mask <= m_thresh + *mask = _mm_cmpeq_epi8(t7, _mm_setzero_si128()); } //------------------------------------------------------------------------------ // Edge filtering functions // Applies filter on 2 pixels (p0 and q0) -static WEBP_INLINE void DoFilter2(const __m128i* p1, __m128i* p0, __m128i* q0, - const __m128i* q1, int thresh) { +static WEBP_INLINE void DoFilter2(__m128i* const p1, __m128i* const p0, + __m128i* const q0, __m128i* const q1, + int thresh) { __m128i a, mask; const __m128i sign_bit = _mm_set1_epi8(0x80); + // convert p1/q1 to int8_t (for GetBaseDelta) const __m128i p1s = _mm_xor_si128(*p1, sign_bit); const __m128i q1s = _mm_xor_si128(*q1, sign_bit); NeedsFilter(p1, p0, q0, q1, thresh, &mask); - // convert to signed values FLIP_SIGN_BIT2(*p0, *q0); - - GET_BASE_DELTA(p1s, *p0, *q0, q1s, a); + GetBaseDelta(&p1s, p0, q0, &q1s, &a); a = _mm_and_si128(a, mask); // mask filter values we don't care about - DO_SIMPLE_FILTER(*p0, *q0, a); - - // unoffset + DoSimpleFilter(p0, q0, &a); FLIP_SIGN_BIT2(*p0, *q0); } // Applies filter on 4 pixels (p1, p0, q0 and q1) -static WEBP_INLINE void DoFilter4(__m128i* p1, __m128i *p0, - __m128i* q0, __m128i* q1, - const __m128i* mask, int hev_thresh) { +static WEBP_INLINE void DoFilter4(__m128i* const p1, __m128i* const p0, + __m128i* const q0, __m128i* const q1, + const __m128i* const mask, int hev_thresh) { + const __m128i zero = _mm_setzero_si128(); + const __m128i sign_bit = _mm_set1_epi8(0x80); + const __m128i k64 = _mm_set1_epi8(64); + const __m128i k3 = _mm_set1_epi8(3); + const __m128i k4 = _mm_set1_epi8(4); __m128i not_hev; __m128i t1, t2, t3; - const __m128i sign_bit = _mm_set1_epi8(0x80); // compute hev mask - GET_NOTHEV(*p1, *p0, *q0, *q1, hev_thresh, not_hev); + GetNotHEV(p1, p0, q0, q1, hev_thresh, ¬_hev); // convert to signed values FLIP_SIGN_BIT4(*p1, *p0, *q0, *q1); @@ -387,135 +397,108 @@ static WEBP_INLINE void DoFilter4(__m128i* p1, __m128i *p0, t1 = _mm_adds_epi8(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0) t1 = _mm_and_si128(t1, *mask); // mask filter values we don't care about - // Do +4 side - t2 = _mm_set1_epi8(4); - t2 = _mm_adds_epi8(t1, t2); // 3 * (q0 - p0) + (p1 - q1) + 4 - SIGNED_SHIFT_N(t2, 3); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 - t3 = t2; // save t2 - *q0 = _mm_subs_epi8(*q0, t2); // q0 -= t2 - - // Now do +3 side - t2 = _mm_set1_epi8(3); - t2 = _mm_adds_epi8(t1, t2); // +3 instead of +4 - SIGNED_SHIFT_N(t2, 3); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 + t2 = _mm_adds_epi8(t1, k3); // 3 * (q0 - p0) + hev(p1 - q1) + 3 + t3 = _mm_adds_epi8(t1, k4); // 3 * (q0 - p0) + hev(p1 - q1) + 4 + SignedShift8b(&t2); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 + SignedShift8b(&t3); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 *p0 = _mm_adds_epi8(*p0, t2); // p0 += t2 + *q0 = _mm_subs_epi8(*q0, t3); // q0 -= t3 + FLIP_SIGN_BIT2(*p0, *q0); - t2 = _mm_set1_epi8(1); - t3 = _mm_adds_epi8(t3, t2); - SIGNED_SHIFT_N(t3, 1); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 4 + // this is equivalent to signed (a + 1) >> 1 calculation + t2 = _mm_add_epi8(t3, sign_bit); + t3 = _mm_avg_epu8(t2, zero); + t3 = _mm_sub_epi8(t3, k64); t3 = _mm_and_si128(not_hev, t3); // if !hev *q1 = _mm_subs_epi8(*q1, t3); // q1 -= t3 *p1 = _mm_adds_epi8(*p1, t3); // p1 += t3 - - // unoffset - FLIP_SIGN_BIT4(*p1, *p0, *q0, *q1); + FLIP_SIGN_BIT2(*p1, *q1); } // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) -static WEBP_INLINE void DoFilter6(__m128i *p2, __m128i* p1, __m128i *p0, - __m128i* q0, __m128i* q1, __m128i *q2, - const __m128i* mask, int hev_thresh) { - __m128i a, not_hev; +static WEBP_INLINE void DoFilter6(__m128i* const p2, __m128i* const p1, + __m128i* const p0, __m128i* const q0, + __m128i* const q1, __m128i* const q2, + const __m128i* const mask, int hev_thresh) { + const __m128i zero = _mm_setzero_si128(); const __m128i sign_bit = _mm_set1_epi8(0x80); + __m128i a, not_hev; // compute hev mask - GET_NOTHEV(*p1, *p0, *q0, *q1, hev_thresh, not_hev); + GetNotHEV(p1, p0, q0, q1, hev_thresh, ¬_hev); - // convert to signed values FLIP_SIGN_BIT4(*p1, *p0, *q0, *q1); FLIP_SIGN_BIT2(*p2, *q2); - - GET_BASE_DELTA(*p1, *p0, *q0, *q1, a); + GetBaseDelta(p1, p0, q0, q1, &a); { // do simple filter on pixels with hev const __m128i m = _mm_andnot_si128(not_hev, *mask); const __m128i f = _mm_and_si128(a, m); - DO_SIMPLE_FILTER(*p0, *q0, f); + DoSimpleFilter(p0, q0, &f); } + { // do strong filter on pixels with not hev - const __m128i zero = _mm_setzero_si128(); - const __m128i nine = _mm_set1_epi16(0x0900); - const __m128i sixty_three = _mm_set1_epi16(63); + const __m128i k9 = _mm_set1_epi16(0x0900); + const __m128i k63 = _mm_set1_epi16(63); const __m128i m = _mm_and_si128(not_hev, *mask); const __m128i f = _mm_and_si128(a, m); + const __m128i f_lo = _mm_unpacklo_epi8(zero, f); const __m128i f_hi = _mm_unpackhi_epi8(zero, f); - const __m128i f9_lo = _mm_mulhi_epi16(f_lo, nine); // Filter (lo) * 9 - const __m128i f9_hi = _mm_mulhi_epi16(f_hi, nine); // Filter (hi) * 9 - const __m128i f18_lo = _mm_add_epi16(f9_lo, f9_lo); // Filter (lo) * 18 - const __m128i f18_hi = _mm_add_epi16(f9_hi, f9_hi); // Filter (hi) * 18 + const __m128i f9_lo = _mm_mulhi_epi16(f_lo, k9); // Filter (lo) * 9 + const __m128i f9_hi = _mm_mulhi_epi16(f_hi, k9); // Filter (hi) * 9 - const __m128i a2_lo = _mm_add_epi16(f9_lo, sixty_three); // Filter * 9 + 63 - const __m128i a2_hi = _mm_add_epi16(f9_hi, sixty_three); // Filter * 9 + 63 + const __m128i a2_lo = _mm_add_epi16(f9_lo, k63); // Filter * 9 + 63 + const __m128i a2_hi = _mm_add_epi16(f9_hi, k63); // Filter * 9 + 63 - const __m128i a1_lo = _mm_add_epi16(f18_lo, sixty_three); // F... * 18 + 63 - const __m128i a1_hi = _mm_add_epi16(f18_hi, sixty_three); // F... * 18 + 63 + const __m128i a1_lo = _mm_add_epi16(a2_lo, f9_lo); // Filter * 18 + 63 + const __m128i a1_hi = _mm_add_epi16(a2_hi, f9_hi); // Filter * 18 + 63 - const __m128i a0_lo = _mm_add_epi16(f18_lo, a2_lo); // Filter * 27 + 63 - const __m128i a0_hi = _mm_add_epi16(f18_hi, a2_hi); // Filter * 27 + 63 + const __m128i a0_lo = _mm_add_epi16(a1_lo, f9_lo); // Filter * 27 + 63 + const __m128i a0_hi = _mm_add_epi16(a1_hi, f9_hi); // Filter * 27 + 63 - UPDATE_2PIXELS(*p2, *q2, a2_lo, a2_hi); - UPDATE_2PIXELS(*p1, *q1, a1_lo, a1_hi); - UPDATE_2PIXELS(*p0, *q0, a0_lo, a0_hi); + Update2Pixels(p2, q2, &a2_lo, &a2_hi); + Update2Pixels(p1, q1, &a1_lo, &a1_hi); + Update2Pixels(p0, q0, &a0_lo, &a0_hi); } - - // unoffset - FLIP_SIGN_BIT4(*p1, *p0, *q0, *q1); - FLIP_SIGN_BIT2(*p2, *q2); } // reads 8 rows across a vertical edge. -// -// TODO(somnath): Investigate _mm_shuffle* also see if it can be broken into -// two Load4x4() to avoid code duplication. -static WEBP_INLINE void Load8x4(const uint8_t* b, int stride, - __m128i* p, __m128i* q) { - __m128i t1, t2; +static WEBP_INLINE void Load8x4(const uint8_t* const b, int stride, + __m128i* const p, __m128i* const q) { + // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 + // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 + const __m128i A0 = _mm_set_epi32( + WebPMemToUint32(&b[6 * stride]), WebPMemToUint32(&b[2 * stride]), + WebPMemToUint32(&b[4 * stride]), WebPMemToUint32(&b[0 * stride])); + const __m128i A1 = _mm_set_epi32( + WebPMemToUint32(&b[7 * stride]), WebPMemToUint32(&b[3 * stride]), + WebPMemToUint32(&b[5 * stride]), WebPMemToUint32(&b[1 * stride])); - // Load 0th, 1st, 4th and 5th rows - __m128i r0 = _mm_cvtsi32_si128(*((int*)&b[0 * stride])); // 03 02 01 00 - __m128i r1 = _mm_cvtsi32_si128(*((int*)&b[1 * stride])); // 13 12 11 10 - __m128i r4 = _mm_cvtsi32_si128(*((int*)&b[4 * stride])); // 43 42 41 40 - __m128i r5 = _mm_cvtsi32_si128(*((int*)&b[5 * stride])); // 53 52 51 50 + // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 + // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 + const __m128i B0 = _mm_unpacklo_epi8(A0, A1); + const __m128i B1 = _mm_unpackhi_epi8(A0, A1); - r0 = _mm_unpacklo_epi32(r0, r4); // 43 42 41 40 03 02 01 00 - r1 = _mm_unpacklo_epi32(r1, r5); // 53 52 51 50 13 12 11 10 - - // t1 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 - t1 = _mm_unpacklo_epi8(r0, r1); - - // Load 2nd, 3rd, 6th and 7th rows - r0 = _mm_cvtsi32_si128(*((int*)&b[2 * stride])); // 23 22 21 22 - r1 = _mm_cvtsi32_si128(*((int*)&b[3 * stride])); // 33 32 31 30 - r4 = _mm_cvtsi32_si128(*((int*)&b[6 * stride])); // 63 62 61 60 - r5 = _mm_cvtsi32_si128(*((int*)&b[7 * stride])); // 73 72 71 70 - - r0 = _mm_unpacklo_epi32(r0, r4); // 63 62 61 60 23 22 21 20 - r1 = _mm_unpacklo_epi32(r1, r5); // 73 72 71 70 33 32 31 30 - - // t2 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 - t2 = _mm_unpacklo_epi8(r0, r1); - - // t1 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 - // t2 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 - r0 = t1; - t1 = _mm_unpacklo_epi16(t1, t2); - t2 = _mm_unpackhi_epi16(r0, t2); + // C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 + // C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 + const __m128i C0 = _mm_unpacklo_epi16(B0, B1); + const __m128i C1 = _mm_unpackhi_epi16(B0, B1); // *p = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 // *q = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 - *p = _mm_unpacklo_epi32(t1, t2); - *q = _mm_unpackhi_epi32(t1, t2); + *p = _mm_unpacklo_epi32(C0, C1); + *q = _mm_unpackhi_epi32(C0, C1); } -static WEBP_INLINE void Load16x4(const uint8_t* r0, const uint8_t* r8, +static WEBP_INLINE void Load16x4(const uint8_t* const r0, + const uint8_t* const r8, int stride, - __m128i* p1, __m128i* p0, - __m128i* q0, __m128i* q1) { - __m128i t1, t2; + __m128i* const p1, __m128i* const p0, + __m128i* const q0, __m128i* const q1) { // Assume the pixels around the edge (|) are numbered as follows // 00 01 | 02 03 // 10 11 | 12 13 @@ -534,69 +517,74 @@ static WEBP_INLINE void Load16x4(const uint8_t* r0, const uint8_t* r8, Load8x4(r0, stride, p1, q0); Load8x4(r8, stride, p0, q1); - t1 = *p1; - t2 = *q0; - // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 - // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 - // q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02 - // q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03 - *p1 = _mm_unpacklo_epi64(t1, *p0); - *p0 = _mm_unpackhi_epi64(t1, *p0); - *q0 = _mm_unpacklo_epi64(t2, *q1); - *q1 = _mm_unpackhi_epi64(t2, *q1); + { + // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 + // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 + // q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02 + // q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03 + const __m128i t1 = *p1; + const __m128i t2 = *q0; + *p1 = _mm_unpacklo_epi64(t1, *p0); + *p0 = _mm_unpackhi_epi64(t1, *p0); + *q0 = _mm_unpacklo_epi64(t2, *q1); + *q1 = _mm_unpackhi_epi64(t2, *q1); + } } -static WEBP_INLINE void Store4x4(__m128i* x, uint8_t* dst, int stride) { +static WEBP_INLINE void Store4x4(__m128i* const x, uint8_t* dst, int stride) { int i; for (i = 0; i < 4; ++i, dst += stride) { - *((int32_t*)dst) = _mm_cvtsi128_si32(*x); + WebPUint32ToMem(dst, _mm_cvtsi128_si32(*x)); *x = _mm_srli_si128(*x, 4); } } // Transpose back and store -static WEBP_INLINE void Store16x4(uint8_t* r0, uint8_t* r8, int stride, - __m128i* p1, __m128i* p0, - __m128i* q0, __m128i* q1) { - __m128i t1; +static WEBP_INLINE void Store16x4(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + uint8_t* r0, uint8_t* r8, + int stride) { + __m128i t1, p1_s, p0_s, q0_s, q1_s; // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 t1 = *p0; - *p0 = _mm_unpacklo_epi8(*p1, t1); - *p1 = _mm_unpackhi_epi8(*p1, t1); + p0_s = _mm_unpacklo_epi8(*p1, t1); + p1_s = _mm_unpackhi_epi8(*p1, t1); // q0 = 73 72 63 62 53 52 43 42 33 32 23 22 13 12 03 02 // q1 = f3 f2 e3 e2 d3 d2 c3 c2 b3 b2 a3 a2 93 92 83 82 t1 = *q0; - *q0 = _mm_unpacklo_epi8(t1, *q1); - *q1 = _mm_unpackhi_epi8(t1, *q1); + q0_s = _mm_unpacklo_epi8(t1, *q1); + q1_s = _mm_unpackhi_epi8(t1, *q1); // p0 = 33 32 31 30 23 22 21 20 13 12 11 10 03 02 01 00 // q0 = 73 72 71 70 63 62 61 60 53 52 51 50 43 42 41 40 - t1 = *p0; - *p0 = _mm_unpacklo_epi16(t1, *q0); - *q0 = _mm_unpackhi_epi16(t1, *q0); + t1 = p0_s; + p0_s = _mm_unpacklo_epi16(t1, q0_s); + q0_s = _mm_unpackhi_epi16(t1, q0_s); // p1 = b3 b2 b1 b0 a3 a2 a1 a0 93 92 91 90 83 82 81 80 // q1 = f3 f2 f1 f0 e3 e2 e1 e0 d3 d2 d1 d0 c3 c2 c1 c0 - t1 = *p1; - *p1 = _mm_unpacklo_epi16(t1, *q1); - *q1 = _mm_unpackhi_epi16(t1, *q1); + t1 = p1_s; + p1_s = _mm_unpacklo_epi16(t1, q1_s); + q1_s = _mm_unpackhi_epi16(t1, q1_s); - Store4x4(p0, r0, stride); + Store4x4(&p0_s, r0, stride); r0 += 4 * stride; - Store4x4(q0, r0, stride); + Store4x4(&q0_s, r0, stride); - Store4x4(p1, r8, stride); + Store4x4(&p1_s, r8, stride); r8 += 4 * stride; - Store4x4(q1, r8, stride); + Store4x4(&q1_s, r8, stride); } //------------------------------------------------------------------------------ // Simple In-loop filtering (Paragraph 15.2) -static void SimpleVFilter16SSE2(uint8_t* p, int stride, int thresh) { +static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { // Load __m128i p1 = _mm_loadu_si128((__m128i*)&p[-2 * stride]); __m128i p0 = _mm_loadu_si128((__m128i*)&p[-stride]); @@ -607,49 +595,49 @@ static void SimpleVFilter16SSE2(uint8_t* p, int stride, int thresh) { // Store _mm_storeu_si128((__m128i*)&p[-stride], p0); - _mm_storeu_si128((__m128i*)p, q0); + _mm_storeu_si128((__m128i*)&p[0], q0); } -static void SimpleHFilter16SSE2(uint8_t* p, int stride, int thresh) { +static void SimpleHFilter16(uint8_t* p, int stride, int thresh) { __m128i p1, p0, q0, q1; p -= 2; // beginning of p1 - Load16x4(p, p + 8 * stride, stride, &p1, &p0, &q0, &q1); + Load16x4(p, p + 8 * stride, stride, &p1, &p0, &q0, &q1); DoFilter2(&p1, &p0, &q0, &q1, thresh); - Store16x4(p, p + 8 * stride, stride, &p1, &p0, &q0, &q1); + Store16x4(&p1, &p0, &q0, &q1, p, p + 8 * stride, stride); } -static void SimpleVFilter16iSSE2(uint8_t* p, int stride, int thresh) { +static void SimpleVFilter16i(uint8_t* p, int stride, int thresh) { int k; for (k = 3; k > 0; --k) { p += 4 * stride; - SimpleVFilter16SSE2(p, stride, thresh); + SimpleVFilter16(p, stride, thresh); } } -static void SimpleHFilter16iSSE2(uint8_t* p, int stride, int thresh) { +static void SimpleHFilter16i(uint8_t* p, int stride, int thresh) { int k; for (k = 3; k > 0; --k) { p += 4; - SimpleHFilter16SSE2(p, stride, thresh); + SimpleHFilter16(p, stride, thresh); } } //------------------------------------------------------------------------------ // Complex In-loop filtering (Paragraph 15.3) -#define MAX_DIFF1(p3, p2, p1, p0, m) { \ - m = MM_ABS(p3, p2); \ - m = _mm_max_epu8(m, MM_ABS(p2, p1)); \ - m = _mm_max_epu8(m, MM_ABS(p1, p0)); \ -} - -#define MAX_DIFF2(p3, p2, p1, p0, m) { \ +#define MAX_DIFF1(p3, p2, p1, p0, m) do { \ + m = MM_ABS(p1, p0); \ m = _mm_max_epu8(m, MM_ABS(p3, p2)); \ m = _mm_max_epu8(m, MM_ABS(p2, p1)); \ +} while (0) + +#define MAX_DIFF2(p3, p2, p1, p0, m) do { \ m = _mm_max_epu8(m, MM_ABS(p1, p0)); \ -} + m = _mm_max_epu8(m, MM_ABS(p3, p2)); \ + m = _mm_max_epu8(m, MM_ABS(p2, p1)); \ +} while (0) #define LOAD_H_EDGES4(p, stride, e1, e2, e3, e4) { \ e1 = _mm_loadu_si128((__m128i*)&(p)[0 * stride]); \ @@ -658,10 +646,11 @@ static void SimpleHFilter16iSSE2(uint8_t* p, int stride, int thresh) { e4 = _mm_loadu_si128((__m128i*)&(p)[3 * stride]); \ } -#define LOADUV_H_EDGE(p, u, v, stride) { \ - p = _mm_loadl_epi64((__m128i*)&(u)[(stride)]); \ - p = _mm_unpacklo_epi64(p, _mm_loadl_epi64((__m128i*)&(v)[(stride)])); \ -} +#define LOADUV_H_EDGE(p, u, v, stride) do { \ + const __m128i U = _mm_loadl_epi64((__m128i*)&(u)[(stride)]); \ + const __m128i V = _mm_loadl_epi64((__m128i*)&(v)[(stride)]); \ + p = _mm_unpacklo_epi64(U, V); \ +} while (0) #define LOADUV_H_EDGES4(u, v, stride, e1, e2, e3, e4) { \ LOADUV_H_EDGE(e1, u, v, 0 * stride); \ @@ -676,18 +665,23 @@ static void SimpleHFilter16iSSE2(uint8_t* p, int stride, int thresh) { _mm_storel_epi64((__m128i*)&v[(stride)], p); \ } -#define COMPLEX_FL_MASK(p1, p0, q0, q1, thresh, ithresh, mask) { \ - __m128i fl_yes; \ - const __m128i it = _mm_set1_epi8(ithresh); \ - mask = _mm_subs_epu8(mask, it); \ - mask = _mm_cmpeq_epi8(mask, _mm_setzero_si128()); \ - NeedsFilter(&p1, &p0, &q0, &q1, thresh, &fl_yes); \ - mask = _mm_and_si128(mask, fl_yes); \ +static WEBP_INLINE void ComplexMask(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + int thresh, int ithresh, + __m128i* const mask) { + const __m128i it = _mm_set1_epi8(ithresh); + const __m128i diff = _mm_subs_epu8(*mask, it); + const __m128i thresh_mask = _mm_cmpeq_epi8(diff, _mm_setzero_si128()); + __m128i filter_mask; + NeedsFilter(p1, p0, q0, q1, thresh, &filter_mask); + *mask = _mm_and_si128(thresh_mask, filter_mask); } // on macroblock edges -static void VFilter16SSE2(uint8_t* p, int stride, - int thresh, int ithresh, int hev_thresh) { +static void VFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { __m128i t1; __m128i mask; __m128i p2, p1, p0, q0, q1, q2; @@ -700,20 +694,20 @@ static void VFilter16SSE2(uint8_t* p, int stride, LOAD_H_EDGES4(p, stride, q0, q1, q2, t1); MAX_DIFF2(t1, q2, q1, q0, mask); - COMPLEX_FL_MASK(p1, p0, q0, q1, thresh, ithresh, mask); + ComplexMask(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); DoFilter6(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); // Store _mm_storeu_si128((__m128i*)&p[-3 * stride], p2); _mm_storeu_si128((__m128i*)&p[-2 * stride], p1); _mm_storeu_si128((__m128i*)&p[-1 * stride], p0); - _mm_storeu_si128((__m128i*)&p[0 * stride], q0); - _mm_storeu_si128((__m128i*)&p[1 * stride], q1); - _mm_storeu_si128((__m128i*)&p[2 * stride], q2); + _mm_storeu_si128((__m128i*)&p[+0 * stride], q0); + _mm_storeu_si128((__m128i*)&p[+1 * stride], q1); + _mm_storeu_si128((__m128i*)&p[+2 * stride], q2); } -static void HFilter16SSE2(uint8_t* p, int stride, - int thresh, int ithresh, int hev_thresh) { +static void HFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { __m128i mask; __m128i p3, p2, p1, p0, q0, q1, q2, q3; @@ -724,71 +718,78 @@ static void HFilter16SSE2(uint8_t* p, int stride, Load16x4(p, p + 8 * stride, stride, &q0, &q1, &q2, &q3); // q0, q1, q2, q3 MAX_DIFF2(q3, q2, q1, q0, mask); - COMPLEX_FL_MASK(p1, p0, q0, q1, thresh, ithresh, mask); + ComplexMask(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); DoFilter6(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); - Store16x4(b, b + 8 * stride, stride, &p3, &p2, &p1, &p0); - Store16x4(p, p + 8 * stride, stride, &q0, &q1, &q2, &q3); + Store16x4(&p3, &p2, &p1, &p0, b, b + 8 * stride, stride); + Store16x4(&q0, &q1, &q2, &q3, p, p + 8 * stride, stride); } // on three inner edges -static void VFilter16iSSE2(uint8_t* p, int stride, - int thresh, int ithresh, int hev_thresh) { +static void VFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { int k; - __m128i mask; - __m128i t1, t2, p1, p0, q0, q1; + __m128i p3, p2, p1, p0; // loop invariants + + LOAD_H_EDGES4(p, stride, p3, p2, p1, p0); // prologue for (k = 3; k > 0; --k) { - // Load p3, p2, p1, p0 - LOAD_H_EDGES4(p, stride, t2, t1, p1, p0); - MAX_DIFF1(t2, t1, p1, p0, mask); - + __m128i mask, tmp1, tmp2; + uint8_t* const b = p + 2 * stride; // beginning of p1 p += 4 * stride; - // Load q0, q1, q2, q3 - LOAD_H_EDGES4(p, stride, q0, q1, t1, t2); - MAX_DIFF2(t2, t1, q1, q0, mask); + MAX_DIFF1(p3, p2, p1, p0, mask); // compute partial mask + LOAD_H_EDGES4(p, stride, p3, p2, tmp1, tmp2); + MAX_DIFF2(p3, p2, tmp1, tmp2, mask); - COMPLEX_FL_MASK(p1, p0, q0, q1, thresh, ithresh, mask); - DoFilter4(&p1, &p0, &q0, &q1, &mask, hev_thresh); + // p3 and p2 are not just temporary variables here: they will be + // re-used for next span. And q2/q3 will become p1/p0 accordingly. + ComplexMask(&p1, &p0, &p3, &p2, thresh, ithresh, &mask); + DoFilter4(&p1, &p0, &p3, &p2, &mask, hev_thresh); // Store - _mm_storeu_si128((__m128i*)&p[-2 * stride], p1); - _mm_storeu_si128((__m128i*)&p[-1 * stride], p0); - _mm_storeu_si128((__m128i*)&p[0 * stride], q0); - _mm_storeu_si128((__m128i*)&p[1 * stride], q1); + _mm_storeu_si128((__m128i*)&b[0 * stride], p1); + _mm_storeu_si128((__m128i*)&b[1 * stride], p0); + _mm_storeu_si128((__m128i*)&b[2 * stride], p3); + _mm_storeu_si128((__m128i*)&b[3 * stride], p2); + + // rotate samples + p1 = tmp1; + p0 = tmp2; } } -static void HFilter16iSSE2(uint8_t* p, int stride, - int thresh, int ithresh, int hev_thresh) { +static void HFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { int k; - uint8_t* b; - __m128i mask; - __m128i t1, t2, p1, p0, q0, q1; + __m128i p3, p2, p1, p0; // loop invariants + + Load16x4(p, p + 8 * stride, stride, &p3, &p2, &p1, &p0); // prologue for (k = 3; k > 0; --k) { - b = p; - Load16x4(b, b + 8 * stride, stride, &t2, &t1, &p1, &p0); // p3, p2, p1, p0 - MAX_DIFF1(t2, t1, p1, p0, mask); + __m128i mask, tmp1, tmp2; + uint8_t* const b = p + 2; // beginning of p1 - b += 4; // beginning of q0 - Load16x4(b, b + 8 * stride, stride, &q0, &q1, &t1, &t2); // q0, q1, q2, q3 - MAX_DIFF2(t2, t1, q1, q0, mask); + p += 4; // beginning of q0 (and next span) - COMPLEX_FL_MASK(p1, p0, q0, q1, thresh, ithresh, mask); - DoFilter4(&p1, &p0, &q0, &q1, &mask, hev_thresh); + MAX_DIFF1(p3, p2, p1, p0, mask); // compute partial mask + Load16x4(p, p + 8 * stride, stride, &p3, &p2, &tmp1, &tmp2); + MAX_DIFF2(p3, p2, tmp1, tmp2, mask); - b -= 2; // beginning of p1 - Store16x4(b, b + 8 * stride, stride, &p1, &p0, &q0, &q1); + ComplexMask(&p1, &p0, &p3, &p2, thresh, ithresh, &mask); + DoFilter4(&p1, &p0, &p3, &p2, &mask, hev_thresh); - p += 4; + Store16x4(&p1, &p0, &p3, &p2, b, b + 8 * stride, stride); + + // rotate samples + p1 = tmp1; + p0 = tmp2; } } // 8-pixels wide variant, for chroma filtering -static void VFilter8SSE2(uint8_t* u, uint8_t* v, int stride, - int thresh, int ithresh, int hev_thresh) { +static void VFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { __m128i mask; __m128i t1, p2, p1, p0, q0, q1, q2; @@ -800,7 +801,7 @@ static void VFilter8SSE2(uint8_t* u, uint8_t* v, int stride, LOADUV_H_EDGES4(u, v, stride, q0, q1, q2, t1); MAX_DIFF2(t1, q2, q1, q0, mask); - COMPLEX_FL_MASK(p1, p0, q0, q1, thresh, ithresh, mask); + ComplexMask(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); DoFilter6(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); // Store @@ -812,8 +813,8 @@ static void VFilter8SSE2(uint8_t* u, uint8_t* v, int stride, STOREUV(q2, u, v, 2 * stride); } -static void HFilter8SSE2(uint8_t* u, uint8_t* v, int stride, - int thresh, int ithresh, int hev_thresh) { +static void HFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { __m128i mask; __m128i p3, p2, p1, p0, q0, q1, q2, q3; @@ -825,15 +826,15 @@ static void HFilter8SSE2(uint8_t* u, uint8_t* v, int stride, Load16x4(u, v, stride, &q0, &q1, &q2, &q3); // q0, q1, q2, q3 MAX_DIFF2(q3, q2, q1, q0, mask); - COMPLEX_FL_MASK(p1, p0, q0, q1, thresh, ithresh, mask); + ComplexMask(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); DoFilter6(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); - Store16x4(tu, tv, stride, &p3, &p2, &p1, &p0); - Store16x4(u, v, stride, &q0, &q1, &q2, &q3); + Store16x4(&p3, &p2, &p1, &p0, tu, tv, stride); + Store16x4(&q0, &q1, &q2, &q3, u, v, stride); } -static void VFilter8iSSE2(uint8_t* u, uint8_t* v, int stride, - int thresh, int ithresh, int hev_thresh) { +static void VFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { __m128i mask; __m128i t1, t2, p1, p0, q0, q1; @@ -848,7 +849,7 @@ static void VFilter8iSSE2(uint8_t* u, uint8_t* v, int stride, LOADUV_H_EDGES4(u, v, stride, q0, q1, t1, t2); MAX_DIFF2(t2, t1, q1, q0, mask); - COMPLEX_FL_MASK(p1, p0, q0, q1, thresh, ithresh, mask); + ComplexMask(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); DoFilter4(&p1, &p0, &q0, &q1, &mask, hev_thresh); // Store @@ -858,8 +859,8 @@ static void VFilter8iSSE2(uint8_t* u, uint8_t* v, int stride, STOREUV(q1, u, v, 1 * stride); } -static void HFilter8iSSE2(uint8_t* u, uint8_t* v, int stride, - int thresh, int ithresh, int hev_thresh) { +static void HFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { __m128i mask; __m128i t1, t2, p1, p0, q0, q1; Load16x4(u, v, stride, &t2, &t1, &p1, &p0); // p3, p2, p1, p0 @@ -870,41 +871,361 @@ static void HFilter8iSSE2(uint8_t* u, uint8_t* v, int stride, Load16x4(u, v, stride, &q0, &q1, &t1, &t2); // q0, q1, q2, q3 MAX_DIFF2(t2, t1, q1, q0, mask); - COMPLEX_FL_MASK(p1, p0, q0, q1, thresh, ithresh, mask); + ComplexMask(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); DoFilter4(&p1, &p0, &q0, &q1, &mask, hev_thresh); u -= 2; // beginning of p1 v -= 2; - Store16x4(u, v, stride, &p1, &p0, &q0, &q1); + Store16x4(&p1, &p0, &q0, &q1, u, v, stride); } -#endif // WEBP_USE_SSE2 +//------------------------------------------------------------------------------ +// 4x4 predictions + +#define DST(x, y) dst[(x) + (y) * BPS] +#define AVG3(a, b, c) (((a) + 2 * (b) + (c) + 2) >> 2) + +// We use the following 8b-arithmetic tricks: +// (a + 2 * b + c + 2) >> 2 = (AC + b + 1) >> 1 +// where: AC = (a + c) >> 1 = [(a + c + 1) >> 1] - [(a^c) & 1] +// and: +// (a + 2 * b + c + 2) >> 2 = (AB + BC + 1) >> 1 - (ab|bc)&lsb +// where: AC = (a + b + 1) >> 1, BC = (b + c + 1) >> 1 +// and ab = a ^ b, bc = b ^ c, lsb = (AC^BC)&1 + +static void VE4(uint8_t* dst) { // vertical + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(dst - BPS - 1)); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i a = _mm_avg_epu8(ABCDEFGH, CDEFGH00); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGH00), one); + const __m128i b = _mm_subs_epu8(a, lsb); + const __m128i avg = _mm_avg_epu8(b, BCDEFGH0); + const uint32_t vals = _mm_cvtsi128_si32(avg); + int i; + for (i = 0; i < 4; ++i) { + WebPUint32ToMem(dst + i * BPS, vals); + } +} + +static void LD4(uint8_t* dst) { // Down-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(dst - BPS)); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i CDEFGHH0 = _mm_insert_epi16(CDEFGH00, dst[-BPS + 7], 3); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, CDEFGHH0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGHH0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, BCDEFGH0); + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +static void VR4(uint8_t* dst) { // Vertical-Right + const __m128i one = _mm_set1_epi8(1); + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int X = dst[-1 - BPS]; + const __m128i XABCD = _mm_loadl_epi64((__m128i*)(dst - BPS - 1)); + const __m128i ABCD0 = _mm_srli_si128(XABCD, 1); + const __m128i abcd = _mm_avg_epu8(XABCD, ABCD0); + const __m128i _XABCD = _mm_slli_si128(XABCD, 1); + const __m128i IXABCD = _mm_insert_epi16(_XABCD, I | (X << 8), 0); + const __m128i avg1 = _mm_avg_epu8(IXABCD, ABCD0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(IXABCD, ABCD0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i efgh = _mm_avg_epu8(avg2, XABCD); + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcd )); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( efgh )); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(abcd, 1))); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(efgh, 1))); + + // these two are hard to implement in SSE2, so we keep the C-version: + DST(0, 2) = AVG3(J, I, X); + DST(0, 3) = AVG3(K, J, I); +} + +static void VL4(uint8_t* dst) { // Vertical-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(dst - BPS)); + const __m128i BCDEFGH_ = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH__ = _mm_srli_si128(ABCDEFGH, 2); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, BCDEFGH_); + const __m128i avg2 = _mm_avg_epu8(CDEFGH__, BCDEFGH_); + const __m128i avg3 = _mm_avg_epu8(avg1, avg2); + const __m128i lsb1 = _mm_and_si128(_mm_xor_si128(avg1, avg2), one); + const __m128i ab = _mm_xor_si128(ABCDEFGH, BCDEFGH_); + const __m128i bc = _mm_xor_si128(CDEFGH__, BCDEFGH_); + const __m128i abbc = _mm_or_si128(ab, bc); + const __m128i lsb2 = _mm_and_si128(abbc, lsb1); + const __m128i avg4 = _mm_subs_epu8(avg3, lsb2); + const uint32_t extra_out = _mm_cvtsi128_si32(_mm_srli_si128(avg4, 4)); + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( avg1 )); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( avg4 )); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg1, 1))); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg4, 1))); + + // these two are hard to get and irregular + DST(3, 2) = (extra_out >> 0) & 0xff; + DST(3, 3) = (extra_out >> 8) & 0xff; +} + +static void RD4(uint8_t* dst) { // Down-right + const __m128i one = _mm_set1_epi8(1); + const __m128i XABCD = _mm_loadl_epi64((__m128i*)(dst - BPS - 1)); + const __m128i ____XABCD = _mm_slli_si128(XABCD, 4); + const uint32_t I = dst[-1 + 0 * BPS]; + const uint32_t J = dst[-1 + 1 * BPS]; + const uint32_t K = dst[-1 + 2 * BPS]; + const uint32_t L = dst[-1 + 3 * BPS]; + const __m128i LKJI_____ = + _mm_cvtsi32_si128(L | (K << 8) | (J << 16) | (I << 24)); + const __m128i LKJIXABCD = _mm_or_si128(LKJI_____, ____XABCD); + const __m128i KJIXABCD_ = _mm_srli_si128(LKJIXABCD, 1); + const __m128i JIXABCD__ = _mm_srli_si128(LKJIXABCD, 2); + const __m128i avg1 = _mm_avg_epu8(JIXABCD__, LKJIXABCD); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(JIXABCD__, LKJIXABCD), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, KJIXABCD_); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +#undef DST +#undef AVG3 + +//------------------------------------------------------------------------------ +// Luma 16x16 + +static WEBP_INLINE void TrueMotion(uint8_t* dst, int size) { + const uint8_t* top = dst - BPS; + const __m128i zero = _mm_setzero_si128(); + int y; + if (size == 4) { + const __m128i top_values = _mm_cvtsi32_si128(WebPMemToUint32(top)); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + for (y = 0; y < 4; ++y, dst += BPS) { + const int val = dst[-1] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + WebPUint32ToMem(dst, _mm_cvtsi128_si32(out)); + } + } else if (size == 8) { + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + for (y = 0; y < 8; ++y, dst += BPS) { + const int val = dst[-1] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + _mm_storel_epi64((__m128i*)dst, out); + } + } else { + const __m128i top_values = _mm_loadu_si128((const __m128i*)top); + const __m128i top_base_0 = _mm_unpacklo_epi8(top_values, zero); + const __m128i top_base_1 = _mm_unpackhi_epi8(top_values, zero); + for (y = 0; y < 16; ++y, dst += BPS) { + const int val = dst[-1] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out_0 = _mm_add_epi16(base, top_base_0); + const __m128i out_1 = _mm_add_epi16(base, top_base_1); + const __m128i out = _mm_packus_epi16(out_0, out_1); + _mm_storeu_si128((__m128i*)dst, out); + } + } +} + +static void TM4(uint8_t* dst) { TrueMotion(dst, 4); } +static void TM8uv(uint8_t* dst) { TrueMotion(dst, 8); } +static void TM16(uint8_t* dst) { TrueMotion(dst, 16); } + +static void VE16(uint8_t* dst) { + const __m128i top = _mm_loadu_si128((const __m128i*)(dst - BPS)); + int j; + for (j = 0; j < 16; ++j) { + _mm_storeu_si128((__m128i*)(dst + j * BPS), top); + } +} + +static void HE16(uint8_t* dst) { // horizontal + int j; + for (j = 16; j > 0; --j) { + const __m128i values = _mm_set1_epi8(dst[-1]); + _mm_storeu_si128((__m128i*)dst, values); + dst += BPS; + } +} + +static WEBP_INLINE void Put16(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8(v); + for (j = 0; j < 16; ++j) { + _mm_storeu_si128((__m128i*)(dst + j * BPS), values); + } +} + +static void DC16(uint8_t* dst) { // DC + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadu_si128((const __m128i*)(dst - BPS)); + const __m128i sad8x2 = _mm_sad_epu8(top, zero); + // sum the two sads: sad8x2[0:1] + sad8x2[8:9] + const __m128i sum = _mm_add_epi16(sad8x2, _mm_shuffle_epi32(sad8x2, 2)); + int left = 0; + int j; + for (j = 0; j < 16; ++j) { + left += dst[-1 + j * BPS]; + } + { + const int DC = _mm_cvtsi128_si32(sum) + left + 16; + Put16(DC >> 5, dst); + } +} + +static void DC16NoTop(uint8_t* dst) { // DC with top samples not available + int DC = 8; + int j; + for (j = 0; j < 16; ++j) { + DC += dst[-1 + j * BPS]; + } + Put16(DC >> 4, dst); +} + +static void DC16NoLeft(uint8_t* dst) { // DC with left samples not available + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadu_si128((const __m128i*)(dst - BPS)); + const __m128i sad8x2 = _mm_sad_epu8(top, zero); + // sum the two sads: sad8x2[0:1] + sad8x2[8:9] + const __m128i sum = _mm_add_epi16(sad8x2, _mm_shuffle_epi32(sad8x2, 2)); + const int DC = _mm_cvtsi128_si32(sum) + 8; + Put16(DC >> 4, dst); +} + +static void DC16NoTopLeft(uint8_t* dst) { // DC with no top and left samples + Put16(0x80, dst); +} + +//------------------------------------------------------------------------------ +// Chroma + +static void VE8uv(uint8_t* dst) { // vertical + int j; + const __m128i top = _mm_loadl_epi64((const __m128i*)(dst - BPS)); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), top); + } +} + +static void HE8uv(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 8; ++j) { + const __m128i values = _mm_set1_epi8(dst[-1]); + _mm_storel_epi64((__m128i*)dst, values); + dst += BPS; + } +} + +// helper for chroma-DC predictions +static WEBP_INLINE void Put8x8uv(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8(v); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), values); + } +} + +static void DC8uv(uint8_t* dst) { // DC + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadl_epi64((const __m128i*)(dst - BPS)); + const __m128i sum = _mm_sad_epu8(top, zero); + int left = 0; + int j; + for (j = 0; j < 8; ++j) { + left += dst[-1 + j * BPS]; + } + { + const int DC = _mm_cvtsi128_si32(sum) + left + 8; + Put8x8uv(DC >> 4, dst); + } +} + +static void DC8uvNoLeft(uint8_t* dst) { // DC with no left samples + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadl_epi64((const __m128i*)(dst - BPS)); + const __m128i sum = _mm_sad_epu8(top, zero); + const int DC = _mm_cvtsi128_si32(sum) + 4; + Put8x8uv(DC >> 3, dst); +} + +static void DC8uvNoTop(uint8_t* dst) { // DC with no top samples + int dc0 = 4; + int i; + for (i = 0; i < 8; ++i) { + dc0 += dst[-1 + i * BPS]; + } + Put8x8uv(dc0 >> 3, dst); +} + +static void DC8uvNoTopLeft(uint8_t* dst) { // DC with nothing + Put8x8uv(0x80, dst); +} //------------------------------------------------------------------------------ // Entry point extern void VP8DspInitSSE2(void); -void VP8DspInitSSE2(void) { -#if defined(WEBP_USE_SSE2) - VP8Transform = TransformSSE2; +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitSSE2(void) { + VP8Transform = Transform; +#if defined(USE_TRANSFORM_AC3) + VP8TransformAC3 = TransformAC3; +#endif - VP8VFilter16 = VFilter16SSE2; - VP8HFilter16 = HFilter16SSE2; - VP8VFilter8 = VFilter8SSE2; - VP8HFilter8 = HFilter8SSE2; - VP8VFilter16i = VFilter16iSSE2; - VP8HFilter16i = HFilter16iSSE2; - VP8VFilter8i = VFilter8iSSE2; - VP8HFilter8i = HFilter8iSSE2; + VP8VFilter16 = VFilter16; + VP8HFilter16 = HFilter16; + VP8VFilter8 = VFilter8; + VP8HFilter8 = HFilter8; + VP8VFilter16i = VFilter16i; + VP8HFilter16i = HFilter16i; + VP8VFilter8i = VFilter8i; + VP8HFilter8i = HFilter8i; - VP8SimpleVFilter16 = SimpleVFilter16SSE2; - VP8SimpleHFilter16 = SimpleHFilter16SSE2; - VP8SimpleVFilter16i = SimpleVFilter16iSSE2; - VP8SimpleHFilter16i = SimpleHFilter16iSSE2; -#endif // WEBP_USE_SSE2 + VP8SimpleVFilter16 = SimpleVFilter16; + VP8SimpleHFilter16 = SimpleHFilter16; + VP8SimpleVFilter16i = SimpleVFilter16i; + VP8SimpleHFilter16i = SimpleHFilter16i; + + VP8PredLuma4[1] = TM4; + VP8PredLuma4[2] = VE4; + VP8PredLuma4[4] = RD4; + VP8PredLuma4[5] = VR4; + VP8PredLuma4[6] = LD4; + VP8PredLuma4[7] = VL4; + + VP8PredLuma16[0] = DC16; + VP8PredLuma16[1] = TM16; + VP8PredLuma16[2] = VE16; + VP8PredLuma16[3] = HE16; + VP8PredLuma16[4] = DC16NoTop; + VP8PredLuma16[5] = DC16NoLeft; + VP8PredLuma16[6] = DC16NoTopLeft; + + VP8PredChroma8[0] = DC8uv; + VP8PredChroma8[1] = TM8uv; + VP8PredChroma8[2] = VE8uv; + VP8PredChroma8[3] = HE8uv; + VP8PredChroma8[4] = DC8uvNoTop; + VP8PredChroma8[5] = DC8uvNoLeft; + VP8PredChroma8[6] = DC8uvNoTopLeft; } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8DspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/dsp/dec_sse41.c b/3rdparty/libwebp/dsp/dec_sse41.c new file mode 100644 index 0000000000..4e81ec4d80 --- /dev/null +++ b/3rdparty/libwebp/dsp/dec_sse41.c @@ -0,0 +1,46 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4 version of some decoding functions. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE41) + +#include +#include "../dec/vp8i_dec.h" +#include "../utils/utils.h" + +static void HE16(uint8_t* dst) { // horizontal + int j; + const __m128i kShuffle3 = _mm_set1_epi8(3); + for (j = 16; j > 0; --j) { + const __m128i in = _mm_cvtsi32_si128(WebPMemToUint32(dst - 4)); + const __m128i values = _mm_shuffle_epi8(in, kShuffle3); + _mm_storeu_si128((__m128i*)dst, values); + dst += BPS; + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitSSE41(void) { + VP8PredLuma16[3] = HE16; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(VP8DspInitSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/3rdparty/libwebp/dsp/dsp.h b/3rdparty/libwebp/dsp/dsp.h index 01a95891fe..813fed4a35 100644 --- a/3rdparty/libwebp/dsp/dsp.h +++ b/3rdparty/libwebp/dsp/dsp.h @@ -14,39 +14,145 @@ #ifndef WEBP_DSP_DSP_H_ #define WEBP_DSP_DSP_H_ +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" +#endif + #include "../webp/types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif +#define BPS 32 // this is the common stride for enc/dec + //------------------------------------------------------------------------------ // CPU detection -#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) +#if defined(__GNUC__) +# define LOCAL_GCC_VERSION ((__GNUC__ << 8) | __GNUC_MINOR__) +# define LOCAL_GCC_PREREQ(maj, min) \ + (LOCAL_GCC_VERSION >= (((maj) << 8) | (min))) +#else +# define LOCAL_GCC_VERSION 0 +# define LOCAL_GCC_PREREQ(maj, min) 0 +#endif + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +#if defined(_MSC_VER) && _MSC_VER > 1310 && \ + (defined(_M_X64) || defined(_M_IX86)) #define WEBP_MSC_SSE2 // Visual C++ SSE2 targets #endif -#if defined(__SSE2__) || defined(WEBP_MSC_SSE2) +#if defined(_MSC_VER) && _MSC_VER >= 1500 && \ + (defined(_M_X64) || defined(_M_IX86)) +#define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets +#endif + +// WEBP_HAVE_* are used to indicate the presence of the instruction set in dsp +// files without intrinsics, allowing the corresponding Init() to be called. +// Files containing intrinsics will need to be built targeting the instruction +// set so should succeed on one of the earlier tests. +#if defined(__SSE2__) || defined(WEBP_MSC_SSE2) || defined(WEBP_HAVE_SSE2) #define WEBP_USE_SSE2 #endif +#if defined(__SSE4_1__) || defined(WEBP_MSC_SSE41) || defined(WEBP_HAVE_SSE41) +#define WEBP_USE_SSE41 +#endif + +#if defined(__AVX2__) || defined(WEBP_HAVE_AVX2) +#define WEBP_USE_AVX2 +#endif + #if defined(__ANDROID__) && defined(__ARM_ARCH_7A__) #define WEBP_ANDROID_NEON // Android targets that might support NEON #endif -#if defined(__ARM_NEON__) || defined(WEBP_ANDROID_NEON) +// The intrinsics currently cause compiler errors with arm-nacl-gcc and the +// inline assembly would need to be modified for use with Native Client. +#if (defined(__ARM_NEON__) || defined(WEBP_ANDROID_NEON) || \ + defined(__aarch64__) || defined(WEBP_HAVE_NEON)) && \ + !defined(__native_client__) #define WEBP_USE_NEON #endif +#if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) +#define WEBP_USE_NEON +#define WEBP_USE_INTRINSICS +#endif + +#if defined(__mips__) && !defined(__mips64) && \ + defined(__mips_isa_rev) && (__mips_isa_rev >= 1) && (__mips_isa_rev < 6) +#define WEBP_USE_MIPS32 +#if (__mips_isa_rev >= 2) +#define WEBP_USE_MIPS32_R2 +#if defined(__mips_dspr2) || (__mips_dsp_rev >= 2) +#define WEBP_USE_MIPS_DSP_R2 +#endif +#endif +#endif + +#if defined(__mips_msa) && defined(__mips_isa_rev) && (__mips_isa_rev >= 5) +#define WEBP_USE_MSA +#endif + +// This macro prevents thread_sanitizer from reporting known concurrent writes. +#define WEBP_TSAN_IGNORE_FUNCTION +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) +#undef WEBP_TSAN_IGNORE_FUNCTION +#define WEBP_TSAN_IGNORE_FUNCTION __attribute__((no_sanitize_thread)) +#endif +#endif + +#define WEBP_UBSAN_IGNORE_UNDEF +#define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(no_sanitize) +// This macro prevents the undefined behavior sanitizer from reporting +// failures. This is only meant to silence unaligned loads on platforms that +// are known to support them. +#undef WEBP_UBSAN_IGNORE_UNDEF +#define WEBP_UBSAN_IGNORE_UNDEF \ + __attribute__((no_sanitize("undefined"))) + +// This macro prevents the undefined behavior sanitizer from reporting +// failures related to unsigned integer overflows. This is only meant to +// silence cases where this well defined behavior is expected. +#undef WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW +#define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW \ + __attribute__((no_sanitize("unsigned-integer-overflow"))) +#endif +#endif + typedef enum { kSSE2, kSSE3, - kNEON + kSlowSSSE3, // special feature for slow SSSE3 architectures + kSSE4_1, + kAVX, + kAVX2, + kNEON, + kMIPS32, + kMIPSdspR2, + kMSA } CPUFeature; // returns true if the CPU supports the feature. typedef int (*VP8CPUInfo)(CPUFeature feature); -extern VP8CPUInfo VP8GetCPUInfo; +WEBP_EXTERN(VP8CPUInfo) VP8GetCPUInfo; + +//------------------------------------------------------------------------------ +// Init stub generator + +// Defines an init function stub to ensure each module exposes a symbol, +// avoiding a compiler warning. +#define WEBP_DSP_INIT_STUB(func) \ + extern void func(void); \ + WEBP_TSAN_IGNORE_FUNCTION void func(void) {} //------------------------------------------------------------------------------ // Encoding @@ -60,7 +166,7 @@ typedef void (*VP8Fdct)(const uint8_t* src, const uint8_t* ref, int16_t* out); typedef void (*VP8WHT)(const int16_t* in, int16_t* out); extern VP8Idct VP8ITransform; extern VP8Fdct VP8FTransform; -extern VP8WHT VP8ITransformWHT; +extern VP8Fdct VP8FTransform2; // performs two transforms at a time extern VP8WHT VP8FTransformWHT; // Predictions // *dst is the destination block. *top and *left can be NULL. @@ -75,25 +181,110 @@ typedef int (*VP8Metric)(const uint8_t* pix, const uint8_t* ref); extern VP8Metric VP8SSE16x16, VP8SSE16x8, VP8SSE8x8, VP8SSE4x4; typedef int (*VP8WMetric)(const uint8_t* pix, const uint8_t* ref, const uint16_t* const weights); +// The weights for VP8TDisto4x4 and VP8TDisto16x16 contain a row-major +// 4 by 4 symmetric matrix. extern VP8WMetric VP8TDisto4x4, VP8TDisto16x16; +// Compute the average (DC) of four 4x4 blocks. +// Each sub-4x4 block #i sum is stored in dc[i]. +typedef void (*VP8MeanMetric)(const uint8_t* ref, uint32_t dc[4]); +extern VP8MeanMetric VP8Mean16x4; + typedef void (*VP8BlockCopy)(const uint8_t* src, uint8_t* dst); extern VP8BlockCopy VP8Copy4x4; +extern VP8BlockCopy VP8Copy16x8; // Quantization struct VP8Matrix; // forward declaration typedef int (*VP8QuantizeBlock)(int16_t in[16], int16_t out[16], - int n, const struct VP8Matrix* const mtx); -extern VP8QuantizeBlock VP8EncQuantizeBlock; + const struct VP8Matrix* const mtx); +// Same as VP8QuantizeBlock, but quantizes two consecutive blocks. +typedef int (*VP8Quantize2Blocks)(int16_t in[32], int16_t out[32], + const struct VP8Matrix* const mtx); -// Collect histogram for susceptibility calculation and accumulate in histo[]. -struct VP8Histogram; +extern VP8QuantizeBlock VP8EncQuantizeBlock; +extern VP8Quantize2Blocks VP8EncQuantize2Blocks; + +// specific to 2nd transform: +typedef int (*VP8QuantizeBlockWHT)(int16_t in[16], int16_t out[16], + const struct VP8Matrix* const mtx); +extern VP8QuantizeBlockWHT VP8EncQuantizeBlockWHT; + +extern const int VP8DspScan[16 + 4 + 4]; + +// Collect histogram for susceptibility calculation. +#define MAX_COEFF_THRESH 31 // size of histogram used by CollectHistogram. +typedef struct { + // We only need to store max_value and last_non_zero, not the distribution. + int max_value; + int last_non_zero; +} VP8Histogram; typedef void (*VP8CHisto)(const uint8_t* ref, const uint8_t* pred, int start_block, int end_block, - struct VP8Histogram* const histo); -extern const int VP8DspScan[16 + 4 + 4]; + VP8Histogram* const histo); extern VP8CHisto VP8CollectHistogram; +// General-purpose util function to help VP8CollectHistogram(). +void VP8SetHistogramData(const int distribution[MAX_COEFF_THRESH + 1], + VP8Histogram* const histo); -void VP8EncDspInit(void); // must be called before using any of the above +// must be called before using any of the above +void VP8EncDspInit(void); + +//------------------------------------------------------------------------------ +// cost functions (encoding) + +extern const uint16_t VP8EntropyCost[256]; // 8bit fixed-point log(p) +// approximate cost per level: +extern const uint16_t VP8LevelFixedCosts[2047 /*MAX_LEVEL*/ + 1]; +extern const uint8_t VP8EncBands[16 + 1]; + +struct VP8Residual; +typedef void (*VP8SetResidualCoeffsFunc)(const int16_t* const coeffs, + struct VP8Residual* const res); +extern VP8SetResidualCoeffsFunc VP8SetResidualCoeffs; + +// Cost calculation function. +typedef int (*VP8GetResidualCostFunc)(int ctx0, + const struct VP8Residual* const res); +extern VP8GetResidualCostFunc VP8GetResidualCost; + +// must be called before anything using the above +void VP8EncDspCostInit(void); + +//------------------------------------------------------------------------------ +// SSIM / PSNR utils + +// struct for accumulating statistical moments +typedef struct { + uint32_t w; // sum(w_i) : sum of weights + uint32_t xm, ym; // sum(w_i * x_i), sum(w_i * y_i) + uint32_t xxm, xym, yym; // sum(w_i * x_i * x_i), etc. +} VP8DistoStats; + +// Compute the final SSIM value +// The non-clipped version assumes stats->w = (2 * VP8_SSIM_KERNEL + 1)^2. +double VP8SSIMFromStats(const VP8DistoStats* const stats); +double VP8SSIMFromStatsClipped(const VP8DistoStats* const stats); + +#define VP8_SSIM_KERNEL 3 // total size of the kernel: 2 * VP8_SSIM_KERNEL + 1 +typedef double (*VP8SSIMGetClippedFunc)(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2, + int xo, int yo, // center position + int W, int H); // plane dimension + +// This version is called with the guarantee that you can load 8 bytes and +// 8 rows at offset src1 and src2 +typedef double (*VP8SSIMGetFunc)(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2); + +extern VP8SSIMGetFunc VP8SSIMGet; // unclipped / unchecked +extern VP8SSIMGetClippedFunc VP8SSIMGetClipped; // with clipping + +typedef uint32_t (*VP8AccumulateSSEFunc)(const uint8_t* src1, + const uint8_t* src2, int len); +extern VP8AccumulateSSEFunc VP8AccumulateSSE; + +// must be called before using any of the above directly +void VP8SSIMDspInit(void); //------------------------------------------------------------------------------ // Decoding @@ -102,6 +293,7 @@ typedef void (*VP8DecIdct)(const int16_t* coeffs, uint8_t* dst); // when doing two transforms, coeffs is actually int16_t[2][16]. typedef void (*VP8DecIdct2)(const int16_t* coeffs, uint8_t* dst, int do_two); extern VP8DecIdct2 VP8Transform; +extern VP8DecIdct VP8TransformAC3; extern VP8DecIdct VP8TransformUV; extern VP8DecIdct VP8TransformDC; extern VP8DecIdct VP8TransformDCUV; @@ -110,9 +302,17 @@ extern VP8WHT VP8TransformWHT; // *dst is the destination block, with stride BPS. Boundary samples are // assumed accessible when needed. typedef void (*VP8PredFunc)(uint8_t* dst); -extern const VP8PredFunc VP8PredLuma16[/* NUM_B_DC_MODES */]; -extern const VP8PredFunc VP8PredChroma8[/* NUM_B_DC_MODES */]; -extern const VP8PredFunc VP8PredLuma4[/* NUM_BMODES */]; +extern VP8PredFunc VP8PredLuma16[/* NUM_B_DC_MODES */]; +extern VP8PredFunc VP8PredChroma8[/* NUM_B_DC_MODES */]; +extern VP8PredFunc VP8PredLuma4[/* NUM_BMODES */]; + +// clipping tables (for filtering) +extern const int8_t* const VP8ksclip1; // clips [-1020, 1020] to [-128, 127] +extern const int8_t* const VP8ksclip2; // clips [-112, 112] to [-16, 15] +extern const uint8_t* const VP8kclip1; // clips [-255,511] to [0,255] +extern const uint8_t* const VP8kabs0; // abs(x) for x in [-255,255] +// must be called first +void VP8InitClipTables(void); // simple filter (only for luma) typedef void (*VP8SimpleFilterFunc)(uint8_t* p, int stride, int thresh); @@ -138,6 +338,15 @@ extern VP8LumaFilterFunc VP8HFilter16i; extern VP8ChromaFilterFunc VP8VFilter8i; // filtering u and v altogether extern VP8ChromaFilterFunc VP8HFilter8i; +// Dithering. Combines dithering values (centered around 128) with dst[], +// according to: dst[] = clip(dst[] + (((dither[]-128) + 8) >> 4) +#define VP8_DITHER_DESCALE 4 +#define VP8_DITHER_DESCALE_ROUNDER (1 << (VP8_DITHER_DESCALE - 1)) +#define VP8_DITHER_AMP_BITS 7 +#define VP8_DITHER_AMP_CENTER (1 << VP8_DITHER_AMP_BITS) +extern void (*VP8DitherCombine8x8)(const uint8_t* dither, uint8_t* dst, + int dst_stride); + // must be called before anything using the above void VP8DspInit(void); @@ -146,6 +355,8 @@ void VP8DspInit(void); #define FANCY_UPSAMPLING // undefined to remove fancy upsampling support +// Convert a pair of y/u/v lines together to the output rgb/a colorspace. +// bottom_y can be NULL if only one line of output is needed (at top/bottom). typedef void (*WebPUpsampleLinePairFunc)( const uint8_t* top_y, const uint8_t* bottom_y, const uint8_t* top_u, const uint8_t* top_v, @@ -157,21 +368,20 @@ typedef void (*WebPUpsampleLinePairFunc)( // Fancy upsampling functions to convert YUV to RGB(A) modes extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; -// Initializes SSE2 version of the fancy upsamplers. -void WebPInitUpsamplersSSE2(void); - -// NEON version -void WebPInitUpsamplersNEON(void); - #endif // FANCY_UPSAMPLING -// Point-sampling methods. -typedef void (*WebPSampleLinePairFunc)( - const uint8_t* top_y, const uint8_t* bottom_y, - const uint8_t* u, const uint8_t* v, - uint8_t* top_dst, uint8_t* bottom_dst, int len); +// Per-row point-sampling methods. +typedef void (*WebPSamplerRowFunc)(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len); +// Generic function to apply 'WebPSamplerRowFunc' to the whole plane: +void WebPSamplerProcessPlane(const uint8_t* y, int y_stride, + const uint8_t* u, const uint8_t* v, int uv_stride, + uint8_t* dst, int dst_stride, + int width, int height, WebPSamplerRowFunc func); -extern const WebPSampleLinePairFunc WebPSamplers[/* MODE_LAST */]; +// Sampling functions to convert rows of YUV to RGB(A) +extern WebPSamplerRowFunc WebPSamplers[/* MODE_LAST */]; // General function for converting two lines of ARGB or RGBA. // 'alpha_is_last' should be true if 0xff000000 is stored in memory as @@ -183,13 +393,93 @@ typedef void (*WebPYUV444Converter)(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst, int len); -extern const WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; -// Main function to be called +// Must be called before using the WebPUpsamplers[] (and for premultiplied +// colorspaces like rgbA, rgbA4444, etc) void WebPInitUpsamplers(void); +// Must be called before using WebPSamplers[] +void WebPInitSamplers(void); +// Must be called before using WebPYUV444Converters[] +void WebPInitYUV444Converters(void); //------------------------------------------------------------------------------ -// Pre-multiply planes with alpha values +// ARGB -> YUV converters + +// Convert ARGB samples to luma Y. +extern void (*WebPConvertARGBToY)(const uint32_t* argb, uint8_t* y, int width); +// Convert ARGB samples to U/V with downsampling. do_store should be '1' for +// even lines and '0' for odd ones. 'src_width' is the original width, not +// the U/V one. +extern void (*WebPConvertARGBToUV)(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store); + +// Convert a row of accumulated (four-values) of rgba32 toward U/V +extern void (*WebPConvertRGBA32ToUV)(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width); + +// Convert RGB or BGR to Y +extern void (*WebPConvertRGB24ToY)(const uint8_t* rgb, uint8_t* y, int width); +extern void (*WebPConvertBGR24ToY)(const uint8_t* bgr, uint8_t* y, int width); + +// used for plain-C fallback. +extern void WebPConvertARGBToUV_C(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store); +extern void WebPConvertRGBA32ToUV_C(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width); + +// utilities for accurate RGB->YUV conversion +extern uint64_t (*WebPSharpYUVUpdateY)(const uint16_t* src, const uint16_t* ref, + uint16_t* dst, int len); +extern void (*WebPSharpYUVUpdateRGB)(const int16_t* src, const int16_t* ref, + int16_t* dst, int len); +extern void (*WebPSharpYUVFilterRow)(const int16_t* A, const int16_t* B, + int len, + const uint16_t* best_y, uint16_t* out); + +// Must be called before using the above. +void WebPInitConvertARGBToYUV(void); + +//------------------------------------------------------------------------------ +// Rescaler + +struct WebPRescaler; + +// Import a row of data and save its contribution in the rescaler. +// 'channel' denotes the channel number to be imported. 'Expand' corresponds to +// the wrk->x_expand case. Otherwise, 'Shrink' is to be used. +typedef void (*WebPRescalerImportRowFunc)(struct WebPRescaler* const wrk, + const uint8_t* src); + +extern WebPRescalerImportRowFunc WebPRescalerImportRowExpand; +extern WebPRescalerImportRowFunc WebPRescalerImportRowShrink; + +// Export one row (starting at x_out position) from rescaler. +// 'Expand' corresponds to the wrk->y_expand case. +// Otherwise 'Shrink' is to be used +typedef void (*WebPRescalerExportRowFunc)(struct WebPRescaler* const wrk); +extern WebPRescalerExportRowFunc WebPRescalerExportRowExpand; +extern WebPRescalerExportRowFunc WebPRescalerExportRowShrink; + +// Plain-C implementation, as fall-back. +extern void WebPRescalerImportRowExpandC(struct WebPRescaler* const wrk, + const uint8_t* src); +extern void WebPRescalerImportRowShrinkC(struct WebPRescaler* const wrk, + const uint8_t* src); +extern void WebPRescalerExportRowExpandC(struct WebPRescaler* const wrk); +extern void WebPRescalerExportRowShrinkC(struct WebPRescaler* const wrk); + +// Main entry calls: +extern void WebPRescalerImportRow(struct WebPRescaler* const wrk, + const uint8_t* src); +// Export one row (starting at x_out position) from rescaler. +extern void WebPRescalerExportRow(struct WebPRescaler* const wrk); + +// Must be called first before using the above. +void WebPRescalerDspInit(void); + +//------------------------------------------------------------------------------ +// Utilities for processing transparent channel. // Apply alpha pre-multiply on an rgba, bgra or argb plane of size w * h. // alpha_first should be 0 for argb, 1 for rgba or bgra (where alpha is last). @@ -200,15 +490,104 @@ extern void (*WebPApplyAlphaMultiply)( extern void (*WebPApplyAlphaMultiply4444)( uint8_t* rgba4444, int w, int h, int stride); -// To be called first before using the above. -void WebPInitPremultiply(void); +// Dispatch the values from alpha[] plane to the ARGB destination 'dst'. +// Returns true if alpha[] plane has non-trivial values different from 0xff. +extern int (*WebPDispatchAlpha)(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint8_t* dst, int dst_stride); -void WebPInitPremultiplySSE2(void); // should not be called directly. -void WebPInitPremultiplyNEON(void); +// Transfer packed 8b alpha[] values to green channel in dst[], zero'ing the +// A/R/B values. 'dst_stride' is the stride for dst[] in uint32_t units. +extern void (*WebPDispatchAlphaToGreen)(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint32_t* dst, int dst_stride); + +// Extract the alpha values from 32b values in argb[] and pack them into alpha[] +// (this is the opposite of WebPDispatchAlpha). +// Returns true if there's only trivial 0xff alpha values. +extern int (*WebPExtractAlpha)(const uint8_t* argb, int argb_stride, + int width, int height, + uint8_t* alpha, int alpha_stride); + +// Extract the green values from 32b values in argb[] and pack them into alpha[] +// (this is the opposite of WebPDispatchAlphaToGreen). +extern void (*WebPExtractGreen)(const uint32_t* argb, uint8_t* alpha, int size); + +// Pre-Multiply operation transforms x into x * A / 255 (where x=Y,R,G or B). +// Un-Multiply operation transforms x into x * 255 / A. + +// Pre-Multiply or Un-Multiply (if 'inverse' is true) argb values in a row. +extern void (*WebPMultARGBRow)(uint32_t* const ptr, int width, int inverse); + +// Same a WebPMultARGBRow(), but for several rows. +void WebPMultARGBRows(uint8_t* ptr, int stride, int width, int num_rows, + int inverse); + +// Same for a row of single values, with side alpha values. +extern void (*WebPMultRow)(uint8_t* const ptr, const uint8_t* const alpha, + int width, int inverse); + +// Same a WebPMultRow(), but for several 'num_rows' rows. +void WebPMultRows(uint8_t* ptr, int stride, + const uint8_t* alpha, int alpha_stride, + int width, int num_rows, int inverse); + +// Plain-C versions, used as fallback by some implementations. +void WebPMultRowC(uint8_t* const ptr, const uint8_t* const alpha, + int width, int inverse); +void WebPMultARGBRowC(uint32_t* const ptr, int width, int inverse); + +// To be called first before using the above. +void WebPInitAlphaProcessing(void); + +// ARGB packing function: a/r/g/b input is rgba or bgra order. +extern void (*VP8PackARGB)(const uint8_t* a, const uint8_t* r, + const uint8_t* g, const uint8_t* b, int len, + uint32_t* out); + +// RGB packing function. 'step' can be 3 or 4. r/g/b input is rgb or bgr order. +extern void (*VP8PackRGB)(const uint8_t* r, const uint8_t* g, const uint8_t* b, + int len, int step, uint32_t* out); + +// To be called first before using the above. +void VP8EncDspARGBInit(void); //------------------------------------------------------------------------------ +// Filter functions -#if defined(__cplusplus) || defined(c_plusplus) +typedef enum { // Filter types. + WEBP_FILTER_NONE = 0, + WEBP_FILTER_HORIZONTAL, + WEBP_FILTER_VERTICAL, + WEBP_FILTER_GRADIENT, + WEBP_FILTER_LAST = WEBP_FILTER_GRADIENT + 1, // end marker + WEBP_FILTER_BEST, // meta-types + WEBP_FILTER_FAST +} WEBP_FILTER_TYPE; + +typedef void (*WebPFilterFunc)(const uint8_t* in, int width, int height, + int stride, uint8_t* out); +// In-place un-filtering. +// Warning! 'prev_line' pointer can be equal to 'cur_line' or 'preds'. +typedef void (*WebPUnfilterFunc)(const uint8_t* prev_line, const uint8_t* preds, + uint8_t* cur_line, int width); + +// Filter the given data using the given predictor. +// 'in' corresponds to a 2-dimensional pixel array of size (stride * height) +// in raster order. +// 'stride' is number of bytes per scan line (with possible padding). +// 'out' should be pre-allocated. +extern WebPFilterFunc WebPFilters[WEBP_FILTER_LAST]; + +// In-place reconstruct the original data from the given filtered data. +// The reconstruction will be done for 'num_rows' rows starting from 'row' +// (assuming rows upto 'row - 1' are already reconstructed). +extern WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST]; + +// To be called first before using the above. +void VP8FiltersInit(void); + +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/dsp/enc.c b/3rdparty/libwebp/dsp/enc.c index 552807ad95..f31bc6de18 100644 --- a/3rdparty/libwebp/dsp/enc.c +++ b/3rdparty/libwebp/dsp/enc.c @@ -11,13 +11,11 @@ // // Author: Skal (pascal.massimino@gmail.com) +#include #include // for abs() -#include "./dsp.h" -#include "../enc/vp8enci.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./dsp.h" +#include "../enc/vp8i_enc.h" static WEBP_INLINE uint8_t clip_8b(int v) { return (!(v & ~0xff)) ? v : (v < 0) ? 0 : 255; @@ -42,10 +40,27 @@ const int VP8DspScan[16 + 4 + 4] = { 8 + 0 * BPS, 12 + 0 * BPS, 8 + 4 * BPS, 12 + 4 * BPS // V }; +// general-purpose util function +void VP8SetHistogramData(const int distribution[MAX_COEFF_THRESH + 1], + VP8Histogram* const histo) { + int max_value = 0, last_non_zero = 1; + int k; + for (k = 0; k <= MAX_COEFF_THRESH; ++k) { + const int value = distribution[k]; + if (value > 0) { + if (value > max_value) max_value = value; + last_non_zero = k; + } + } + histo->max_value = max_value; + histo->last_non_zero = last_non_zero; +} + static void CollectHistogram(const uint8_t* ref, const uint8_t* pred, int start_block, int end_block, VP8Histogram* const histo) { int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; for (j = start_block; j < end_block; ++j) { int k; int16_t out[16]; @@ -54,11 +69,12 @@ static void CollectHistogram(const uint8_t* ref, const uint8_t* pred, // Convert coefficients to bin. for (k = 0; k < 16; ++k) { - const int v = abs(out[k]) >> 3; // TODO(skal): add rounding? + const int v = abs(out[k]) >> 3; const int clipped_value = clip_max(v, MAX_COEFF_THRESH); - histo->distribution[clipped_value]++; + ++distribution[clipped_value]; } } + VP8SetHistogramData(distribution, histo); } //------------------------------------------------------------------------------ @@ -70,7 +86,7 @@ static uint8_t clip1[255 + 510 + 1]; // clips [-255,510] to [0,255] // and make sure it's set to true _last_ (so as to be thread-safe) static volatile int tables_ok = 0; -static void InitTables(void) { +static WEBP_TSAN_IGNORE_FUNCTION void InitTables(void) { if (!tables_ok) { int i; for (i = -255; i <= 255 + 255; ++i) { @@ -161,36 +177,14 @@ static void FTransform(const uint8_t* src, const uint8_t* ref, int16_t* out) { } } -static void ITransformWHT(const int16_t* in, int16_t* out) { - int tmp[16]; - int i; - for (i = 0; i < 4; ++i) { - const int a0 = in[0 + i] + in[12 + i]; - const int a1 = in[4 + i] + in[ 8 + i]; - const int a2 = in[4 + i] - in[ 8 + i]; - const int a3 = in[0 + i] - in[12 + i]; - tmp[0 + i] = a0 + a1; - tmp[8 + i] = a0 - a1; - tmp[4 + i] = a3 + a2; - tmp[12 + i] = a3 - a2; - } - for (i = 0; i < 4; ++i) { - const int dc = tmp[0 + i * 4] + 3; // w/ rounder - const int a0 = dc + tmp[3 + i * 4]; - const int a1 = tmp[1 + i * 4] + tmp[2 + i * 4]; - const int a2 = tmp[1 + i * 4] - tmp[2 + i * 4]; - const int a3 = dc - tmp[3 + i * 4]; - out[ 0] = (a0 + a1) >> 3; - out[16] = (a3 + a2) >> 3; - out[32] = (a0 - a1) >> 3; - out[48] = (a3 - a2) >> 3; - out += 64; - } +static void FTransform2(const uint8_t* src, const uint8_t* ref, int16_t* out) { + VP8FTransform(src, ref, out); + VP8FTransform(src + 4, ref + 4, out + 16); } static void FTransformWHT(const int16_t* in, int16_t* out) { // input is 12b signed - int16_t tmp[16]; + int32_t tmp[16]; int i; for (i = 0; i < 4; ++i, in += 64) { const int a0 = (in[0 * 16] + in[2 * 16]); // 13b @@ -224,8 +218,6 @@ static void FTransformWHT(const int16_t* in, int16_t* out) { //------------------------------------------------------------------------------ // Intra predictions -#define DST(x, y) dst[(x) + (y) * BPS] - static WEBP_INLINE void Fill(uint8_t* dst, int value, int size) { int j; for (j = 0; j < size; ++j) { @@ -236,7 +228,7 @@ static WEBP_INLINE void Fill(uint8_t* dst, int value, int size) { static WEBP_INLINE void VerticalPred(uint8_t* dst, const uint8_t* top, int size) { int j; - if (top) { + if (top != NULL) { for (j = 0; j < size; ++j) memcpy(dst + j * BPS, top, size); } else { Fill(dst, 127, size); @@ -245,7 +237,7 @@ static WEBP_INLINE void VerticalPred(uint8_t* dst, static WEBP_INLINE void HorizontalPred(uint8_t* dst, const uint8_t* left, int size) { - if (left) { + if (left != NULL) { int j; for (j = 0; j < size; ++j) { memset(dst + j * BPS, left[j], size); @@ -258,8 +250,8 @@ static WEBP_INLINE void HorizontalPred(uint8_t* dst, static WEBP_INLINE void TrueMotion(uint8_t* dst, const uint8_t* left, const uint8_t* top, int size) { int y; - if (left) { - if (top) { + if (left != NULL) { + if (top != NULL) { const uint8_t* const clip = clip1 + 255 - left[-1]; for (y = 0; y < size; ++y) { const uint8_t* const clip_table = clip + left[y]; @@ -277,7 +269,7 @@ static WEBP_INLINE void TrueMotion(uint8_t* dst, const uint8_t* left, // is equivalent to VE prediction where you just copy the top samples. // Note that if top samples are not available, the default value is // then 129, and not 127 as in the VerticalPred case. - if (top) { + if (top != NULL) { VerticalPred(dst, top, size); } else { Fill(dst, 129, size); @@ -290,15 +282,15 @@ static WEBP_INLINE void DCMode(uint8_t* dst, const uint8_t* left, int size, int round, int shift) { int DC = 0; int j; - if (top) { + if (top != NULL) { for (j = 0; j < size; ++j) DC += top[j]; - if (left) { // top and left present + if (left != NULL) { // top and left present for (j = 0; j < size; ++j) DC += left[j]; } else { // top, but no left DC += DC; } DC = (DC + round) >> shift; - } else if (left) { // left but no top + } else if (left != NULL) { // left but no top for (j = 0; j < size; ++j) DC += left[j]; DC += DC; DC = (DC + round) >> shift; @@ -320,8 +312,8 @@ static void IntraChromaPreds(uint8_t* dst, const uint8_t* left, TrueMotion(C8TM8 + dst, left, top, 8); // V block dst += 8; - if (top) top += 8; - if (left) left += 16; + if (top != NULL) top += 8; + if (left != NULL) left += 16; DCMode(C8DC8 + dst, left, top, 8, 8, 4); VerticalPred(C8VE8 + dst, top, 8); HorizontalPred(C8HE8 + dst, left, 8); @@ -342,7 +334,8 @@ static void Intra16Preds(uint8_t* dst, //------------------------------------------------------------------------------ // luma 4x4 prediction -#define AVG3(a, b, c) (((a) + 2 * (b) + (c) + 2) >> 2) +#define DST(x, y) dst[(x) + (y) * BPS] +#define AVG3(a, b, c) ((uint8_t)(((a) + 2 * (b) + (c) + 2) >> 2)) #define AVG2(a, b) (((a) + (b) + 1) >> 1) static void VE4(uint8_t* dst, const uint8_t* top) { // vertical @@ -364,10 +357,10 @@ static void HE4(uint8_t* dst, const uint8_t* top) { // horizontal const int J = top[-3]; const int K = top[-4]; const int L = top[-5]; - *(uint32_t*)(dst + 0 * BPS) = 0x01010101U * AVG3(X, I, J); - *(uint32_t*)(dst + 1 * BPS) = 0x01010101U * AVG3(I, J, K); - *(uint32_t*)(dst + 2 * BPS) = 0x01010101U * AVG3(J, K, L); - *(uint32_t*)(dst + 3 * BPS) = 0x01010101U * AVG3(K, L, L); + WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(X, I, J)); + WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(I, J, K)); + WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(J, K, L)); + WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(K, L, L)); } static void DC4(uint8_t* dst, const uint8_t* top) { @@ -558,6 +551,20 @@ static int SSE4x4(const uint8_t* a, const uint8_t* b) { return GetSSE(a, b, 4, 4); } +static void Mean16x4(const uint8_t* ref, uint32_t dc[4]) { + int k, x, y; + for (k = 0; k < 4; ++k) { + uint32_t avg = 0; + for (y = 0; y < 4; ++y) { + for (x = 0; x < 4; ++x) { + avg += ref[x + y * BPS]; + } + } + dc[k] = avg; + ref += 4; // go to next 4x4 block. + } +} + //------------------------------------------------------------------------------ // Texture distortion // @@ -566,6 +573,7 @@ static int SSE4x4(const uint8_t* a, const uint8_t* b) { // Hadamard transform // Returns the weighted sum of the absolute value of transformed coefficients. +// w[] contains a row-major 4 by 4 symmetric matrix. static int TTransform(const uint8_t* in, const uint16_t* w) { int sum = 0; int tmp[16]; @@ -629,21 +637,23 @@ static const uint8_t kZigzag[16] = { // Simple quantization static int QuantizeBlock(int16_t in[16], int16_t out[16], - int n, const VP8Matrix* const mtx) { + const VP8Matrix* const mtx) { int last = -1; - for (; n < 16; ++n) { + int n; + for (n = 0; n < 16; ++n) { const int j = kZigzag[n]; const int sign = (in[j] < 0); - const int coeff = (sign ? -in[j] : in[j]) + mtx->sharpen_[j]; + const uint32_t coeff = (sign ? -in[j] : in[j]) + mtx->sharpen_[j]; if (coeff > mtx->zthresh_[j]) { - const int Q = mtx->q_[j]; - const int iQ = mtx->iq_[j]; - const int B = mtx->bias_[j]; - out[n] = QUANTDIV(coeff, iQ, B); - if (out[n] > MAX_LEVEL) out[n] = MAX_LEVEL; - if (sign) out[n] = -out[n]; - in[j] = out[n] * Q; - if (out[n]) last = n; + const uint32_t Q = mtx->q_[j]; + const uint32_t iQ = mtx->iq_[j]; + const uint32_t B = mtx->bias_[j]; + int level = QUANTDIV(coeff, iQ, B); + if (level > MAX_LEVEL) level = MAX_LEVEL; + if (sign) level = -level; + in[j] = level * (int)Q; + out[n] = level; + if (level) last = n; } else { out[n] = 0; in[j] = 0; @@ -652,19 +662,167 @@ static int QuantizeBlock(int16_t in[16], int16_t out[16], return (last >= 0); } +static int Quantize2Blocks(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = VP8EncQuantizeBlock(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= VP8EncQuantizeBlock(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} + //------------------------------------------------------------------------------ // Block copy -static WEBP_INLINE void Copy(const uint8_t* src, uint8_t* dst, int size) { +static WEBP_INLINE void Copy(const uint8_t* src, uint8_t* dst, int w, int h) { int y; - for (y = 0; y < size; ++y) { - memcpy(dst, src, size); + for (y = 0; y < h; ++y) { + memcpy(dst, src, w); src += BPS; dst += BPS; } } -static void Copy4x4(const uint8_t* src, uint8_t* dst) { Copy(src, dst, 4); } +static void Copy4x4(const uint8_t* src, uint8_t* dst) { + Copy(src, dst, 4, 4); +} + +static void Copy16x8(const uint8_t* src, uint8_t* dst) { + Copy(src, dst, 16, 8); +} + +//------------------------------------------------------------------------------ +// SSIM / PSNR + +// hat-shaped filter. Sum of coefficients is equal to 16. +static const uint32_t kWeight[2 * VP8_SSIM_KERNEL + 1] = { + 1, 2, 3, 4, 3, 2, 1 +}; +static const uint32_t kWeightSum = 16 * 16; // sum{kWeight}^2 + +static WEBP_INLINE double SSIMCalculation( + const VP8DistoStats* const stats, uint32_t N /*num samples*/) { + const uint32_t w2 = N * N; + const uint32_t C1 = 20 * w2; + const uint32_t C2 = 60 * w2; + const uint32_t C3 = 8 * 8 * w2; // 'dark' limit ~= 6 + const uint64_t xmxm = (uint64_t)stats->xm * stats->xm; + const uint64_t ymym = (uint64_t)stats->ym * stats->ym; + if (xmxm + ymym >= C3) { + const int64_t xmym = (int64_t)stats->xm * stats->ym; + const int64_t sxy = (int64_t)stats->xym * N - xmym; // can be negative + const uint64_t sxx = (uint64_t)stats->xxm * N - xmxm; + const uint64_t syy = (uint64_t)stats->yym * N - ymym; + // we descale by 8 to prevent overflow during the fnum/fden multiply. + const uint64_t num_S = (2 * (uint64_t)(sxy < 0 ? 0 : sxy) + C2) >> 8; + const uint64_t den_S = (sxx + syy + C2) >> 8; + const uint64_t fnum = (2 * xmym + C1) * num_S; + const uint64_t fden = (xmxm + ymym + C1) * den_S; + const double r = (double)fnum / fden; + assert(r >= 0. && r <= 1.0); + return r; + } + return 1.; // area is too dark to contribute meaningfully +} + +double VP8SSIMFromStats(const VP8DistoStats* const stats) { + return SSIMCalculation(stats, kWeightSum); +} + +double VP8SSIMFromStatsClipped(const VP8DistoStats* const stats) { + return SSIMCalculation(stats, stats->w); +} + +static double SSIMGetClipped_C(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2, + int xo, int yo, int W, int H) { + VP8DistoStats stats = { 0, 0, 0, 0, 0, 0 }; + const int ymin = (yo - VP8_SSIM_KERNEL < 0) ? 0 : yo - VP8_SSIM_KERNEL; + const int ymax = (yo + VP8_SSIM_KERNEL > H - 1) ? H - 1 + : yo + VP8_SSIM_KERNEL; + const int xmin = (xo - VP8_SSIM_KERNEL < 0) ? 0 : xo - VP8_SSIM_KERNEL; + const int xmax = (xo + VP8_SSIM_KERNEL > W - 1) ? W - 1 + : xo + VP8_SSIM_KERNEL; + int x, y; + src1 += ymin * stride1; + src2 += ymin * stride2; + for (y = ymin; y <= ymax; ++y, src1 += stride1, src2 += stride2) { + for (x = xmin; x <= xmax; ++x) { + const uint32_t w = kWeight[VP8_SSIM_KERNEL + x - xo] + * kWeight[VP8_SSIM_KERNEL + y - yo]; + const uint32_t s1 = src1[x]; + const uint32_t s2 = src2[x]; + stats.w += w; + stats.xm += w * s1; + stats.ym += w * s2; + stats.xxm += w * s1 * s1; + stats.xym += w * s1 * s2; + stats.yym += w * s2 * s2; + } + } + return VP8SSIMFromStatsClipped(&stats); +} + +static double SSIMGet_C(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2) { + VP8DistoStats stats = { 0, 0, 0, 0, 0, 0 }; + int x, y; + for (y = 0; y <= 2 * VP8_SSIM_KERNEL; ++y, src1 += stride1, src2 += stride2) { + for (x = 0; x <= 2 * VP8_SSIM_KERNEL; ++x) { + const uint32_t w = kWeight[x] * kWeight[y]; + const uint32_t s1 = src1[x]; + const uint32_t s2 = src2[x]; + stats.xm += w * s1; + stats.ym += w * s2; + stats.xxm += w * s1 * s1; + stats.xym += w * s1 * s2; + stats.yym += w * s2 * s2; + } + } + return VP8SSIMFromStats(&stats); +} + +//------------------------------------------------------------------------------ + +static uint32_t AccumulateSSE(const uint8_t* src1, + const uint8_t* src2, int len) { + int i; + uint32_t sse2 = 0; + assert(len <= 65535); // to ensure that accumulation fits within uint32_t + for (i = 0; i < len; ++i) { + const int32_t diff = src1[i] - src2[i]; + sse2 += diff * diff; + } + return sse2; +} + +//------------------------------------------------------------------------------ + +VP8SSIMGetFunc VP8SSIMGet; +VP8SSIMGetClippedFunc VP8SSIMGetClipped; +VP8AccumulateSSEFunc VP8AccumulateSSE; + +extern void VP8SSIMDspInitSSE2(void); + +static volatile VP8CPUInfo ssim_last_cpuinfo_used = + (VP8CPUInfo)&ssim_last_cpuinfo_used; + +WEBP_TSAN_IGNORE_FUNCTION void VP8SSIMDspInit(void) { + if (ssim_last_cpuinfo_used == VP8GetCPUInfo) return; + + VP8SSIMGetClipped = SSIMGetClipped_C; + VP8SSIMGet = SSIMGet_C; + + VP8AccumulateSSE = AccumulateSSE; + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8SSIMDspInitSSE2(); + } +#endif + } + + ssim_last_cpuinfo_used = VP8GetCPUInfo; +} //------------------------------------------------------------------------------ // Initialization @@ -674,7 +832,7 @@ static void Copy4x4(const uint8_t* src, uint8_t* dst) { Copy(src, dst, 4); } VP8CHisto VP8CollectHistogram; VP8Idct VP8ITransform; VP8Fdct VP8FTransform; -VP8WHT VP8ITransformWHT; +VP8Fdct VP8FTransform2; VP8WHT VP8FTransformWHT; VP8Intra4Preds VP8EncPredLuma4; VP8IntraPreds VP8EncPredLuma16; @@ -685,20 +843,35 @@ VP8Metric VP8SSE16x8; VP8Metric VP8SSE4x4; VP8WMetric VP8TDisto4x4; VP8WMetric VP8TDisto16x16; +VP8MeanMetric VP8Mean16x4; VP8QuantizeBlock VP8EncQuantizeBlock; +VP8Quantize2Blocks VP8EncQuantize2Blocks; +VP8QuantizeBlockWHT VP8EncQuantizeBlockWHT; VP8BlockCopy VP8Copy4x4; +VP8BlockCopy VP8Copy16x8; extern void VP8EncDspInitSSE2(void); +extern void VP8EncDspInitSSE41(void); +extern void VP8EncDspInitAVX2(void); extern void VP8EncDspInitNEON(void); +extern void VP8EncDspInitMIPS32(void); +extern void VP8EncDspInitMIPSdspR2(void); +extern void VP8EncDspInitMSA(void); -void VP8EncDspInit(void) { +static volatile VP8CPUInfo enc_last_cpuinfo_used = + (VP8CPUInfo)&enc_last_cpuinfo_used; + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInit(void) { + if (enc_last_cpuinfo_used == VP8GetCPUInfo) return; + + VP8DspInit(); // common inverse transforms InitTables(); // default C implementations VP8CollectHistogram = CollectHistogram; VP8ITransform = ITransform; VP8FTransform = FTransform; - VP8ITransformWHT = ITransformWHT; + VP8FTransform2 = FTransform2; VP8FTransformWHT = FTransformWHT; VP8EncPredLuma4 = Intra4Preds; VP8EncPredLuma16 = Intra16Preds; @@ -709,23 +882,50 @@ void VP8EncDspInit(void) { VP8SSE4x4 = SSE4x4; VP8TDisto4x4 = Disto4x4; VP8TDisto16x16 = Disto16x16; + VP8Mean16x4 = Mean16x4; VP8EncQuantizeBlock = QuantizeBlock; + VP8EncQuantize2Blocks = Quantize2Blocks; + VP8EncQuantizeBlockWHT = QuantizeBlock; VP8Copy4x4 = Copy4x4; + VP8Copy16x8 = Copy16x8; // If defined, use CPUInfo() to overwrite some pointers with faster versions. - if (VP8GetCPUInfo) { + if (VP8GetCPUInfo != NULL) { #if defined(WEBP_USE_SSE2) if (VP8GetCPUInfo(kSSE2)) { VP8EncDspInitSSE2(); +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + VP8EncDspInitSSE41(); + } +#endif } -#elif defined(WEBP_USE_NEON) +#endif +#if defined(WEBP_USE_AVX2) + if (VP8GetCPUInfo(kAVX2)) { + VP8EncDspInitAVX2(); + } +#endif +#if defined(WEBP_USE_NEON) if (VP8GetCPUInfo(kNEON)) { VP8EncDspInitNEON(); } #endif - } -} - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + VP8EncDspInitMIPS32(); + } #endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8EncDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8EncDspInitMSA(); + } +#endif + } + enc_last_cpuinfo_used = VP8GetCPUInfo; +} diff --git a/3rdparty/libwebp/dsp/enc_avx2.c b/3rdparty/libwebp/dsp/enc_avx2.c new file mode 100644 index 0000000000..93efb30b10 --- /dev/null +++ b/3rdparty/libwebp/dsp/enc_avx2.c @@ -0,0 +1,21 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// AVX2 version of speed-critical encoding functions. + +#include "./dsp.h" + +#if defined(WEBP_USE_AVX2) + +#endif // WEBP_USE_AVX2 + +//------------------------------------------------------------------------------ +// Entry point + +WEBP_DSP_INIT_STUB(VP8EncDspInitAVX2) diff --git a/3rdparty/libwebp/dsp/enc_mips32.c b/3rdparty/libwebp/dsp/enc_mips32.c new file mode 100644 index 0000000000..752b14daf6 --- /dev/null +++ b/3rdparty/libwebp/dsp/enc_mips32.c @@ -0,0 +1,672 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of speed-critical encoding functions. +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) +// Slobodan Prijic (slobodan.prijic@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS32) + +#include "./mips_macro.h" +#include "../enc/vp8i_enc.h" +#include "../enc/cost_enc.h" + +static const int kC1 = 20091 + (1 << 16); +static const int kC2 = 35468; + +// macro for one vertical pass in ITransformOne +// MUL macro inlined +// temp0..temp15 holds tmp[0]..tmp[15] +// A..D - offsets in bytes to load from in buffer +// TEMP0..TEMP3 - registers for corresponding tmp elements +// TEMP4..TEMP5 - temporary registers +#define VERTICAL_PASS(A, B, C, D, TEMP4, TEMP0, TEMP1, TEMP2, TEMP3) \ + "lh %[temp16], " #A "(%[temp20]) \n\t" \ + "lh %[temp18], " #B "(%[temp20]) \n\t" \ + "lh %[temp17], " #C "(%[temp20]) \n\t" \ + "lh %[temp19], " #D "(%[temp20]) \n\t" \ + "addu %[" #TEMP4 "], %[temp16], %[temp18] \n\t" \ + "subu %[temp16], %[temp16], %[temp18] \n\t" \ + "mul %[" #TEMP0 "], %[temp17], %[kC2] \n\t" \ + "mul %[temp18], %[temp19], %[kC1] \n\t" \ + "mul %[temp17], %[temp17], %[kC1] \n\t" \ + "mul %[temp19], %[temp19], %[kC2] \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 16 \n\n" \ + "sra %[temp18], %[temp18], 16 \n\n" \ + "sra %[temp17], %[temp17], 16 \n\n" \ + "sra %[temp19], %[temp19], 16 \n\n" \ + "subu %[" #TEMP2 "], %[" #TEMP0 "], %[temp18] \n\t" \ + "addu %[" #TEMP3 "], %[temp17], %[temp19] \n\t" \ + "addu %[" #TEMP0 "], %[" #TEMP4 "], %[" #TEMP3 "] \n\t" \ + "addu %[" #TEMP1 "], %[temp16], %[" #TEMP2 "] \n\t" \ + "subu %[" #TEMP2 "], %[temp16], %[" #TEMP2 "] \n\t" \ + "subu %[" #TEMP3 "], %[" #TEMP4 "], %[" #TEMP3 "] \n\t" + +// macro for one horizontal pass in ITransformOne +// MUL and STORE macros inlined +// a = clip_8b(a) is replaced with: a = max(a, 0); a = min(a, 255) +// temp0..temp15 holds tmp[0]..tmp[15] +// A - offset in bytes to load from ref and store to dst buffer +// TEMP0, TEMP4, TEMP8 and TEMP12 - registers for corresponding tmp elements +#define HORIZONTAL_PASS(A, TEMP0, TEMP4, TEMP8, TEMP12) \ + "addiu %[" #TEMP0 "], %[" #TEMP0 "], 4 \n\t" \ + "addu %[temp16], %[" #TEMP0 "], %[" #TEMP8 "] \n\t" \ + "subu %[temp17], %[" #TEMP0 "], %[" #TEMP8 "] \n\t" \ + "mul %[" #TEMP0 "], %[" #TEMP4 "], %[kC2] \n\t" \ + "mul %[" #TEMP8 "], %[" #TEMP12 "], %[kC1] \n\t" \ + "mul %[" #TEMP4 "], %[" #TEMP4 "], %[kC1] \n\t" \ + "mul %[" #TEMP12 "], %[" #TEMP12 "], %[kC2] \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 16 \n\t" \ + "sra %[" #TEMP8 "], %[" #TEMP8 "], 16 \n\t" \ + "sra %[" #TEMP4 "], %[" #TEMP4 "], 16 \n\t" \ + "sra %[" #TEMP12 "], %[" #TEMP12 "], 16 \n\t" \ + "subu %[temp18], %[" #TEMP0 "], %[" #TEMP8 "] \n\t" \ + "addu %[temp19], %[" #TEMP4 "], %[" #TEMP12 "] \n\t" \ + "addu %[" #TEMP0 "], %[temp16], %[temp19] \n\t" \ + "addu %[" #TEMP4 "], %[temp17], %[temp18] \n\t" \ + "subu %[" #TEMP8 "], %[temp17], %[temp18] \n\t" \ + "subu %[" #TEMP12 "], %[temp16], %[temp19] \n\t" \ + "lw %[temp20], 0(%[args]) \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 3 \n\t" \ + "sra %[" #TEMP4 "], %[" #TEMP4 "], 3 \n\t" \ + "sra %[" #TEMP8 "], %[" #TEMP8 "], 3 \n\t" \ + "sra %[" #TEMP12 "], %[" #TEMP12 "], 3 \n\t" \ + "lbu %[temp16], 0+" XSTR(BPS) "*" #A "(%[temp20]) \n\t" \ + "lbu %[temp17], 1+" XSTR(BPS) "*" #A "(%[temp20]) \n\t" \ + "lbu %[temp18], 2+" XSTR(BPS) "*" #A "(%[temp20]) \n\t" \ + "lbu %[temp19], 3+" XSTR(BPS) "*" #A "(%[temp20]) \n\t" \ + "addu %[" #TEMP0 "], %[temp16], %[" #TEMP0 "] \n\t" \ + "addu %[" #TEMP4 "], %[temp17], %[" #TEMP4 "] \n\t" \ + "addu %[" #TEMP8 "], %[temp18], %[" #TEMP8 "] \n\t" \ + "addu %[" #TEMP12 "], %[temp19], %[" #TEMP12 "] \n\t" \ + "slt %[temp16], %[" #TEMP0 "], $zero \n\t" \ + "slt %[temp17], %[" #TEMP4 "], $zero \n\t" \ + "slt %[temp18], %[" #TEMP8 "], $zero \n\t" \ + "slt %[temp19], %[" #TEMP12 "], $zero \n\t" \ + "movn %[" #TEMP0 "], $zero, %[temp16] \n\t" \ + "movn %[" #TEMP4 "], $zero, %[temp17] \n\t" \ + "movn %[" #TEMP8 "], $zero, %[temp18] \n\t" \ + "movn %[" #TEMP12 "], $zero, %[temp19] \n\t" \ + "addiu %[temp20], $zero, 255 \n\t" \ + "slt %[temp16], %[" #TEMP0 "], %[temp20] \n\t" \ + "slt %[temp17], %[" #TEMP4 "], %[temp20] \n\t" \ + "slt %[temp18], %[" #TEMP8 "], %[temp20] \n\t" \ + "slt %[temp19], %[" #TEMP12 "], %[temp20] \n\t" \ + "movz %[" #TEMP0 "], %[temp20], %[temp16] \n\t" \ + "movz %[" #TEMP4 "], %[temp20], %[temp17] \n\t" \ + "lw %[temp16], 8(%[args]) \n\t" \ + "movz %[" #TEMP8 "], %[temp20], %[temp18] \n\t" \ + "movz %[" #TEMP12 "], %[temp20], %[temp19] \n\t" \ + "sb %[" #TEMP0 "], 0+" XSTR(BPS) "*" #A "(%[temp16]) \n\t" \ + "sb %[" #TEMP4 "], 1+" XSTR(BPS) "*" #A "(%[temp16]) \n\t" \ + "sb %[" #TEMP8 "], 2+" XSTR(BPS) "*" #A "(%[temp16]) \n\t" \ + "sb %[" #TEMP12 "], 3+" XSTR(BPS) "*" #A "(%[temp16]) \n\t" + +// Does one or two inverse transforms. +static WEBP_INLINE void ITransformOne(const uint8_t* ref, const int16_t* in, + uint8_t* dst) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + int temp7, temp8, temp9, temp10, temp11, temp12, temp13; + int temp14, temp15, temp16, temp17, temp18, temp19, temp20; + const int* args[3] = {(const int*)ref, (const int*)in, (const int*)dst}; + + __asm__ volatile( + "lw %[temp20], 4(%[args]) \n\t" + VERTICAL_PASS(0, 16, 8, 24, temp4, temp0, temp1, temp2, temp3) + VERTICAL_PASS(2, 18, 10, 26, temp8, temp4, temp5, temp6, temp7) + VERTICAL_PASS(4, 20, 12, 28, temp12, temp8, temp9, temp10, temp11) + VERTICAL_PASS(6, 22, 14, 30, temp20, temp12, temp13, temp14, temp15) + + HORIZONTAL_PASS(0, temp0, temp4, temp8, temp12) + HORIZONTAL_PASS(1, temp1, temp5, temp9, temp13) + HORIZONTAL_PASS(2, temp2, temp6, temp10, temp14) + HORIZONTAL_PASS(3, temp3, temp7, temp11, temp15) + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11), + [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), [temp14]"=&r"(temp14), + [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), [temp17]"=&r"(temp17), + [temp18]"=&r"(temp18), [temp19]"=&r"(temp19), [temp20]"=&r"(temp20) + : [args]"r"(args), [kC1]"r"(kC1), [kC2]"r"(kC2) + : "memory", "hi", "lo" + ); +} + +static void ITransform(const uint8_t* ref, const int16_t* in, + uint8_t* dst, int do_two) { + ITransformOne(ref, in, dst); + if (do_two) { + ITransformOne(ref + 4, in + 16, dst + 4); + } +} + +#undef VERTICAL_PASS +#undef HORIZONTAL_PASS + +// macro for one pass through for loop in QuantizeBlock +// QUANTDIV macro inlined +// J - offset in bytes (kZigzag[n] * 2) +// K - offset in bytes (kZigzag[n] * 4) +// N - offset in bytes (n * 2) +#define QUANTIZE_ONE(J, K, N) \ + "lh %[temp0], " #J "(%[ppin]) \n\t" \ + "lhu %[temp1], " #J "(%[ppsharpen]) \n\t" \ + "lw %[temp2], " #K "(%[ppzthresh]) \n\t" \ + "sra %[sign], %[temp0], 15 \n\t" \ + "xor %[coeff], %[temp0], %[sign] \n\t" \ + "subu %[coeff], %[coeff], %[sign] \n\t" \ + "addu %[coeff], %[coeff], %[temp1] \n\t" \ + "slt %[temp4], %[temp2], %[coeff] \n\t" \ + "addiu %[temp5], $zero, 0 \n\t" \ + "addiu %[level], $zero, 0 \n\t" \ + "beqz %[temp4], 2f \n\t" \ + "lhu %[temp1], " #J "(%[ppiq]) \n\t" \ + "lw %[temp2], " #K "(%[ppbias]) \n\t" \ + "lhu %[temp3], " #J "(%[ppq]) \n\t" \ + "mul %[level], %[coeff], %[temp1] \n\t" \ + "addu %[level], %[level], %[temp2] \n\t" \ + "sra %[level], %[level], 17 \n\t" \ + "slt %[temp4], %[max_level], %[level] \n\t" \ + "movn %[level], %[max_level], %[temp4] \n\t" \ + "xor %[level], %[level], %[sign] \n\t" \ + "subu %[level], %[level], %[sign] \n\t" \ + "mul %[temp5], %[level], %[temp3] \n\t" \ +"2: \n\t" \ + "sh %[temp5], " #J "(%[ppin]) \n\t" \ + "sh %[level], " #N "(%[pout]) \n\t" + +static int QuantizeBlock(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + int temp0, temp1, temp2, temp3, temp4, temp5; + int sign, coeff, level, i; + int max_level = MAX_LEVEL; + + int16_t* ppin = &in[0]; + int16_t* pout = &out[0]; + const uint16_t* ppsharpen = &mtx->sharpen_[0]; + const uint32_t* ppzthresh = &mtx->zthresh_[0]; + const uint16_t* ppq = &mtx->q_[0]; + const uint16_t* ppiq = &mtx->iq_[0]; + const uint32_t* ppbias = &mtx->bias_[0]; + + __asm__ volatile( + QUANTIZE_ONE( 0, 0, 0) + QUANTIZE_ONE( 2, 4, 2) + QUANTIZE_ONE( 8, 16, 4) + QUANTIZE_ONE(16, 32, 6) + QUANTIZE_ONE(10, 20, 8) + QUANTIZE_ONE( 4, 8, 10) + QUANTIZE_ONE( 6, 12, 12) + QUANTIZE_ONE(12, 24, 14) + QUANTIZE_ONE(18, 36, 16) + QUANTIZE_ONE(24, 48, 18) + QUANTIZE_ONE(26, 52, 20) + QUANTIZE_ONE(20, 40, 22) + QUANTIZE_ONE(14, 28, 24) + QUANTIZE_ONE(22, 44, 26) + QUANTIZE_ONE(28, 56, 28) + QUANTIZE_ONE(30, 60, 30) + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [sign]"=&r"(sign), [coeff]"=&r"(coeff), + [level]"=&r"(level) + : [pout]"r"(pout), [ppin]"r"(ppin), + [ppiq]"r"(ppiq), [max_level]"r"(max_level), + [ppbias]"r"(ppbias), [ppzthresh]"r"(ppzthresh), + [ppsharpen]"r"(ppsharpen), [ppq]"r"(ppq) + : "memory", "hi", "lo" + ); + + // moved out from macro to increase possibility for earlier breaking + for (i = 15; i >= 0; i--) { + if (out[i]) return 1; + } + return 0; +} + +static int Quantize2Blocks(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = QuantizeBlock(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= QuantizeBlock(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} + +#undef QUANTIZE_ONE + +// macro for one horizontal pass in Disto4x4 (TTransform) +// two calls of function TTransform are merged into single one +// A - offset in bytes to load from a and b buffers +// E..H - offsets in bytes to store first results to tmp buffer +// E1..H1 - offsets in bytes to store second results to tmp buffer +#define HORIZONTAL_PASS(A, E, F, G, H, E1, F1, G1, H1) \ + "lbu %[temp0], 0+" XSTR(BPS) "*" #A "(%[a]) \n\t" \ + "lbu %[temp1], 1+" XSTR(BPS) "*" #A "(%[a]) \n\t" \ + "lbu %[temp2], 2+" XSTR(BPS) "*" #A "(%[a]) \n\t" \ + "lbu %[temp3], 3+" XSTR(BPS) "*" #A "(%[a]) \n\t" \ + "lbu %[temp4], 0+" XSTR(BPS) "*" #A "(%[b]) \n\t" \ + "lbu %[temp5], 1+" XSTR(BPS) "*" #A "(%[b]) \n\t" \ + "lbu %[temp6], 2+" XSTR(BPS) "*" #A "(%[b]) \n\t" \ + "lbu %[temp7], 3+" XSTR(BPS) "*" #A "(%[b]) \n\t" \ + "addu %[temp8], %[temp0], %[temp2] \n\t" \ + "subu %[temp0], %[temp0], %[temp2] \n\t" \ + "addu %[temp2], %[temp1], %[temp3] \n\t" \ + "subu %[temp1], %[temp1], %[temp3] \n\t" \ + "addu %[temp3], %[temp4], %[temp6] \n\t" \ + "subu %[temp4], %[temp4], %[temp6] \n\t" \ + "addu %[temp6], %[temp5], %[temp7] \n\t" \ + "subu %[temp5], %[temp5], %[temp7] \n\t" \ + "addu %[temp7], %[temp8], %[temp2] \n\t" \ + "subu %[temp2], %[temp8], %[temp2] \n\t" \ + "addu %[temp8], %[temp0], %[temp1] \n\t" \ + "subu %[temp0], %[temp0], %[temp1] \n\t" \ + "addu %[temp1], %[temp3], %[temp6] \n\t" \ + "subu %[temp3], %[temp3], %[temp6] \n\t" \ + "addu %[temp6], %[temp4], %[temp5] \n\t" \ + "subu %[temp4], %[temp4], %[temp5] \n\t" \ + "sw %[temp7], " #E "(%[tmp]) \n\t" \ + "sw %[temp2], " #H "(%[tmp]) \n\t" \ + "sw %[temp8], " #F "(%[tmp]) \n\t" \ + "sw %[temp0], " #G "(%[tmp]) \n\t" \ + "sw %[temp1], " #E1 "(%[tmp]) \n\t" \ + "sw %[temp3], " #H1 "(%[tmp]) \n\t" \ + "sw %[temp6], " #F1 "(%[tmp]) \n\t" \ + "sw %[temp4], " #G1 "(%[tmp]) \n\t" + +// macro for one vertical pass in Disto4x4 (TTransform) +// two calls of function TTransform are merged into single one +// since only one accu is available in mips32r1 instruction set +// first is done second call of function TTransform and after +// that first one. +// const int sum1 = TTransform(a, w); +// const int sum2 = TTransform(b, w); +// return abs(sum2 - sum1) >> 5; +// (sum2 - sum1) is calculated with madds (sub2) and msubs (sub1) +// A..D - offsets in bytes to load first results from tmp buffer +// A1..D1 - offsets in bytes to load second results from tmp buffer +// E..H - offsets in bytes to load from w buffer +#define VERTICAL_PASS(A, B, C, D, A1, B1, C1, D1, E, F, G, H) \ + "lw %[temp0], " #A1 "(%[tmp]) \n\t" \ + "lw %[temp1], " #C1 "(%[tmp]) \n\t" \ + "lw %[temp2], " #B1 "(%[tmp]) \n\t" \ + "lw %[temp3], " #D1 "(%[tmp]) \n\t" \ + "addu %[temp8], %[temp0], %[temp1] \n\t" \ + "subu %[temp0], %[temp0], %[temp1] \n\t" \ + "addu %[temp1], %[temp2], %[temp3] \n\t" \ + "subu %[temp2], %[temp2], %[temp3] \n\t" \ + "addu %[temp3], %[temp8], %[temp1] \n\t" \ + "subu %[temp8], %[temp8], %[temp1] \n\t" \ + "addu %[temp1], %[temp0], %[temp2] \n\t" \ + "subu %[temp0], %[temp0], %[temp2] \n\t" \ + "sra %[temp4], %[temp3], 31 \n\t" \ + "sra %[temp5], %[temp1], 31 \n\t" \ + "sra %[temp6], %[temp0], 31 \n\t" \ + "sra %[temp7], %[temp8], 31 \n\t" \ + "xor %[temp3], %[temp3], %[temp4] \n\t" \ + "xor %[temp1], %[temp1], %[temp5] \n\t" \ + "xor %[temp0], %[temp0], %[temp6] \n\t" \ + "xor %[temp8], %[temp8], %[temp7] \n\t" \ + "subu %[temp3], %[temp3], %[temp4] \n\t" \ + "subu %[temp1], %[temp1], %[temp5] \n\t" \ + "subu %[temp0], %[temp0], %[temp6] \n\t" \ + "subu %[temp8], %[temp8], %[temp7] \n\t" \ + "lhu %[temp4], " #E "(%[w]) \n\t" \ + "lhu %[temp5], " #F "(%[w]) \n\t" \ + "lhu %[temp6], " #G "(%[w]) \n\t" \ + "lhu %[temp7], " #H "(%[w]) \n\t" \ + "madd %[temp4], %[temp3] \n\t" \ + "madd %[temp5], %[temp1] \n\t" \ + "madd %[temp6], %[temp0] \n\t" \ + "madd %[temp7], %[temp8] \n\t" \ + "lw %[temp0], " #A "(%[tmp]) \n\t" \ + "lw %[temp1], " #C "(%[tmp]) \n\t" \ + "lw %[temp2], " #B "(%[tmp]) \n\t" \ + "lw %[temp3], " #D "(%[tmp]) \n\t" \ + "addu %[temp8], %[temp0], %[temp1] \n\t" \ + "subu %[temp0], %[temp0], %[temp1] \n\t" \ + "addu %[temp1], %[temp2], %[temp3] \n\t" \ + "subu %[temp2], %[temp2], %[temp3] \n\t" \ + "addu %[temp3], %[temp8], %[temp1] \n\t" \ + "subu %[temp1], %[temp8], %[temp1] \n\t" \ + "addu %[temp8], %[temp0], %[temp2] \n\t" \ + "subu %[temp0], %[temp0], %[temp2] \n\t" \ + "sra %[temp2], %[temp3], 31 \n\t" \ + "xor %[temp3], %[temp3], %[temp2] \n\t" \ + "subu %[temp3], %[temp3], %[temp2] \n\t" \ + "msub %[temp4], %[temp3] \n\t" \ + "sra %[temp2], %[temp8], 31 \n\t" \ + "sra %[temp3], %[temp0], 31 \n\t" \ + "sra %[temp4], %[temp1], 31 \n\t" \ + "xor %[temp8], %[temp8], %[temp2] \n\t" \ + "xor %[temp0], %[temp0], %[temp3] \n\t" \ + "xor %[temp1], %[temp1], %[temp4] \n\t" \ + "subu %[temp8], %[temp8], %[temp2] \n\t" \ + "subu %[temp0], %[temp0], %[temp3] \n\t" \ + "subu %[temp1], %[temp1], %[temp4] \n\t" \ + "msub %[temp5], %[temp8] \n\t" \ + "msub %[temp6], %[temp0] \n\t" \ + "msub %[temp7], %[temp1] \n\t" + +static int Disto4x4(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int tmp[32]; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + + __asm__ volatile( + HORIZONTAL_PASS(0, 0, 4, 8, 12, 64, 68, 72, 76) + HORIZONTAL_PASS(1, 16, 20, 24, 28, 80, 84, 88, 92) + HORIZONTAL_PASS(2, 32, 36, 40, 44, 96, 100, 104, 108) + HORIZONTAL_PASS(3, 48, 52, 56, 60, 112, 116, 120, 124) + "mthi $zero \n\t" + "mtlo $zero \n\t" + VERTICAL_PASS( 0, 16, 32, 48, 64, 80, 96, 112, 0, 8, 16, 24) + VERTICAL_PASS( 4, 20, 36, 52, 68, 84, 100, 116, 2, 10, 18, 26) + VERTICAL_PASS( 8, 24, 40, 56, 72, 88, 104, 120, 4, 12, 20, 28) + VERTICAL_PASS(12, 28, 44, 60, 76, 92, 108, 124, 6, 14, 22, 30) + "mflo %[temp0] \n\t" + "sra %[temp1], %[temp0], 31 \n\t" + "xor %[temp0], %[temp0], %[temp1] \n\t" + "subu %[temp0], %[temp0], %[temp1] \n\t" + "sra %[temp0], %[temp0], 5 \n\t" + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8) + : [a]"r"(a), [b]"r"(b), [w]"r"(w), [tmp]"r"(tmp) + : "memory", "hi", "lo" + ); + + return temp0; +} + +#undef VERTICAL_PASS +#undef HORIZONTAL_PASS + +static int Disto16x16(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4(a + x + y, b + x + y, w); + } + } + return D; +} + +// macro for one horizontal pass in FTransform +// temp0..temp15 holds tmp[0]..tmp[15] +// A - offset in bytes to load from src and ref buffers +// TEMP0..TEMP3 - registers for corresponding tmp elements +#define HORIZONTAL_PASS(A, TEMP0, TEMP1, TEMP2, TEMP3) \ + "lw %[" #TEMP1 "], 0(%[args]) \n\t" \ + "lw %[" #TEMP2 "], 4(%[args]) \n\t" \ + "lbu %[temp16], 0+" XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "lbu %[temp17], 0+" XSTR(BPS) "*" #A "(%[" #TEMP2 "]) \n\t" \ + "lbu %[temp18], 1+" XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "lbu %[temp19], 1+" XSTR(BPS) "*" #A "(%[" #TEMP2 "]) \n\t" \ + "subu %[temp20], %[temp16], %[temp17] \n\t" \ + "lbu %[temp16], 2+" XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "lbu %[temp17], 2+" XSTR(BPS) "*" #A "(%[" #TEMP2 "]) \n\t" \ + "subu %[" #TEMP0 "], %[temp18], %[temp19] \n\t" \ + "lbu %[temp18], 3+" XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "lbu %[temp19], 3+" XSTR(BPS) "*" #A "(%[" #TEMP2 "]) \n\t" \ + "subu %[" #TEMP1 "], %[temp16], %[temp17] \n\t" \ + "subu %[" #TEMP2 "], %[temp18], %[temp19] \n\t" \ + "addu %[" #TEMP3 "], %[temp20], %[" #TEMP2 "] \n\t" \ + "subu %[" #TEMP2 "], %[temp20], %[" #TEMP2 "] \n\t" \ + "addu %[temp20], %[" #TEMP0 "], %[" #TEMP1 "] \n\t" \ + "subu %[" #TEMP0 "], %[" #TEMP0 "], %[" #TEMP1 "] \n\t" \ + "mul %[temp16], %[" #TEMP2 "], %[c5352] \n\t" \ + "mul %[temp17], %[" #TEMP2 "], %[c2217] \n\t" \ + "mul %[temp18], %[" #TEMP0 "], %[c5352] \n\t" \ + "mul %[temp19], %[" #TEMP0 "], %[c2217] \n\t" \ + "addu %[" #TEMP1 "], %[" #TEMP3 "], %[temp20] \n\t" \ + "subu %[temp20], %[" #TEMP3 "], %[temp20] \n\t" \ + "sll %[" #TEMP0 "], %[" #TEMP1 "], 3 \n\t" \ + "sll %[" #TEMP2 "], %[temp20], 3 \n\t" \ + "addiu %[temp16], %[temp16], 1812 \n\t" \ + "addiu %[temp17], %[temp17], 937 \n\t" \ + "addu %[temp16], %[temp16], %[temp19] \n\t" \ + "subu %[temp17], %[temp17], %[temp18] \n\t" \ + "sra %[" #TEMP1 "], %[temp16], 9 \n\t" \ + "sra %[" #TEMP3 "], %[temp17], 9 \n\t" + +// macro for one vertical pass in FTransform +// temp0..temp15 holds tmp[0]..tmp[15] +// A..D - offsets in bytes to store to out buffer +// TEMP0, TEMP4, TEMP8 and TEMP12 - registers for corresponding tmp elements +#define VERTICAL_PASS(A, B, C, D, TEMP0, TEMP4, TEMP8, TEMP12) \ + "addu %[temp16], %[" #TEMP0 "], %[" #TEMP12 "] \n\t" \ + "subu %[temp19], %[" #TEMP0 "], %[" #TEMP12 "] \n\t" \ + "addu %[temp17], %[" #TEMP4 "], %[" #TEMP8 "] \n\t" \ + "subu %[temp18], %[" #TEMP4 "], %[" #TEMP8 "] \n\t" \ + "mul %[" #TEMP8 "], %[temp19], %[c2217] \n\t" \ + "mul %[" #TEMP12 "], %[temp18], %[c2217] \n\t" \ + "mul %[" #TEMP4 "], %[temp19], %[c5352] \n\t" \ + "mul %[temp18], %[temp18], %[c5352] \n\t" \ + "addiu %[temp16], %[temp16], 7 \n\t" \ + "addu %[" #TEMP0 "], %[temp16], %[temp17] \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 4 \n\t" \ + "addu %[" #TEMP12 "], %[" #TEMP12 "], %[" #TEMP4 "] \n\t" \ + "subu %[" #TEMP4 "], %[temp16], %[temp17] \n\t" \ + "sra %[" #TEMP4 "], %[" #TEMP4 "], 4 \n\t" \ + "addiu %[" #TEMP8 "], %[" #TEMP8 "], 30000 \n\t" \ + "addiu %[" #TEMP12 "], %[" #TEMP12 "], 12000 \n\t" \ + "addiu %[" #TEMP8 "], %[" #TEMP8 "], 21000 \n\t" \ + "subu %[" #TEMP8 "], %[" #TEMP8 "], %[temp18] \n\t" \ + "sra %[" #TEMP12 "], %[" #TEMP12 "], 16 \n\t" \ + "sra %[" #TEMP8 "], %[" #TEMP8 "], 16 \n\t" \ + "addiu %[temp16], %[" #TEMP12 "], 1 \n\t" \ + "movn %[" #TEMP12 "], %[temp16], %[temp19] \n\t" \ + "sh %[" #TEMP0 "], " #A "(%[temp20]) \n\t" \ + "sh %[" #TEMP4 "], " #C "(%[temp20]) \n\t" \ + "sh %[" #TEMP8 "], " #D "(%[temp20]) \n\t" \ + "sh %[" #TEMP12 "], " #B "(%[temp20]) \n\t" + +static void FTransform(const uint8_t* src, const uint8_t* ref, int16_t* out) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + int temp9, temp10, temp11, temp12, temp13, temp14, temp15, temp16; + int temp17, temp18, temp19, temp20; + const int c2217 = 2217; + const int c5352 = 5352; + const int* const args[3] = + { (const int*)src, (const int*)ref, (const int*)out }; + + __asm__ volatile( + HORIZONTAL_PASS(0, temp0, temp1, temp2, temp3) + HORIZONTAL_PASS(1, temp4, temp5, temp6, temp7) + HORIZONTAL_PASS(2, temp8, temp9, temp10, temp11) + HORIZONTAL_PASS(3, temp12, temp13, temp14, temp15) + "lw %[temp20], 8(%[args]) \n\t" + VERTICAL_PASS(0, 8, 16, 24, temp0, temp4, temp8, temp12) + VERTICAL_PASS(2, 10, 18, 26, temp1, temp5, temp9, temp13) + VERTICAL_PASS(4, 12, 20, 28, temp2, temp6, temp10, temp14) + VERTICAL_PASS(6, 14, 22, 30, temp3, temp7, temp11, temp15) + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11), + [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), [temp14]"=&r"(temp14), + [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), [temp17]"=&r"(temp17), + [temp18]"=&r"(temp18), [temp19]"=&r"(temp19), [temp20]"=&r"(temp20) + : [args]"r"(args), [c2217]"r"(c2217), [c5352]"r"(c5352) + : "memory", "hi", "lo" + ); +} + +#undef VERTICAL_PASS +#undef HORIZONTAL_PASS + +#if !defined(WORK_AROUND_GCC) + +#define GET_SSE_INNER(A, B, C, D) \ + "lbu %[temp0], " #A "(%[a]) \n\t" \ + "lbu %[temp1], " #A "(%[b]) \n\t" \ + "lbu %[temp2], " #B "(%[a]) \n\t" \ + "lbu %[temp3], " #B "(%[b]) \n\t" \ + "lbu %[temp4], " #C "(%[a]) \n\t" \ + "lbu %[temp5], " #C "(%[b]) \n\t" \ + "lbu %[temp6], " #D "(%[a]) \n\t" \ + "lbu %[temp7], " #D "(%[b]) \n\t" \ + "subu %[temp0], %[temp0], %[temp1] \n\t" \ + "subu %[temp2], %[temp2], %[temp3] \n\t" \ + "subu %[temp4], %[temp4], %[temp5] \n\t" \ + "subu %[temp6], %[temp6], %[temp7] \n\t" \ + "madd %[temp0], %[temp0] \n\t" \ + "madd %[temp2], %[temp2] \n\t" \ + "madd %[temp4], %[temp4] \n\t" \ + "madd %[temp6], %[temp6] \n\t" + +#define GET_SSE(A, B, C, D) \ + GET_SSE_INNER(A, A + 1, A + 2, A + 3) \ + GET_SSE_INNER(B, B + 1, B + 2, B + 3) \ + GET_SSE_INNER(C, C + 1, C + 2, C + 3) \ + GET_SSE_INNER(D, D + 1, D + 2, D + 3) + +static int SSE16x16(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + __asm__ volatile( + "mult $zero, $zero \n\t" + + GET_SSE( 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS) + GET_SSE( 1 * BPS, 4 + 1 * BPS, 8 + 1 * BPS, 12 + 1 * BPS) + GET_SSE( 2 * BPS, 4 + 2 * BPS, 8 + 2 * BPS, 12 + 2 * BPS) + GET_SSE( 3 * BPS, 4 + 3 * BPS, 8 + 3 * BPS, 12 + 3 * BPS) + GET_SSE( 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS) + GET_SSE( 5 * BPS, 4 + 5 * BPS, 8 + 5 * BPS, 12 + 5 * BPS) + GET_SSE( 6 * BPS, 4 + 6 * BPS, 8 + 6 * BPS, 12 + 6 * BPS) + GET_SSE( 7 * BPS, 4 + 7 * BPS, 8 + 7 * BPS, 12 + 7 * BPS) + GET_SSE( 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS) + GET_SSE( 9 * BPS, 4 + 9 * BPS, 8 + 9 * BPS, 12 + 9 * BPS) + GET_SSE(10 * BPS, 4 + 10 * BPS, 8 + 10 * BPS, 12 + 10 * BPS) + GET_SSE(11 * BPS, 4 + 11 * BPS, 8 + 11 * BPS, 12 + 11 * BPS) + GET_SSE(12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS) + GET_SSE(13 * BPS, 4 + 13 * BPS, 8 + 13 * BPS, 12 + 13 * BPS) + GET_SSE(14 * BPS, 4 + 14 * BPS, 8 + 14 * BPS, 12 + 14 * BPS) + GET_SSE(15 * BPS, 4 + 15 * BPS, 8 + 15 * BPS, 12 + 15 * BPS) + + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE16x8(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + __asm__ volatile( + "mult $zero, $zero \n\t" + + GET_SSE( 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS) + GET_SSE( 1 * BPS, 4 + 1 * BPS, 8 + 1 * BPS, 12 + 1 * BPS) + GET_SSE( 2 * BPS, 4 + 2 * BPS, 8 + 2 * BPS, 12 + 2 * BPS) + GET_SSE( 3 * BPS, 4 + 3 * BPS, 8 + 3 * BPS, 12 + 3 * BPS) + GET_SSE( 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS) + GET_SSE( 5 * BPS, 4 + 5 * BPS, 8 + 5 * BPS, 12 + 5 * BPS) + GET_SSE( 6 * BPS, 4 + 6 * BPS, 8 + 6 * BPS, 12 + 6 * BPS) + GET_SSE( 7 * BPS, 4 + 7 * BPS, 8 + 7 * BPS, 12 + 7 * BPS) + + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE8x8(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + __asm__ volatile( + "mult $zero, $zero \n\t" + + GET_SSE(0 * BPS, 4 + 0 * BPS, 1 * BPS, 4 + 1 * BPS) + GET_SSE(2 * BPS, 4 + 2 * BPS, 3 * BPS, 4 + 3 * BPS) + GET_SSE(4 * BPS, 4 + 4 * BPS, 5 * BPS, 4 + 5 * BPS) + GET_SSE(6 * BPS, 4 + 6 * BPS, 7 * BPS, 4 + 7 * BPS) + + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE4x4(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + __asm__ volatile( + "mult $zero, $zero \n\t" + + GET_SSE(0 * BPS, 1 * BPS, 2 * BPS, 3 * BPS) + + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +#undef GET_SSE +#undef GET_SSE_INNER + +#endif // !WORK_AROUND_GCC + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitMIPS32(void) { + VP8ITransform = ITransform; + VP8FTransform = FTransform; + VP8EncQuantizeBlock = QuantizeBlock; + VP8EncQuantize2Blocks = Quantize2Blocks; + VP8TDisto4x4 = Disto4x4; + VP8TDisto16x16 = Disto16x16; +#if !defined(WORK_AROUND_GCC) + VP8SSE16x16 = SSE16x16; + VP8SSE8x8 = SSE8x8; + VP8SSE16x8 = SSE16x8; + VP8SSE4x4 = SSE4x4; +#endif +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(VP8EncDspInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/3rdparty/libwebp/dsp/enc_mips_dsp_r2.c b/3rdparty/libwebp/dsp/enc_mips_dsp_r2.c new file mode 100644 index 0000000000..6c8c1c6acd --- /dev/null +++ b/3rdparty/libwebp/dsp/enc_mips_dsp_r2.c @@ -0,0 +1,1510 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of speed-critical encoding functions. +// +// Author(s): Darko Laus (darko.laus@imgtec.com) +// Mirko Raus (mirko.raus@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "./mips_macro.h" +#include "../enc/cost_enc.h" +#include "../enc/vp8i_enc.h" + +static const int kC1 = 20091 + (1 << 16); +static const int kC2 = 35468; + +// O - output +// I - input (macro doesn't change it) +#define ADD_SUB_HALVES_X4(O0, O1, O2, O3, O4, O5, O6, O7, \ + I0, I1, I2, I3, I4, I5, I6, I7) \ + "addq.ph %[" #O0 "], %[" #I0 "], %[" #I1 "] \n\t" \ + "subq.ph %[" #O1 "], %[" #I0 "], %[" #I1 "] \n\t" \ + "addq.ph %[" #O2 "], %[" #I2 "], %[" #I3 "] \n\t" \ + "subq.ph %[" #O3 "], %[" #I2 "], %[" #I3 "] \n\t" \ + "addq.ph %[" #O4 "], %[" #I4 "], %[" #I5 "] \n\t" \ + "subq.ph %[" #O5 "], %[" #I4 "], %[" #I5 "] \n\t" \ + "addq.ph %[" #O6 "], %[" #I6 "], %[" #I7 "] \n\t" \ + "subq.ph %[" #O7 "], %[" #I6 "], %[" #I7 "] \n\t" + +// IO - input/output +#define ABS_X8(IO0, IO1, IO2, IO3, IO4, IO5, IO6, IO7) \ + "absq_s.ph %[" #IO0 "], %[" #IO0 "] \n\t" \ + "absq_s.ph %[" #IO1 "], %[" #IO1 "] \n\t" \ + "absq_s.ph %[" #IO2 "], %[" #IO2 "] \n\t" \ + "absq_s.ph %[" #IO3 "], %[" #IO3 "] \n\t" \ + "absq_s.ph %[" #IO4 "], %[" #IO4 "] \n\t" \ + "absq_s.ph %[" #IO5 "], %[" #IO5 "] \n\t" \ + "absq_s.ph %[" #IO6 "], %[" #IO6 "] \n\t" \ + "absq_s.ph %[" #IO7 "], %[" #IO7 "] \n\t" + +// dpa.w.ph $ac0 temp0 ,temp1 +// $ac += temp0[31..16] * temp1[31..16] + temp0[15..0] * temp1[15..0] +// dpax.w.ph $ac0 temp0 ,temp1 +// $ac += temp0[31..16] * temp1[15..0] + temp0[15..0] * temp1[31..16] +// O - output +// I - input (macro doesn't change it) +#define MUL_HALF(O0, I0, I1, I2, I3, I4, I5, I6, I7, \ + I8, I9, I10, I11, I12, I13, I14, I15) \ + "mult $ac0, $zero, $zero \n\t" \ + "dpa.w.ph $ac0, %[" #I2 "], %[" #I0 "] \n\t" \ + "dpax.w.ph $ac0, %[" #I5 "], %[" #I6 "] \n\t" \ + "dpa.w.ph $ac0, %[" #I8 "], %[" #I9 "] \n\t" \ + "dpax.w.ph $ac0, %[" #I11 "], %[" #I4 "] \n\t" \ + "dpa.w.ph $ac0, %[" #I12 "], %[" #I7 "] \n\t" \ + "dpax.w.ph $ac0, %[" #I13 "], %[" #I1 "] \n\t" \ + "dpa.w.ph $ac0, %[" #I14 "], %[" #I3 "] \n\t" \ + "dpax.w.ph $ac0, %[" #I15 "], %[" #I10 "] \n\t" \ + "mflo %[" #O0 "], $ac0 \n\t" + +#define OUTPUT_EARLY_CLOBBER_REGS_17() \ + OUTPUT_EARLY_CLOBBER_REGS_10(), \ + [temp11]"=&r"(temp11), [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), \ + [temp14]"=&r"(temp14), [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), \ + [temp17]"=&r"(temp17) + +// macro for one horizontal pass in FTransform +// temp0..temp15 holds tmp[0]..tmp[15] +// A - offset in bytes to load from src and ref buffers +// TEMP0..TEMP3 - registers for corresponding tmp elements +#define HORIZONTAL_PASS(A, TEMP0, TEMP1, TEMP2, TEMP3) \ + "lw %[" #TEMP0 "], 0(%[args]) \n\t" \ + "lw %[" #TEMP1 "], 4(%[args]) \n\t" \ + "lw %[" #TEMP2 "], " XSTR(BPS) "*" #A "(%[" #TEMP0 "]) \n\t" \ + "lw %[" #TEMP3 "], " XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "preceu.ph.qbl %[" #TEMP0 "], %[" #TEMP2 "] \n\t" \ + "preceu.ph.qbl %[" #TEMP1 "], %[" #TEMP3 "] \n\t" \ + "preceu.ph.qbr %[" #TEMP2 "], %[" #TEMP2 "] \n\t" \ + "preceu.ph.qbr %[" #TEMP3 "], %[" #TEMP3 "] \n\t" \ + "subq.ph %[" #TEMP0 "], %[" #TEMP0 "], %[" #TEMP1 "] \n\t" \ + "subq.ph %[" #TEMP2 "], %[" #TEMP2 "], %[" #TEMP3 "] \n\t" \ + "rotr %[" #TEMP0 "], %[" #TEMP0 "], 16 \n\t" \ + "addq.ph %[" #TEMP1 "], %[" #TEMP2 "], %[" #TEMP0 "] \n\t" \ + "subq.ph %[" #TEMP3 "], %[" #TEMP2 "], %[" #TEMP0 "] \n\t" \ + "seh %[" #TEMP0 "], %[" #TEMP1 "] \n\t" \ + "sra %[temp16], %[" #TEMP1 "], 16 \n\t" \ + "seh %[temp19], %[" #TEMP3 "] \n\t" \ + "sra %[" #TEMP3 "], %[" #TEMP3 "], 16 \n\t" \ + "subu %[" #TEMP2 "], %[" #TEMP0 "], %[temp16] \n\t" \ + "addu %[" #TEMP0 "], %[" #TEMP0 "], %[temp16] \n\t" \ + "mul %[temp17], %[temp19], %[c2217] \n\t" \ + "mul %[temp18], %[" #TEMP3 "], %[c5352] \n\t" \ + "mul %[" #TEMP1 "], %[temp19], %[c5352] \n\t" \ + "mul %[temp16], %[" #TEMP3 "], %[c2217] \n\t" \ + "sll %[" #TEMP2 "], %[" #TEMP2 "], 3 \n\t" \ + "sll %[" #TEMP0 "], %[" #TEMP0 "], 3 \n\t" \ + "subu %[" #TEMP3 "], %[temp17], %[temp18] \n\t" \ + "addu %[" #TEMP1 "], %[temp16], %[" #TEMP1 "] \n\t" \ + "addiu %[" #TEMP3 "], %[" #TEMP3 "], 937 \n\t" \ + "addiu %[" #TEMP1 "], %[" #TEMP1 "], 1812 \n\t" \ + "sra %[" #TEMP3 "], %[" #TEMP3 "], 9 \n\t" \ + "sra %[" #TEMP1 "], %[" #TEMP1 "], 9 \n\t" + +// macro for one vertical pass in FTransform +// temp0..temp15 holds tmp[0]..tmp[15] +// A..D - offsets in bytes to store to out buffer +// TEMP0, TEMP4, TEMP8 and TEMP12 - registers for corresponding tmp elements +#define VERTICAL_PASS(A, B, C, D, TEMP0, TEMP4, TEMP8, TEMP12) \ + "addu %[temp16], %[" #TEMP0 "], %[" #TEMP12 "] \n\t" \ + "subu %[temp19], %[" #TEMP0 "], %[" #TEMP12 "] \n\t" \ + "addu %[temp17], %[" #TEMP4 "], %[" #TEMP8 "] \n\t" \ + "subu %[temp18], %[" #TEMP4 "], %[" #TEMP8 "] \n\t" \ + "mul %[" #TEMP8 "], %[temp19], %[c2217] \n\t" \ + "mul %[" #TEMP12 "], %[temp18], %[c2217] \n\t" \ + "mul %[" #TEMP4 "], %[temp19], %[c5352] \n\t" \ + "mul %[temp18], %[temp18], %[c5352] \n\t" \ + "addiu %[temp16], %[temp16], 7 \n\t" \ + "addu %[" #TEMP0 "], %[temp16], %[temp17] \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 4 \n\t" \ + "addu %[" #TEMP12 "], %[" #TEMP12 "], %[" #TEMP4 "] \n\t" \ + "subu %[" #TEMP4 "], %[temp16], %[temp17] \n\t" \ + "sra %[" #TEMP4 "], %[" #TEMP4 "], 4 \n\t" \ + "addiu %[" #TEMP8 "], %[" #TEMP8 "], 30000 \n\t" \ + "addiu %[" #TEMP12 "], %[" #TEMP12 "], 12000 \n\t" \ + "addiu %[" #TEMP8 "], %[" #TEMP8 "], 21000 \n\t" \ + "subu %[" #TEMP8 "], %[" #TEMP8 "], %[temp18] \n\t" \ + "sra %[" #TEMP12 "], %[" #TEMP12 "], 16 \n\t" \ + "sra %[" #TEMP8 "], %[" #TEMP8 "], 16 \n\t" \ + "addiu %[temp16], %[" #TEMP12 "], 1 \n\t" \ + "movn %[" #TEMP12 "], %[temp16], %[temp19] \n\t" \ + "sh %[" #TEMP0 "], " #A "(%[temp20]) \n\t" \ + "sh %[" #TEMP4 "], " #C "(%[temp20]) \n\t" \ + "sh %[" #TEMP8 "], " #D "(%[temp20]) \n\t" \ + "sh %[" #TEMP12 "], " #B "(%[temp20]) \n\t" + +static void FTransform(const uint8_t* src, const uint8_t* ref, int16_t* out) { + const int c2217 = 2217; + const int c5352 = 5352; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + int temp9, temp10, temp11, temp12, temp13, temp14, temp15, temp16; + int temp17, temp18, temp19, temp20; + const int* const args[3] = + { (const int*)src, (const int*)ref, (const int*)out }; + + __asm__ volatile ( + HORIZONTAL_PASS(0, temp0, temp1, temp2, temp3) + HORIZONTAL_PASS(1, temp4, temp5, temp6, temp7) + HORIZONTAL_PASS(2, temp8, temp9, temp10, temp11) + HORIZONTAL_PASS(3, temp12, temp13, temp14, temp15) + "lw %[temp20], 8(%[args]) \n\t" + VERTICAL_PASS(0, 8, 16, 24, temp0, temp4, temp8, temp12) + VERTICAL_PASS(2, 10, 18, 26, temp1, temp5, temp9, temp13) + VERTICAL_PASS(4, 12, 20, 28, temp2, temp6, temp10, temp14) + VERTICAL_PASS(6, 14, 22, 30, temp3, temp7, temp11, temp15) + OUTPUT_EARLY_CLOBBER_REGS_18(), + [temp0]"=&r"(temp0), [temp19]"=&r"(temp19), [temp20]"=&r"(temp20) + : [args]"r"(args), [c2217]"r"(c2217), [c5352]"r"(c5352) + : "memory", "hi", "lo" + ); +} + +#undef VERTICAL_PASS +#undef HORIZONTAL_PASS + +static WEBP_INLINE void ITransformOne(const uint8_t* ref, const int16_t* in, + uint8_t* dst) { + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17, temp18; + + __asm__ volatile ( + "ulw %[temp1], 0(%[in]) \n\t" + "ulw %[temp2], 16(%[in]) \n\t" + LOAD_IN_X2(temp5, temp6, 24, 26) + ADD_SUB_HALVES(temp3, temp4, temp1, temp2) + LOAD_IN_X2(temp1, temp2, 8, 10) + MUL_SHIFT_SUM(temp7, temp8, temp9, temp10, temp11, temp12, temp13, temp14, + temp10, temp8, temp9, temp7, temp1, temp2, temp5, temp6, + temp13, temp11, temp14, temp12) + INSERT_HALF_X2(temp8, temp7, temp10, temp9) + "ulw %[temp17], 4(%[in]) \n\t" + "ulw %[temp18], 20(%[in]) \n\t" + ADD_SUB_HALVES(temp1, temp2, temp3, temp8) + ADD_SUB_HALVES(temp5, temp6, temp4, temp7) + ADD_SUB_HALVES(temp7, temp8, temp17, temp18) + LOAD_IN_X2(temp17, temp18, 12, 14) + LOAD_IN_X2(temp9, temp10, 28, 30) + MUL_SHIFT_SUM(temp11, temp12, temp13, temp14, temp15, temp16, temp4, temp17, + temp12, temp14, temp11, temp13, temp17, temp18, temp9, temp10, + temp15, temp4, temp16, temp17) + INSERT_HALF_X2(temp11, temp12, temp13, temp14) + ADD_SUB_HALVES(temp17, temp8, temp8, temp11) + ADD_SUB_HALVES(temp3, temp4, temp7, temp12) + + // horizontal + SRA_16(temp9, temp10, temp11, temp12, temp1, temp2, temp5, temp6) + INSERT_HALF_X2(temp1, temp6, temp5, temp2) + SRA_16(temp13, temp14, temp15, temp16, temp3, temp4, temp17, temp8) + "repl.ph %[temp2], 0x4 \n\t" + INSERT_HALF_X2(temp3, temp8, temp17, temp4) + "addq.ph %[temp1], %[temp1], %[temp2] \n\t" + "addq.ph %[temp6], %[temp6], %[temp2] \n\t" + ADD_SUB_HALVES(temp2, temp4, temp1, temp3) + ADD_SUB_HALVES(temp5, temp7, temp6, temp8) + MUL_SHIFT_SUM(temp1, temp3, temp6, temp8, temp9, temp13, temp17, temp18, + temp3, temp13, temp1, temp9, temp9, temp13, temp11, temp15, + temp6, temp17, temp8, temp18) + MUL_SHIFT_SUM(temp6, temp8, temp18, temp17, temp11, temp15, temp12, temp16, + temp8, temp15, temp6, temp11, temp12, temp16, temp10, temp14, + temp18, temp12, temp17, temp16) + INSERT_HALF_X2(temp1, temp3, temp9, temp13) + INSERT_HALF_X2(temp6, temp8, temp11, temp15) + SHIFT_R_SUM_X2(temp9, temp10, temp11, temp12, temp13, temp14, temp15, + temp16, temp2, temp4, temp5, temp7, temp3, temp1, temp8, + temp6) + PACK_2_HALVES_TO_WORD(temp1, temp2, temp3, temp4, temp9, temp12, temp13, + temp16, temp11, temp10, temp15, temp14) + LOAD_WITH_OFFSET_X4(temp10, temp11, temp14, temp15, ref, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp5, temp6, temp7, temp8, temp17, temp18, temp10, + temp11, temp10, temp11, temp14, temp15) + STORE_SAT_SUM_X2(temp5, temp6, temp7, temp8, temp17, temp18, temp10, temp11, + temp9, temp12, temp1, temp2, temp13, temp16, temp3, temp4, + dst, 0, 1, 2, 3, BPS) + + OUTPUT_EARLY_CLOBBER_REGS_18() + : [dst]"r"(dst), [in]"r"(in), [kC1]"r"(kC1), [kC2]"r"(kC2), [ref]"r"(ref) + : "memory", "hi", "lo" + ); +} + +static void ITransform(const uint8_t* ref, const int16_t* in, uint8_t* dst, + int do_two) { + ITransformOne(ref, in, dst); + if (do_two) { + ITransformOne(ref + 4, in + 16, dst + 4); + } +} + +static int Disto4x4(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17; + + __asm__ volatile ( + LOAD_WITH_OFFSET_X4(temp1, temp2, temp3, temp4, a, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp5, temp6, temp7, temp8, temp9,temp10, temp11, + temp12, temp1, temp2, temp3, temp4) + ADD_SUB_HALVES_X4(temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, + temp5, temp6, temp7, temp8, temp9, temp10, temp11, temp12) + PACK_2_HALVES_TO_WORD(temp9, temp10, temp11, temp12, temp1, temp3, temp5, + temp7, temp2, temp4, temp6, temp8) + ADD_SUB_HALVES_X4(temp2, temp4, temp6, temp8, temp9, temp1, temp3, temp10, + temp1, temp9, temp3, temp10, temp5, temp11, temp7, temp12) + ADD_SUB_HALVES_X4(temp5, temp11, temp7, temp2, temp9, temp3, temp6, temp12, + temp2, temp9, temp6, temp3, temp4, temp1, temp8, temp10) + ADD_SUB_HALVES_X4(temp1, temp4, temp10, temp8, temp7, temp11, temp5, temp2, + temp5, temp7, temp11, temp2, temp9, temp6, temp3, temp12) + ABS_X8(temp1, temp4, temp10, temp8, temp7, temp11, temp5, temp2) + LOAD_WITH_OFFSET_X4(temp3, temp6, temp9, temp12, w, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + LOAD_WITH_OFFSET_X4(temp13, temp14, temp15, temp16, w, + 0, 4, 8, 12, + 1, 1, 1, 1, + 16) + MUL_HALF(temp17, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, + temp9, temp10, temp11, temp12, temp13, temp14, temp15, temp16) + LOAD_WITH_OFFSET_X4(temp1, temp2, temp3, temp4, b, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp5,temp6, temp7, temp8, temp9,temp10, temp11, + temp12, temp1, temp2, temp3, temp4) + ADD_SUB_HALVES_X4(temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, + temp5, temp6, temp7, temp8, temp9, temp10, temp11, temp12) + PACK_2_HALVES_TO_WORD(temp9, temp10, temp11, temp12, temp1, temp3, temp5, + temp7, temp2, temp4, temp6, temp8) + ADD_SUB_HALVES_X4(temp2, temp4, temp6, temp8, temp9, temp1, temp3, temp10, + temp1, temp9, temp3, temp10, temp5, temp11, temp7, temp12) + ADD_SUB_HALVES_X4(temp5, temp11, temp7, temp2, temp9, temp3, temp6, temp12, + temp2, temp9, temp6, temp3, temp4, temp1, temp8, temp10) + ADD_SUB_HALVES_X4(temp1, temp4, temp10, temp8, temp7, temp11, temp5, temp2, + temp5, temp7, temp11, temp2, temp9, temp6, temp3, temp12) + ABS_X8(temp1, temp4, temp10, temp8, temp7, temp11, temp5, temp2) + LOAD_WITH_OFFSET_X4(temp3, temp6, temp9, temp12, w, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + LOAD_WITH_OFFSET_X4(temp13, temp14, temp15, temp16, w, + 0, 4, 8, 12, + 1, 1, 1, 1, + 16) + MUL_HALF(temp3, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, + temp9, temp10, temp11, temp12, temp13, temp14, temp15, temp16) + OUTPUT_EARLY_CLOBBER_REGS_17() + : [a]"r"(a), [b]"r"(b), [w]"r"(w) + : "memory", "hi", "lo" + ); + return abs(temp3 - temp17) >> 5; +} + +static int Disto16x16(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4(a + x + y, b + x + y, w); + } + } + return D; +} + +//------------------------------------------------------------------------------ +// Intra predictions + +#define FILL_PART(J, SIZE) \ + "usw %[value], 0+" #J "*" XSTR(BPS) "(%[dst]) \n\t" \ + "usw %[value], 4+" #J "*" XSTR(BPS) "(%[dst]) \n\t" \ + ".if " #SIZE " == 16 \n\t" \ + "usw %[value], 8+" #J "*" XSTR(BPS) "(%[dst]) \n\t" \ + "usw %[value], 12+" #J "*" XSTR(BPS) "(%[dst]) \n\t" \ + ".endif \n\t" + +#define FILL_8_OR_16(DST, VALUE, SIZE) do { \ + int value = (VALUE); \ + __asm__ volatile ( \ + "replv.qb %[value], %[value] \n\t" \ + FILL_PART( 0, SIZE) \ + FILL_PART( 1, SIZE) \ + FILL_PART( 2, SIZE) \ + FILL_PART( 3, SIZE) \ + FILL_PART( 4, SIZE) \ + FILL_PART( 5, SIZE) \ + FILL_PART( 6, SIZE) \ + FILL_PART( 7, SIZE) \ + ".if " #SIZE " == 16 \n\t" \ + FILL_PART( 8, 16) \ + FILL_PART( 9, 16) \ + FILL_PART(10, 16) \ + FILL_PART(11, 16) \ + FILL_PART(12, 16) \ + FILL_PART(13, 16) \ + FILL_PART(14, 16) \ + FILL_PART(15, 16) \ + ".endif \n\t" \ + : [value]"+&r"(value) \ + : [dst]"r"((DST)) \ + : "memory" \ + ); \ +} while (0) + +#define VERTICAL_PRED(DST, TOP, SIZE) \ +static WEBP_INLINE void VerticalPred##SIZE(uint8_t* (DST), \ + const uint8_t* (TOP)) { \ + int j; \ + if ((TOP)) { \ + for (j = 0; j < (SIZE); ++j) memcpy((DST) + j * BPS, (TOP), (SIZE)); \ + } else { \ + FILL_8_OR_16((DST), 127, (SIZE)); \ + } \ +} + +VERTICAL_PRED(dst, top, 8) +VERTICAL_PRED(dst, top, 16) + +#undef VERTICAL_PRED + +#define HORIZONTAL_PRED(DST, LEFT, SIZE) \ +static WEBP_INLINE void HorizontalPred##SIZE(uint8_t* (DST), \ + const uint8_t* (LEFT)) { \ + if (LEFT) { \ + int j; \ + for (j = 0; j < (SIZE); ++j) { \ + memset((DST) + j * BPS, (LEFT)[j], (SIZE)); \ + } \ + } else { \ + FILL_8_OR_16((DST), 129, (SIZE)); \ + } \ +} + +HORIZONTAL_PRED(dst, left, 8) +HORIZONTAL_PRED(dst, left, 16) + +#undef HORIZONTAL_PRED + +#define CLIPPING() \ + "preceu.ph.qbl %[temp2], %[temp0] \n\t" \ + "preceu.ph.qbr %[temp0], %[temp0] \n\t" \ + "preceu.ph.qbl %[temp3], %[temp1] \n\t" \ + "preceu.ph.qbr %[temp1], %[temp1] \n\t" \ + "addu.ph %[temp2], %[temp2], %[leftY_1] \n\t" \ + "addu.ph %[temp0], %[temp0], %[leftY_1] \n\t" \ + "addu.ph %[temp3], %[temp3], %[leftY_1] \n\t" \ + "addu.ph %[temp1], %[temp1], %[leftY_1] \n\t" \ + "shll_s.ph %[temp2], %[temp2], 7 \n\t" \ + "shll_s.ph %[temp0], %[temp0], 7 \n\t" \ + "shll_s.ph %[temp3], %[temp3], 7 \n\t" \ + "shll_s.ph %[temp1], %[temp1], 7 \n\t" \ + "precrqu_s.qb.ph %[temp0], %[temp2], %[temp0] \n\t" \ + "precrqu_s.qb.ph %[temp1], %[temp3], %[temp1] \n\t" + +#define CLIP_8B_TO_DST(DST, LEFT, TOP, SIZE) do { \ + int leftY_1 = ((int)(LEFT)[y] << 16) + (LEFT)[y]; \ + int temp0, temp1, temp2, temp3; \ + __asm__ volatile ( \ + "replv.ph %[leftY_1], %[leftY_1] \n\t" \ + "ulw %[temp0], 0(%[top]) \n\t" \ + "ulw %[temp1], 4(%[top]) \n\t" \ + "subu.ph %[leftY_1], %[leftY_1], %[left_1] \n\t" \ + CLIPPING() \ + "usw %[temp0], 0(%[dst]) \n\t" \ + "usw %[temp1], 4(%[dst]) \n\t" \ + ".if " #SIZE " == 16 \n\t" \ + "ulw %[temp0], 8(%[top]) \n\t" \ + "ulw %[temp1], 12(%[top]) \n\t" \ + CLIPPING() \ + "usw %[temp0], 8(%[dst]) \n\t" \ + "usw %[temp1], 12(%[dst]) \n\t" \ + ".endif \n\t" \ + : [leftY_1]"+&r"(leftY_1), [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), \ + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3) \ + : [left_1]"r"(left_1), [top]"r"((TOP)), [dst]"r"((DST)) \ + : "memory" \ + ); \ +} while (0) + +#define CLIP_TO_DST(DST, LEFT, TOP, SIZE) do { \ + int y; \ + const int left_1 = ((int)(LEFT)[-1] << 16) + (LEFT)[-1]; \ + for (y = 0; y < (SIZE); ++y) { \ + CLIP_8B_TO_DST((DST), (LEFT), (TOP), (SIZE)); \ + (DST) += BPS; \ + } \ +} while (0) + +#define TRUE_MOTION(DST, LEFT, TOP, SIZE) \ +static WEBP_INLINE void TrueMotion##SIZE(uint8_t* (DST), const uint8_t* (LEFT),\ + const uint8_t* (TOP)) { \ + if ((LEFT) != NULL) { \ + if ((TOP) != NULL) { \ + CLIP_TO_DST((DST), (LEFT), (TOP), (SIZE)); \ + } else { \ + HorizontalPred##SIZE((DST), (LEFT)); \ + } \ + } else { \ + /* true motion without left samples (hence: with default 129 value) */ \ + /* is equivalent to VE prediction where you just copy the top samples. */ \ + /* Note that if top samples are not available, the default value is */ \ + /* then 129, and not 127 as in the VerticalPred case. */ \ + if ((TOP) != NULL) { \ + VerticalPred##SIZE((DST), (TOP)); \ + } else { \ + FILL_8_OR_16((DST), 129, (SIZE)); \ + } \ + } \ +} + +TRUE_MOTION(dst, left, top, 8) +TRUE_MOTION(dst, left, top, 16) + +#undef TRUE_MOTION +#undef CLIP_TO_DST +#undef CLIP_8B_TO_DST +#undef CLIPPING + +static WEBP_INLINE void DCMode16(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + int DC, DC1; + int temp0, temp1, temp2, temp3; + + __asm__ volatile( + "beqz %[top], 2f \n\t" + LOAD_WITH_OFFSET_X4(temp0, temp1, temp2, temp3, top, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[DC], %[temp0], %[temp2] \n\t" + "move %[DC1], %[DC] \n\t" + "beqz %[left], 1f \n\t" + LOAD_WITH_OFFSET_X4(temp0, temp1, temp2, temp3, left, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[DC1], %[temp0], %[temp2] \n\t" + "1: \n\t" + "addu %[DC], %[DC], %[DC1] \n\t" + "j 3f \n\t" + "2: \n\t" + "beqz %[left], 4f \n\t" + LOAD_WITH_OFFSET_X4(temp0, temp1, temp2, temp3, left, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[DC], %[temp0], %[temp2] \n\t" + "addu %[DC], %[DC], %[DC] \n\t" + "3: \n\t" + "shra_r.w %[DC], %[DC], 5 \n\t" + "j 5f \n\t" + "4: \n\t" + "li %[DC], 0x80 \n\t" + "5: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [DC]"=&r"(DC), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [DC1]"=&r"(DC1) + : [left]"r"(left), [top]"r"(top) + : "memory" + ); + + FILL_8_OR_16(dst, DC, 16); +} + +static WEBP_INLINE void DCMode8(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + int DC, DC1; + int temp0, temp1, temp2, temp3; + + __asm__ volatile( + "beqz %[top], 2f \n\t" + "ulw %[temp0], 0(%[top]) \n\t" + "ulw %[temp1], 4(%[top]) \n\t" + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[DC], %[temp0], %[temp1] \n\t" + "move %[DC1], %[DC] \n\t" + "beqz %[left], 1f \n\t" + "ulw %[temp2], 0(%[left]) \n\t" + "ulw %[temp3], 4(%[left]) \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[DC1], %[temp2], %[temp3] \n\t" + "1: \n\t" + "addu %[DC], %[DC], %[DC1] \n\t" + "j 3f \n\t" + "2: \n\t" + "beqz %[left], 4f \n\t" + "ulw %[temp2], 0(%[left]) \n\t" + "ulw %[temp3], 4(%[left]) \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[DC], %[temp2], %[temp3] \n\t" + "addu %[DC], %[DC], %[DC] \n\t" + "3: \n\t" + "shra_r.w %[DC], %[DC], 4 \n\t" + "j 5f \n\t" + "4: \n\t" + "li %[DC], 0x80 \n\t" + "5: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [DC]"=&r"(DC), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [DC1]"=&r"(DC1) + : [left]"r"(left), [top]"r"(top) + : "memory" + ); + + FILL_8_OR_16(dst, DC, 8); +} + +static void DC4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1; + __asm__ volatile( + "ulw %[temp0], 0(%[top]) \n\t" + "ulw %[temp1], -5(%[top]) \n\t" + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addiu %[temp0], %[temp0], 4 \n\t" + "srl %[temp0], %[temp0], 3 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + "usw %[temp0], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void TM4(uint8_t* dst, const uint8_t* top) { + int a10, a32, temp0, temp1, temp2, temp3, temp4, temp5; + const int c35 = 0xff00ff; + __asm__ volatile ( + "lbu %[temp1], 0(%[top]) \n\t" + "lbu %[a10], 1(%[top]) \n\t" + "lbu %[temp2], 2(%[top]) \n\t" + "lbu %[a32], 3(%[top]) \n\t" + "ulw %[temp0], -5(%[top]) \n\t" + "lbu %[temp4], -1(%[top]) \n\t" + "append %[a10], %[temp1], 16 \n\t" + "append %[a32], %[temp2], 16 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "shrl.ph %[temp1], %[temp0], 8 \n\t" + "and %[temp0], %[temp0], %[c35] \n\t" + "subu.ph %[temp1], %[temp1], %[temp4] \n\t" + "subu.ph %[temp0], %[temp0], %[temp4] \n\t" + "srl %[temp2], %[temp1], 16 \n\t" + "srl %[temp3], %[temp0], 16 \n\t" + "replv.ph %[temp2], %[temp2] \n\t" + "replv.ph %[temp3], %[temp3] \n\t" + "replv.ph %[temp4], %[temp1] \n\t" + "replv.ph %[temp5], %[temp0] \n\t" + "addu.ph %[temp0], %[temp3], %[a10] \n\t" + "addu.ph %[temp1], %[temp3], %[a32] \n\t" + "addu.ph %[temp3], %[temp2], %[a10] \n\t" + "addu.ph %[temp2], %[temp2], %[a32] \n\t" + "shll_s.ph %[temp0], %[temp0], 7 \n\t" + "shll_s.ph %[temp1], %[temp1], 7 \n\t" + "shll_s.ph %[temp3], %[temp3], 7 \n\t" + "shll_s.ph %[temp2], %[temp2], 7 \n\t" + "precrqu_s.qb.ph %[temp0], %[temp1], %[temp0] \n\t" + "precrqu_s.qb.ph %[temp1], %[temp2], %[temp3] \n\t" + "addu.ph %[temp2], %[temp5], %[a10] \n\t" + "addu.ph %[temp3], %[temp5], %[a32] \n\t" + "addu.ph %[temp5], %[temp4], %[a10] \n\t" + "addu.ph %[temp4], %[temp4], %[a32] \n\t" + "shll_s.ph %[temp2], %[temp2], 7 \n\t" + "shll_s.ph %[temp3], %[temp3], 7 \n\t" + "shll_s.ph %[temp4], %[temp4], 7 \n\t" + "shll_s.ph %[temp5], %[temp5], 7 \n\t" + "precrqu_s.qb.ph %[temp2], %[temp3], %[temp2] \n\t" + "precrqu_s.qb.ph %[temp3], %[temp4], %[temp5] \n\t" + "usw %[temp1], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp3], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp2], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [a10]"=&r"(a10), [a32]"=&r"(a32) + : [c35]"r"(c35), [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void VE4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + __asm__ volatile( + "ulw %[temp0], -1(%[top]) \n\t" + "ulh %[temp1], 3(%[top]) \n\t" + "preceu.ph.qbr %[temp2], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "packrl.ph %[temp5], %[temp3], %[temp2] \n\t" + "packrl.ph %[temp6], %[temp4], %[temp3] \n\t" + "shll.ph %[temp5], %[temp5], 1 \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp2], %[temp5], %[temp2] \n\t" + "addq.ph %[temp6], %[temp6], %[temp4] \n\t" + "addq.ph %[temp2], %[temp2], %[temp3] \n\t" + "addq.ph %[temp6], %[temp6], %[temp3] \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precr.qb.ph %[temp4], %[temp6], %[temp2] \n\t" + "usw %[temp4], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp4], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp4], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp4], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void HE4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + __asm__ volatile( + "ulw %[temp0], -4(%[top]) \n\t" + "lbu %[temp1], -5(%[top]) \n\t" + "preceu.ph.qbr %[temp2], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp0] \n\t" + "replv.ph %[temp4], %[temp1] \n\t" + "packrl.ph %[temp5], %[temp3], %[temp2] \n\t" + "packrl.ph %[temp6], %[temp2], %[temp4] \n\t" + "shll.ph %[temp5], %[temp5], 1 \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp3], %[temp3], %[temp5] \n\t" + "addq.ph %[temp3], %[temp3], %[temp2] \n\t" + "addq.ph %[temp2], %[temp2], %[temp6] \n\t" + "addq.ph %[temp2], %[temp2], %[temp4] \n\t" + "shra_r.ph %[temp3], %[temp3], 2 \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "replv.qb %[temp0], %[temp3] \n\t" + "replv.qb %[temp1], %[temp2] \n\t" + "srl %[temp3], %[temp3], 16 \n\t" + "srl %[temp2], %[temp2], 16 \n\t" + "replv.qb %[temp3], %[temp3] \n\t" + "replv.qb %[temp2], %[temp2] \n\t" + "usw %[temp3], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp2], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp1], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void RD4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5; + int temp6, temp7, temp8, temp9, temp10, temp11; + __asm__ volatile( + "ulw %[temp0], -5(%[top]) \n\t" + "ulw %[temp1], -1(%[top]) \n\t" + "preceu.ph.qbl %[temp2], %[temp0] \n\t" + "preceu.ph.qbr %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "preceu.ph.qbl %[temp5], %[temp1] \n\t" + "packrl.ph %[temp6], %[temp2], %[temp3] \n\t" + "packrl.ph %[temp7], %[temp4], %[temp2] \n\t" + "packrl.ph %[temp8], %[temp5], %[temp4] \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp9], %[temp2], %[temp6] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "addq.ph %[temp9], %[temp9], %[temp3] \n\t" + "shll.ph %[temp8], %[temp8], 1 \n\t" + "shra_r.ph %[temp9], %[temp9], 2 \n\t" + "addq.ph %[temp10], %[temp4], %[temp7] \n\t" + "addq.ph %[temp11], %[temp5], %[temp8] \n\t" + "addq.ph %[temp10], %[temp10], %[temp2] \n\t" + "addq.ph %[temp11], %[temp11], %[temp4] \n\t" + "shra_r.ph %[temp10], %[temp10], 2 \n\t" + "shra_r.ph %[temp11], %[temp11], 2 \n\t" + "lbu %[temp0], 3(%[top]) \n\t" + "lbu %[temp1], 2(%[top]) \n\t" + "lbu %[temp2], 1(%[top]) \n\t" + "sll %[temp1], %[temp1], 1 \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp0], %[temp0], %[temp2] \n\t" + "precr.qb.ph %[temp9], %[temp10], %[temp9] \n\t" + "shra_r.w %[temp0], %[temp0], 2 \n\t" + "precr.qb.ph %[temp10], %[temp11], %[temp10] \n\t" + "usw %[temp9], 3*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp10], 1*" XSTR(BPS) "(%[dst]) \n\t" + "prepend %[temp9], %[temp11], 8 \n\t" + "prepend %[temp10], %[temp0], 8 \n\t" + "usw %[temp9], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp10], 0*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void VR4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + "ulw %[temp0], -4(%[top]) \n\t" + "ulw %[temp1], 0(%[top]) \n\t" + "preceu.ph.qbl %[temp2], %[temp0] \n\t" + "preceu.ph.qbr %[temp0], %[temp0] \n\t" + "preceu.ph.qbla %[temp3], %[temp1] \n\t" + "preceu.ph.qbra %[temp1], %[temp1] \n\t" + "packrl.ph %[temp7], %[temp3], %[temp2] \n\t" + "addqh_r.ph %[temp4], %[temp1], %[temp3] \n\t" + "move %[temp6], %[temp1] \n\t" + "append %[temp1], %[temp2], 16 \n\t" + "shll.ph %[temp9], %[temp6], 1 \n\t" + "addqh_r.ph %[temp5], %[temp7], %[temp6] \n\t" + "shll.ph %[temp8], %[temp7], 1 \n\t" + "addu.ph %[temp3], %[temp7], %[temp3] \n\t" + "addu.ph %[temp1], %[temp1], %[temp6] \n\t" + "packrl.ph %[temp7], %[temp2], %[temp0] \n\t" + "addu.ph %[temp6], %[temp0], %[temp2] \n\t" + "addu.ph %[temp3], %[temp3], %[temp9] \n\t" + "addu.ph %[temp1], %[temp1], %[temp8] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "shra_r.ph %[temp3], %[temp3], 2 \n\t" + "shra_r.ph %[temp1], %[temp1], 2 \n\t" + "addu.ph %[temp6], %[temp6], %[temp7] \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precrq.ph.w %[temp8], %[temp4], %[temp5] \n\t" + "append %[temp4], %[temp5], 16 \n\t" + "precrq.ph.w %[temp2], %[temp3], %[temp1] \n\t" + "append %[temp3], %[temp1], 16 \n\t" + "precr.qb.ph %[temp8], %[temp8], %[temp4] \n\t" + "precr.qb.ph %[temp3], %[temp2], %[temp3] \n\t" + "usw %[temp8], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp3], 1*" XSTR(BPS) "(%[dst]) \n\t" + "append %[temp3], %[temp6], 8 \n\t" + "srl %[temp6], %[temp6], 16 \n\t" + "append %[temp8], %[temp6], 8 \n\t" + "usw %[temp3], 3*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp8], 2*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void LD4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5; + int temp6, temp7, temp8, temp9, temp10, temp11; + __asm__ volatile( + "ulw %[temp0], 0(%[top]) \n\t" + "ulw %[temp1], 4(%[top]) \n\t" + "preceu.ph.qbl %[temp2], %[temp0] \n\t" + "preceu.ph.qbr %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "preceu.ph.qbl %[temp5], %[temp1] \n\t" + "packrl.ph %[temp6], %[temp2], %[temp3] \n\t" + "packrl.ph %[temp7], %[temp4], %[temp2] \n\t" + "packrl.ph %[temp8], %[temp5], %[temp4] \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp9], %[temp2], %[temp6] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "addq.ph %[temp9], %[temp9], %[temp3] \n\t" + "shll.ph %[temp8], %[temp8], 1 \n\t" + "shra_r.ph %[temp9], %[temp9], 2 \n\t" + "addq.ph %[temp10], %[temp4], %[temp7] \n\t" + "addq.ph %[temp11], %[temp5], %[temp8] \n\t" + "addq.ph %[temp10], %[temp10], %[temp2] \n\t" + "addq.ph %[temp11], %[temp11], %[temp4] \n\t" + "shra_r.ph %[temp10], %[temp10], 2 \n\t" + "shra_r.ph %[temp11], %[temp11], 2 \n\t" + "srl %[temp1], %[temp1], 24 \n\t" + "sll %[temp1], %[temp1], 1 \n\t" + "raddu.w.qb %[temp5], %[temp5] \n\t" + "precr.qb.ph %[temp9], %[temp10], %[temp9] \n\t" + "precr.qb.ph %[temp10], %[temp11], %[temp10] \n\t" + "addu %[temp1], %[temp1], %[temp5] \n\t" + "shra_r.w %[temp1], %[temp1], 2 \n\t" + "usw %[temp9], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp10], 2*" XSTR(BPS) "(%[dst]) \n\t" + "prepend %[temp9], %[temp11], 8 \n\t" + "prepend %[temp10], %[temp1], 8 \n\t" + "usw %[temp9], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp10], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void VL4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + "ulw %[temp0], 0(%[top]) \n\t" + "ulw %[temp1], 4(%[top]) \n\t" + "preceu.ph.qbla %[temp2], %[temp0] \n\t" + "preceu.ph.qbra %[temp0], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp1] \n\t" + "preceu.ph.qbr %[temp1], %[temp1] \n\t" + "addqh_r.ph %[temp4], %[temp0], %[temp2] \n\t" + "packrl.ph %[temp7], %[temp1], %[temp0] \n\t" + "precrq.ph.w %[temp6], %[temp1], %[temp2] \n\t" + "shll.ph %[temp9], %[temp2], 1 \n\t" + "addqh_r.ph %[temp5], %[temp7], %[temp2] \n\t" + "shll.ph %[temp8], %[temp7], 1 \n\t" + "addu.ph %[temp2], %[temp2], %[temp6] \n\t" + "addu.ph %[temp0], %[temp0], %[temp7] \n\t" + "packrl.ph %[temp7], %[temp3], %[temp1] \n\t" + "addu.ph %[temp6], %[temp1], %[temp3] \n\t" + "addu.ph %[temp2], %[temp2], %[temp8] \n\t" + "addu.ph %[temp0], %[temp0], %[temp9] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "shra_r.ph %[temp0], %[temp0], 2 \n\t" + "addu.ph %[temp6], %[temp6], %[temp7] \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precrq.ph.w %[temp8], %[temp5], %[temp4] \n\t" + "append %[temp5], %[temp4], 16 \n\t" + "precrq.ph.w %[temp3], %[temp2], %[temp0] \n\t" + "append %[temp2], %[temp0], 16 \n\t" + "precr.qb.ph %[temp8], %[temp8], %[temp5] \n\t" + "precr.qb.ph %[temp3], %[temp3], %[temp2] \n\t" + "usw %[temp8], 0*" XSTR(BPS) "(%[dst]) \n\t" + "prepend %[temp8], %[temp6], 8 \n\t" + "usw %[temp3], 1*" XSTR(BPS) "(%[dst]) \n\t" + "srl %[temp6], %[temp6], 16 \n\t" + "prepend %[temp3], %[temp6], 8 \n\t" + "usw %[temp8], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp3], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void HD4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + "ulw %[temp0], -5(%[top]) \n\t" + "ulw %[temp1], -1(%[top]) \n\t" + "preceu.ph.qbla %[temp2], %[temp0] \n\t" + "preceu.ph.qbra %[temp0], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp1] \n\t" + "preceu.ph.qbr %[temp1], %[temp1] \n\t" + "addqh_r.ph %[temp4], %[temp0], %[temp2] \n\t" + "packrl.ph %[temp7], %[temp1], %[temp0] \n\t" + "precrq.ph.w %[temp6], %[temp1], %[temp2] \n\t" + "shll.ph %[temp9], %[temp2], 1 \n\t" + "addqh_r.ph %[temp5], %[temp7], %[temp2] \n\t" + "shll.ph %[temp8], %[temp7], 1 \n\t" + "addu.ph %[temp2], %[temp2], %[temp6] \n\t" + "addu.ph %[temp0], %[temp0], %[temp7] \n\t" + "packrl.ph %[temp7], %[temp3], %[temp1] \n\t" + "addu.ph %[temp6], %[temp1], %[temp3] \n\t" + "addu.ph %[temp2], %[temp2], %[temp8] \n\t" + "addu.ph %[temp0], %[temp0], %[temp9] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "shra_r.ph %[temp0], %[temp0], 2 \n\t" + "addu.ph %[temp6], %[temp6], %[temp7] \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precrq.ph.w %[temp1], %[temp2], %[temp5] \n\t" + "precrq.ph.w %[temp3], %[temp0], %[temp4] \n\t" + "precr.qb.ph %[temp7], %[temp6], %[temp1] \n\t" + "precr.qb.ph %[temp6], %[temp1], %[temp3] \n\t" + "usw %[temp7], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp6], 1*" XSTR(BPS) "(%[dst]) \n\t" + "append %[temp2], %[temp5], 16 \n\t" + "append %[temp0], %[temp4], 16 \n\t" + "precr.qb.ph %[temp5], %[temp3], %[temp2] \n\t" + "precr.qb.ph %[temp4], %[temp2], %[temp0] \n\t" + "usw %[temp5], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp4], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void HU4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + __asm__ volatile ( + "ulw %[temp0], -5(%[top]) \n\t" + "preceu.ph.qbl %[temp1], %[temp0] \n\t" + "preceu.ph.qbr %[temp2], %[temp0] \n\t" + "packrl.ph %[temp3], %[temp1], %[temp2] \n\t" + "replv.qb %[temp7], %[temp2] \n\t" + "addqh_r.ph %[temp4], %[temp1], %[temp3] \n\t" + "addqh_r.ph %[temp5], %[temp3], %[temp2] \n\t" + "shll.ph %[temp6], %[temp3], 1 \n\t" + "addu.ph %[temp3], %[temp2], %[temp3] \n\t" + "addu.ph %[temp6], %[temp1], %[temp6] \n\t" + "shll.ph %[temp0], %[temp2], 1 \n\t" + "addu.ph %[temp6], %[temp6], %[temp2] \n\t" + "addu.ph %[temp0], %[temp3], %[temp0] \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "shra_r.ph %[temp0], %[temp0], 2 \n\t" + "packrl.ph %[temp3], %[temp6], %[temp5] \n\t" + "precrq.ph.w %[temp2], %[temp6], %[temp4] \n\t" + "append %[temp0], %[temp5], 16 \n\t" + "precr.qb.ph %[temp3], %[temp3], %[temp2] \n\t" + "usw %[temp3], 0*" XSTR(BPS) "(%[dst]) \n\t" + "precr.qb.ph %[temp1], %[temp7], %[temp0] \n\t" + "usw %[temp7], 3*" XSTR(BPS) "(%[dst]) \n\t" + "packrl.ph %[temp2], %[temp1], %[temp3] \n\t" + "usw %[temp1], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp2], 1*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +//------------------------------------------------------------------------------ +// Chroma 8x8 prediction (paragraph 12.2) + +static void IntraChromaPreds(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + // U block + DCMode8(C8DC8 + dst, left, top); + VerticalPred8(C8VE8 + dst, top); + HorizontalPred8(C8HE8 + dst, left); + TrueMotion8(C8TM8 + dst, left, top); + // V block + dst += 8; + if (top) top += 8; + if (left) left += 16; + DCMode8(C8DC8 + dst, left, top); + VerticalPred8(C8VE8 + dst, top); + HorizontalPred8(C8HE8 + dst, left); + TrueMotion8(C8TM8 + dst, left, top); +} + +//------------------------------------------------------------------------------ +// luma 16x16 prediction (paragraph 12.3) + +static void Intra16Preds(uint8_t* dst, + const uint8_t* left, const uint8_t* top) { + DCMode16(I16DC16 + dst, left, top); + VerticalPred16(I16VE16 + dst, top); + HorizontalPred16(I16HE16 + dst, left); + TrueMotion16(I16TM16 + dst, left, top); +} + +// Left samples are top[-5 .. -2], top_left is top[-1], top are +// located at top[0..3], and top right is top[4..7] +static void Intra4Preds(uint8_t* dst, const uint8_t* top) { + DC4(I4DC4 + dst, top); + TM4(I4TM4 + dst, top); + VE4(I4VE4 + dst, top); + HE4(I4HE4 + dst, top); + RD4(I4RD4 + dst, top); + VR4(I4VR4 + dst, top); + LD4(I4LD4 + dst, top); + VL4(I4VL4 + dst, top); + HD4(I4HD4 + dst, top); + HU4(I4HU4 + dst, top); +} + +//------------------------------------------------------------------------------ +// Metric + +#if !defined(WORK_AROUND_GCC) + +#define GET_SSE_INNER(A) \ + "lw %[temp0], " #A "(%[a]) \n\t" \ + "lw %[temp1], " #A "(%[b]) \n\t" \ + "preceu.ph.qbr %[temp2], %[temp0] \n\t" \ + "preceu.ph.qbl %[temp0], %[temp0] \n\t" \ + "preceu.ph.qbr %[temp3], %[temp1] \n\t" \ + "preceu.ph.qbl %[temp1], %[temp1] \n\t" \ + "subq.ph %[temp2], %[temp2], %[temp3] \n\t" \ + "subq.ph %[temp0], %[temp0], %[temp1] \n\t" \ + "dpa.w.ph $ac0, %[temp2], %[temp2] \n\t" \ + "dpa.w.ph $ac0, %[temp0], %[temp0] \n\t" + +#define GET_SSE(A, B, C, D) \ + GET_SSE_INNER(A) \ + GET_SSE_INNER(B) \ + GET_SSE_INNER(C) \ + GET_SSE_INNER(D) + +static int SSE16x16(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3; + __asm__ volatile ( + "mult $zero, $zero \n\t" + GET_SSE( 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS) + GET_SSE( 1 * BPS, 4 + 1 * BPS, 8 + 1 * BPS, 12 + 1 * BPS) + GET_SSE( 2 * BPS, 4 + 2 * BPS, 8 + 2 * BPS, 12 + 2 * BPS) + GET_SSE( 3 * BPS, 4 + 3 * BPS, 8 + 3 * BPS, 12 + 3 * BPS) + GET_SSE( 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS) + GET_SSE( 5 * BPS, 4 + 5 * BPS, 8 + 5 * BPS, 12 + 5 * BPS) + GET_SSE( 6 * BPS, 4 + 6 * BPS, 8 + 6 * BPS, 12 + 6 * BPS) + GET_SSE( 7 * BPS, 4 + 7 * BPS, 8 + 7 * BPS, 12 + 7 * BPS) + GET_SSE( 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS) + GET_SSE( 9 * BPS, 4 + 9 * BPS, 8 + 9 * BPS, 12 + 9 * BPS) + GET_SSE(10 * BPS, 4 + 10 * BPS, 8 + 10 * BPS, 12 + 10 * BPS) + GET_SSE(11 * BPS, 4 + 11 * BPS, 8 + 11 * BPS, 12 + 11 * BPS) + GET_SSE(12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS) + GET_SSE(13 * BPS, 4 + 13 * BPS, 8 + 13 * BPS, 12 + 13 * BPS) + GET_SSE(14 * BPS, 4 + 14 * BPS, 8 + 14 * BPS, 12 + 14 * BPS) + GET_SSE(15 * BPS, 4 + 15 * BPS, 8 + 15 * BPS, 12 + 15 * BPS) + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE16x8(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3; + __asm__ volatile ( + "mult $zero, $zero \n\t" + GET_SSE( 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS) + GET_SSE( 1 * BPS, 4 + 1 * BPS, 8 + 1 * BPS, 12 + 1 * BPS) + GET_SSE( 2 * BPS, 4 + 2 * BPS, 8 + 2 * BPS, 12 + 2 * BPS) + GET_SSE( 3 * BPS, 4 + 3 * BPS, 8 + 3 * BPS, 12 + 3 * BPS) + GET_SSE( 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS) + GET_SSE( 5 * BPS, 4 + 5 * BPS, 8 + 5 * BPS, 12 + 5 * BPS) + GET_SSE( 6 * BPS, 4 + 6 * BPS, 8 + 6 * BPS, 12 + 6 * BPS) + GET_SSE( 7 * BPS, 4 + 7 * BPS, 8 + 7 * BPS, 12 + 7 * BPS) + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE8x8(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3; + __asm__ volatile ( + "mult $zero, $zero \n\t" + GET_SSE(0 * BPS, 4 + 0 * BPS, 1 * BPS, 4 + 1 * BPS) + GET_SSE(2 * BPS, 4 + 2 * BPS, 3 * BPS, 4 + 3 * BPS) + GET_SSE(4 * BPS, 4 + 4 * BPS, 5 * BPS, 4 + 5 * BPS) + GET_SSE(6 * BPS, 4 + 6 * BPS, 7 * BPS, 4 + 7 * BPS) + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE4x4(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3; + __asm__ volatile ( + "mult $zero, $zero \n\t" + GET_SSE(0 * BPS, 1 * BPS, 2 * BPS, 3 * BPS) + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +#undef GET_SSE +#undef GET_SSE_INNER + +#endif // !WORK_AROUND_GCC + +#undef FILL_8_OR_16 +#undef FILL_PART +#undef OUTPUT_EARLY_CLOBBER_REGS_17 +#undef MUL_HALF +#undef ABS_X8 +#undef ADD_SUB_HALVES_X4 + +//------------------------------------------------------------------------------ +// Quantization +// + +// macro for one pass through for loop in QuantizeBlock reading 2 values at time +// QUANTDIV macro inlined +// J - offset in bytes (kZigzag[n] * 2) +// K - offset in bytes (kZigzag[n] * 4) +// N - offset in bytes (n * 2) +// N1 - offset in bytes ((n + 1) * 2) +#define QUANTIZE_ONE(J, K, N, N1) \ + "ulw %[temp1], " #J "(%[ppin]) \n\t" \ + "ulw %[temp2], " #J "(%[ppsharpen]) \n\t" \ + "lhu %[temp3], " #K "(%[ppzthresh]) \n\t" \ + "lhu %[temp6], " #K "+4(%[ppzthresh]) \n\t" \ + "absq_s.ph %[temp4], %[temp1] \n\t" \ + "ins %[temp3], %[temp6], 16, 16 \n\t" \ + "addu.ph %[coeff], %[temp4], %[temp2] \n\t" \ + "shra.ph %[sign], %[temp1], 15 \n\t" \ + "li %[level], 0x10001 \n\t" \ + "cmp.lt.ph %[temp3], %[coeff] \n\t" \ + "lhu %[temp1], " #J "(%[ppiq]) \n\t" \ + "pick.ph %[temp5], %[level], $0 \n\t" \ + "lw %[temp2], " #K "(%[ppbias]) \n\t" \ + "beqz %[temp5], 0f \n\t" \ + "lhu %[temp3], " #J "(%[ppq]) \n\t" \ + "beq %[temp5], %[level], 1f \n\t" \ + "andi %[temp5], %[temp5], 0x1 \n\t" \ + "andi %[temp4], %[coeff], 0xffff \n\t" \ + "beqz %[temp5], 2f \n\t" \ + "mul %[level], %[temp4], %[temp1] \n\t" \ + "sh $0, " #J "+2(%[ppin]) \n\t" \ + "sh $0, " #N1 "(%[pout]) \n\t" \ + "addu %[level], %[level], %[temp2] \n\t" \ + "sra %[level], %[level], 17 \n\t" \ + "slt %[temp4], %[max_level], %[level] \n\t" \ + "movn %[level], %[max_level], %[temp4] \n\t" \ + "andi %[temp6], %[sign], 0xffff \n\t" \ + "xor %[level], %[level], %[temp6] \n\t" \ + "subu %[level], %[level], %[temp6] \n\t" \ + "mul %[temp5], %[level], %[temp3] \n\t" \ + "or %[ret], %[ret], %[level] \n\t" \ + "sh %[level], " #N "(%[pout]) \n\t" \ + "sh %[temp5], " #J "(%[ppin]) \n\t" \ + "j 3f \n\t" \ +"2: \n\t" \ + "lhu %[temp1], " #J "+2(%[ppiq]) \n\t" \ + "srl %[temp5], %[coeff], 16 \n\t" \ + "mul %[level], %[temp5], %[temp1] \n\t" \ + "lw %[temp2], " #K "+4(%[ppbias]) \n\t" \ + "lhu %[temp3], " #J "+2(%[ppq]) \n\t" \ + "addu %[level], %[level], %[temp2] \n\t" \ + "sra %[level], %[level], 17 \n\t" \ + "srl %[temp6], %[sign], 16 \n\t" \ + "slt %[temp4], %[max_level], %[level] \n\t" \ + "movn %[level], %[max_level], %[temp4] \n\t" \ + "xor %[level], %[level], %[temp6] \n\t" \ + "subu %[level], %[level], %[temp6] \n\t" \ + "mul %[temp5], %[level], %[temp3] \n\t" \ + "sh $0, " #J "(%[ppin]) \n\t" \ + "sh $0, " #N "(%[pout]) \n\t" \ + "or %[ret], %[ret], %[level] \n\t" \ + "sh %[temp5], " #J "+2(%[ppin]) \n\t" \ + "sh %[level], " #N1 "(%[pout]) \n\t" \ + "j 3f \n\t" \ +"1: \n\t" \ + "lhu %[temp1], " #J "(%[ppiq]) \n\t" \ + "lw %[temp2], " #K "(%[ppbias]) \n\t" \ + "ulw %[temp3], " #J "(%[ppq]) \n\t" \ + "andi %[temp5], %[coeff], 0xffff \n\t" \ + "srl %[temp0], %[coeff], 16 \n\t" \ + "lhu %[temp6], " #J "+2(%[ppiq]) \n\t" \ + "lw %[coeff], " #K "+4(%[ppbias]) \n\t" \ + "mul %[level], %[temp5], %[temp1] \n\t" \ + "mul %[temp4], %[temp0], %[temp6] \n\t" \ + "addu %[level], %[level], %[temp2] \n\t" \ + "addu %[temp4], %[temp4], %[coeff] \n\t" \ + "precrq.ph.w %[level], %[temp4], %[level] \n\t" \ + "shra.ph %[level], %[level], 1 \n\t" \ + "cmp.lt.ph %[max_level1],%[level] \n\t" \ + "pick.ph %[level], %[max_level], %[level] \n\t" \ + "xor %[level], %[level], %[sign] \n\t" \ + "subu.ph %[level], %[level], %[sign] \n\t" \ + "mul.ph %[temp3], %[level], %[temp3] \n\t" \ + "or %[ret], %[ret], %[level] \n\t" \ + "sh %[level], " #N "(%[pout]) \n\t" \ + "srl %[level], %[level], 16 \n\t" \ + "sh %[level], " #N1 "(%[pout]) \n\t" \ + "usw %[temp3], " #J "(%[ppin]) \n\t" \ + "j 3f \n\t" \ +"0: \n\t" \ + "sh $0, " #N "(%[pout]) \n\t" \ + "sh $0, " #N1 "(%[pout]) \n\t" \ + "usw $0, " #J "(%[ppin]) \n\t" \ +"3: \n\t" + +static int QuantizeBlock(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + int temp0, temp1, temp2, temp3, temp4, temp5,temp6; + int sign, coeff, level; + int max_level = MAX_LEVEL; + int max_level1 = max_level << 16 | max_level; + int ret = 0; + + int16_t* ppin = &in[0]; + int16_t* pout = &out[0]; + const uint16_t* ppsharpen = &mtx->sharpen_[0]; + const uint32_t* ppzthresh = &mtx->zthresh_[0]; + const uint16_t* ppq = &mtx->q_[0]; + const uint16_t* ppiq = &mtx->iq_[0]; + const uint32_t* ppbias = &mtx->bias_[0]; + + __asm__ volatile ( + QUANTIZE_ONE( 0, 0, 0, 2) + QUANTIZE_ONE( 4, 8, 10, 12) + QUANTIZE_ONE( 8, 16, 4, 8) + QUANTIZE_ONE(12, 24, 14, 24) + QUANTIZE_ONE(16, 32, 6, 16) + QUANTIZE_ONE(20, 40, 22, 26) + QUANTIZE_ONE(24, 48, 18, 20) + QUANTIZE_ONE(28, 56, 28, 30) + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [sign]"=&r"(sign), [coeff]"=&r"(coeff), + [level]"=&r"(level), [temp6]"=&r"(temp6), [ret]"+&r"(ret) + : [ppin]"r"(ppin), [pout]"r"(pout), [max_level1]"r"(max_level1), + [ppiq]"r"(ppiq), [max_level]"r"(max_level), + [ppbias]"r"(ppbias), [ppzthresh]"r"(ppzthresh), + [ppsharpen]"r"(ppsharpen), [ppq]"r"(ppq) + : "memory", "hi", "lo" + ); + + return (ret != 0); +} + +static int Quantize2Blocks(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = QuantizeBlock(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= QuantizeBlock(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} + +#undef QUANTIZE_ONE + +// macro for one horizontal pass in FTransformWHT +// temp0..temp7 holds tmp[0]..tmp[15] +// A, B, C, D - offset in bytes to load from in buffer +// TEMP0, TEMP1 - registers for corresponding tmp elements +#define HORIZONTAL_PASS_WHT(A, B, C, D, TEMP0, TEMP1) \ + "lh %[" #TEMP0 "], " #A "(%[in]) \n\t" \ + "lh %[" #TEMP1 "], " #B "(%[in]) \n\t" \ + "lh %[temp8], " #C "(%[in]) \n\t" \ + "lh %[temp9], " #D "(%[in]) \n\t" \ + "ins %[" #TEMP1 "], %[" #TEMP0 "], 16, 16 \n\t" \ + "ins %[temp9], %[temp8], 16, 16 \n\t" \ + "subq.ph %[temp8], %[" #TEMP1 "], %[temp9] \n\t" \ + "addq.ph %[temp9], %[" #TEMP1 "], %[temp9] \n\t" \ + "precrq.ph.w %[" #TEMP0 "], %[temp8], %[temp9] \n\t" \ + "append %[temp8], %[temp9], 16 \n\t" \ + "subq.ph %[" #TEMP1 "], %[" #TEMP0 "], %[temp8] \n\t" \ + "addq.ph %[" #TEMP0 "], %[" #TEMP0 "], %[temp8] \n\t" \ + "rotr %[" #TEMP1 "], %[" #TEMP1 "], 16 \n\t" + +// macro for one vertical pass in FTransformWHT +// temp0..temp7 holds tmp[0]..tmp[15] +// A, B, C, D - offsets in bytes to store to out buffer +// TEMP0, TEMP2, TEMP4 and TEMP6 - registers for corresponding tmp elements +#define VERTICAL_PASS_WHT(A, B, C, D, TEMP0, TEMP2, TEMP4, TEMP6) \ + "addq.ph %[temp8], %[" #TEMP0 "], %[" #TEMP4 "] \n\t" \ + "addq.ph %[temp9], %[" #TEMP2 "], %[" #TEMP6 "] \n\t" \ + "subq.ph %[" #TEMP2 "], %[" #TEMP2 "], %[" #TEMP6 "] \n\t" \ + "subq.ph %[" #TEMP6 "], %[" #TEMP0 "], %[" #TEMP4 "] \n\t" \ + "addqh.ph %[" #TEMP0 "], %[temp8], %[temp9] \n\t" \ + "subqh.ph %[" #TEMP4 "], %[" #TEMP6 "], %[" #TEMP2 "] \n\t" \ + "addqh.ph %[" #TEMP2 "], %[" #TEMP2 "], %[" #TEMP6 "] \n\t" \ + "subqh.ph %[" #TEMP6 "], %[temp8], %[temp9] \n\t" \ + "usw %[" #TEMP0 "], " #A "(%[out]) \n\t" \ + "usw %[" #TEMP2 "], " #B "(%[out]) \n\t" \ + "usw %[" #TEMP4 "], " #C "(%[out]) \n\t" \ + "usw %[" #TEMP6 "], " #D "(%[out]) \n\t" + +static void FTransformWHT(const int16_t* in, int16_t* out) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + + __asm__ volatile ( + HORIZONTAL_PASS_WHT( 0, 32, 64, 96, temp0, temp1) + HORIZONTAL_PASS_WHT(128, 160, 192, 224, temp2, temp3) + HORIZONTAL_PASS_WHT(256, 288, 320, 352, temp4, temp5) + HORIZONTAL_PASS_WHT(384, 416, 448, 480, temp6, temp7) + VERTICAL_PASS_WHT(0, 8, 16, 24, temp0, temp2, temp4, temp6) + VERTICAL_PASS_WHT(4, 12, 20, 28, temp1, temp3, temp5, temp7) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [in]"r"(in), [out]"r"(out) + : "memory" + ); +} + +#undef VERTICAL_PASS_WHT +#undef HORIZONTAL_PASS_WHT + +// macro for converting coefficients to bin +// convert 8 coeffs at time +// A, B, C, D - offsets in bytes to load from out buffer +#define CONVERT_COEFFS_TO_BIN(A, B, C, D) \ + "ulw %[temp0], " #A "(%[out]) \n\t" \ + "ulw %[temp1], " #B "(%[out]) \n\t" \ + "ulw %[temp2], " #C "(%[out]) \n\t" \ + "ulw %[temp3], " #D "(%[out]) \n\t" \ + "absq_s.ph %[temp0], %[temp0] \n\t" \ + "absq_s.ph %[temp1], %[temp1] \n\t" \ + "absq_s.ph %[temp2], %[temp2] \n\t" \ + "absq_s.ph %[temp3], %[temp3] \n\t" \ + "shra.ph %[temp0], %[temp0], 3 \n\t" \ + "shra.ph %[temp1], %[temp1], 3 \n\t" \ + "shra.ph %[temp2], %[temp2], 3 \n\t" \ + "shra.ph %[temp3], %[temp3], 3 \n\t" \ + "shll_s.ph %[temp0], %[temp0], 10 \n\t" \ + "shll_s.ph %[temp1], %[temp1], 10 \n\t" \ + "shll_s.ph %[temp2], %[temp2], 10 \n\t" \ + "shll_s.ph %[temp3], %[temp3], 10 \n\t" \ + "shrl.ph %[temp0], %[temp0], 10 \n\t" \ + "shrl.ph %[temp1], %[temp1], 10 \n\t" \ + "shrl.ph %[temp2], %[temp2], 10 \n\t" \ + "shrl.ph %[temp3], %[temp3], 10 \n\t" \ + "shll.ph %[temp0], %[temp0], 2 \n\t" \ + "shll.ph %[temp1], %[temp1], 2 \n\t" \ + "shll.ph %[temp2], %[temp2], 2 \n\t" \ + "shll.ph %[temp3], %[temp3], 2 \n\t" \ + "ext %[temp4], %[temp0], 0, 16 \n\t" \ + "ext %[temp0], %[temp0], 16, 16 \n\t" \ + "addu %[temp4], %[temp4], %[dist] \n\t" \ + "addu %[temp0], %[temp0], %[dist] \n\t" \ + "ext %[temp5], %[temp1], 0, 16 \n\t" \ + "lw %[temp8], 0(%[temp4]) \n\t" \ + "ext %[temp1], %[temp1], 16, 16 \n\t" \ + "addu %[temp5], %[temp5], %[dist] \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp4]) \n\t" \ + "lw %[temp8], 0(%[temp0]) \n\t" \ + "addu %[temp1], %[temp1], %[dist] \n\t" \ + "ext %[temp6], %[temp2], 0, 16 \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp0]) \n\t" \ + "lw %[temp8], 0(%[temp5]) \n\t" \ + "ext %[temp2], %[temp2], 16, 16 \n\t" \ + "addu %[temp6], %[temp6], %[dist] \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp5]) \n\t" \ + "lw %[temp8], 0(%[temp1]) \n\t" \ + "addu %[temp2], %[temp2], %[dist] \n\t" \ + "ext %[temp7], %[temp3], 0, 16 \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp1]) \n\t" \ + "lw %[temp8], 0(%[temp6]) \n\t" \ + "ext %[temp3], %[temp3], 16, 16 \n\t" \ + "addu %[temp7], %[temp7], %[dist] \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp6]) \n\t" \ + "lw %[temp8], 0(%[temp2]) \n\t" \ + "addu %[temp3], %[temp3], %[dist] \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp2]) \n\t" \ + "lw %[temp8], 0(%[temp7]) \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp7]) \n\t" \ + "lw %[temp8], 0(%[temp3]) \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp3]) \n\t" + +static void CollectHistogram(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + const int max_coeff = (MAX_COEFF_THRESH << 16) + MAX_COEFF_THRESH; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + + VP8FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + + // Convert coefficients to bin. + __asm__ volatile ( + CONVERT_COEFFS_TO_BIN( 0, 4, 8, 12) + CONVERT_COEFFS_TO_BIN(16, 20, 24, 28) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8) + : [dist]"r"(distribution), [out]"r"(out), [max_coeff]"r"(max_coeff) + : "memory" + ); + } + VP8SetHistogramData(distribution, histo); +} + +#undef CONVERT_COEFFS_TO_BIN + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitMIPSdspR2(void) { + VP8FTransform = FTransform; + VP8ITransform = ITransform; + VP8TDisto4x4 = Disto4x4; + VP8TDisto16x16 = Disto16x16; + VP8EncPredLuma16 = Intra16Preds; + VP8EncPredChroma8 = IntraChromaPreds; + VP8EncPredLuma4 = Intra4Preds; +#if !defined(WORK_AROUND_GCC) + VP8SSE16x16 = SSE16x16; + VP8SSE8x8 = SSE8x8; + VP8SSE16x8 = SSE16x8; + VP8SSE4x4 = SSE4x4; +#endif + VP8EncQuantizeBlock = QuantizeBlock; + VP8EncQuantize2Blocks = Quantize2Blocks; + VP8FTransformWHT = FTransformWHT; + VP8CollectHistogram = CollectHistogram; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8EncDspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/enc_msa.c b/3rdparty/libwebp/dsp/enc_msa.c new file mode 100644 index 0000000000..909b46d5d9 --- /dev/null +++ b/3rdparty/libwebp/dsp/enc_msa.c @@ -0,0 +1,892 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA version of encoder dsp functions. +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MSA) + +#include +#include "./msa_macro.h" +#include "../enc/vp8i_enc.h" + +//------------------------------------------------------------------------------ +// Transforms + +#define IDCT_1D_W(in0, in1, in2, in3, out0, out1, out2, out3) do { \ + v4i32 a1_m, b1_m, c1_m, d1_m; \ + const v4i32 cospi8sqrt2minus1 = __msa_fill_w(20091); \ + const v4i32 sinpi8sqrt2 = __msa_fill_w(35468); \ + v4i32 c_tmp1_m = in1 * sinpi8sqrt2; \ + v4i32 c_tmp2_m = in3 * cospi8sqrt2minus1; \ + v4i32 d_tmp1_m = in1 * cospi8sqrt2minus1; \ + v4i32 d_tmp2_m = in3 * sinpi8sqrt2; \ + \ + ADDSUB2(in0, in2, a1_m, b1_m); \ + SRAI_W2_SW(c_tmp1_m, c_tmp2_m, 16); \ + c_tmp2_m = c_tmp2_m + in3; \ + c1_m = c_tmp1_m - c_tmp2_m; \ + SRAI_W2_SW(d_tmp1_m, d_tmp2_m, 16); \ + d_tmp1_m = d_tmp1_m + in1; \ + d1_m = d_tmp1_m + d_tmp2_m; \ + BUTTERFLY_4(a1_m, b1_m, c1_m, d1_m, out0, out1, out2, out3); \ +} while (0) + +static WEBP_INLINE void ITransformOne(const uint8_t* ref, const int16_t* in, + uint8_t* dst) { + v8i16 input0, input1; + v4i32 in0, in1, in2, in3, hz0, hz1, hz2, hz3, vt0, vt1, vt2, vt3; + v4i32 res0, res1, res2, res3; + v16i8 dest0, dest1, dest2, dest3; + const v16i8 zero = { 0 }; + + LD_SH2(in, 8, input0, input1); + UNPCK_SH_SW(input0, in0, in1); + UNPCK_SH_SW(input1, in2, in3); + IDCT_1D_W(in0, in1, in2, in3, hz0, hz1, hz2, hz3); + TRANSPOSE4x4_SW_SW(hz0, hz1, hz2, hz3, hz0, hz1, hz2, hz3); + IDCT_1D_W(hz0, hz1, hz2, hz3, vt0, vt1, vt2, vt3); + SRARI_W4_SW(vt0, vt1, vt2, vt3, 3); + TRANSPOSE4x4_SW_SW(vt0, vt1, vt2, vt3, vt0, vt1, vt2, vt3); + LD_SB4(ref, BPS, dest0, dest1, dest2, dest3); + ILVR_B4_SW(zero, dest0, zero, dest1, zero, dest2, zero, dest3, + res0, res1, res2, res3); + ILVR_H4_SW(zero, res0, zero, res1, zero, res2, zero, res3, + res0, res1, res2, res3); + ADD4(res0, vt0, res1, vt1, res2, vt2, res3, vt3, res0, res1, res2, res3); + CLIP_SW4_0_255(res0, res1, res2, res3); + PCKEV_B2_SW(res0, res1, res2, res3, vt0, vt1); + res0 = (v4i32)__msa_pckev_b((v16i8)vt0, (v16i8)vt1); + ST4x4_UB(res0, res0, 3, 2, 1, 0, dst, BPS); +} + +static void ITransform(const uint8_t* ref, const int16_t* in, uint8_t* dst, + int do_two) { + ITransformOne(ref, in, dst); + if (do_two) { + ITransformOne(ref + 4, in + 16, dst + 4); + } +} + +static void FTransform(const uint8_t* src, const uint8_t* ref, int16_t* out) { + uint64_t out0, out1, out2, out3; + uint32_t in0, in1, in2, in3; + v4i32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + v8i16 t0, t1, t2, t3; + v16u8 srcl0, srcl1, src0, src1; + const v8i16 mask0 = { 0, 4, 8, 12, 1, 5, 9, 13 }; + const v8i16 mask1 = { 3, 7, 11, 15, 2, 6, 10, 14 }; + const v8i16 mask2 = { 4, 0, 5, 1, 6, 2, 7, 3 }; + const v8i16 mask3 = { 0, 4, 1, 5, 2, 6, 3, 7 }; + const v8i16 cnst0 = { 2217, -5352, 2217, -5352, 2217, -5352, 2217, -5352 }; + const v8i16 cnst1 = { 5352, 2217, 5352, 2217, 5352, 2217, 5352, 2217 }; + + LW4(src, BPS, in0, in1, in2, in3); + INSERT_W4_UB(in0, in1, in2, in3, src0); + LW4(ref, BPS, in0, in1, in2, in3); + INSERT_W4_UB(in0, in1, in2, in3, src1); + ILVRL_B2_UB(src0, src1, srcl0, srcl1); + HSUB_UB2_SH(srcl0, srcl1, t0, t1); + VSHF_H2_SH(t0, t1, t0, t1, mask0, mask1, t2, t3); + ADDSUB2(t2, t3, t0, t1); + t0 = SRLI_H(t0, 3); + VSHF_H2_SH(t0, t0, t1, t1, mask2, mask3, t3, t2); + tmp0 = __msa_hadd_s_w(t3, t3); + tmp2 = __msa_hsub_s_w(t3, t3); + FILL_W2_SW(1812, 937, tmp1, tmp3); + DPADD_SH2_SW(t2, t2, cnst0, cnst1, tmp3, tmp1); + SRAI_W2_SW(tmp1, tmp3, 9); + PCKEV_H2_SH(tmp1, tmp0, tmp3, tmp2, t0, t1); + VSHF_H2_SH(t0, t1, t0, t1, mask0, mask1, t2, t3); + ADDSUB2(t2, t3, t0, t1); + VSHF_H2_SH(t0, t0, t1, t1, mask2, mask3, t3, t2); + tmp0 = __msa_hadd_s_w(t3, t3); + tmp2 = __msa_hsub_s_w(t3, t3); + ADDVI_W2_SW(tmp0, 7, tmp2, 7, tmp0, tmp2); + SRAI_W2_SW(tmp0, tmp2, 4); + FILL_W2_SW(12000, 51000, tmp1, tmp3); + DPADD_SH2_SW(t2, t2, cnst0, cnst1, tmp3, tmp1); + SRAI_W2_SW(tmp1, tmp3, 16); + UNPCK_R_SH_SW(t1, tmp4); + tmp5 = __msa_ceqi_w(tmp4, 0); + tmp4 = (v4i32)__msa_nor_v((v16u8)tmp5, (v16u8)tmp5); + tmp5 = __msa_fill_w(1); + tmp5 = (v4i32)__msa_and_v((v16u8)tmp5, (v16u8)tmp4); + tmp1 += tmp5; + PCKEV_H2_SH(tmp1, tmp0, tmp3, tmp2, t0, t1); + out0 = __msa_copy_s_d((v2i64)t0, 0); + out1 = __msa_copy_s_d((v2i64)t0, 1); + out2 = __msa_copy_s_d((v2i64)t1, 0); + out3 = __msa_copy_s_d((v2i64)t1, 1); + SD4(out0, out1, out2, out3, out, 8); +} + +static void FTransformWHT(const int16_t* in, int16_t* out) { + v8i16 in0 = { 0 }; + v8i16 in1 = { 0 }; + v8i16 tmp0, tmp1, tmp2, tmp3; + v8i16 out0, out1; + const v8i16 mask0 = { 0, 1, 2, 3, 8, 9, 10, 11 }; + const v8i16 mask1 = { 4, 5, 6, 7, 12, 13, 14, 15 }; + const v8i16 mask2 = { 0, 4, 8, 12, 1, 5, 9, 13 }; + const v8i16 mask3 = { 3, 7, 11, 15, 2, 6, 10, 14 }; + + in0 = __msa_insert_h(in0, 0, in[ 0]); + in0 = __msa_insert_h(in0, 1, in[ 64]); + in0 = __msa_insert_h(in0, 2, in[128]); + in0 = __msa_insert_h(in0, 3, in[192]); + in0 = __msa_insert_h(in0, 4, in[ 16]); + in0 = __msa_insert_h(in0, 5, in[ 80]); + in0 = __msa_insert_h(in0, 6, in[144]); + in0 = __msa_insert_h(in0, 7, in[208]); + in1 = __msa_insert_h(in1, 0, in[ 48]); + in1 = __msa_insert_h(in1, 1, in[112]); + in1 = __msa_insert_h(in1, 2, in[176]); + in1 = __msa_insert_h(in1, 3, in[240]); + in1 = __msa_insert_h(in1, 4, in[ 32]); + in1 = __msa_insert_h(in1, 5, in[ 96]); + in1 = __msa_insert_h(in1, 6, in[160]); + in1 = __msa_insert_h(in1, 7, in[224]); + ADDSUB2(in0, in1, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + ADDSUB2(tmp2, tmp3, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask2, mask3, in0, in1); + ADDSUB2(in0, in1, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + ADDSUB2(tmp2, tmp3, out0, out1); + SRAI_H2_SH(out0, out1, 1); + ST_SH2(out0, out1, out, 8); +} + +static int TTransform(const uint8_t* in, const uint16_t* w) { + int sum; + uint32_t in0_m, in1_m, in2_m, in3_m; + v16i8 src0; + v8i16 in0, in1, tmp0, tmp1, tmp2, tmp3; + v4i32 dst0, dst1; + const v16i8 zero = { 0 }; + const v8i16 mask0 = { 0, 1, 2, 3, 8, 9, 10, 11 }; + const v8i16 mask1 = { 4, 5, 6, 7, 12, 13, 14, 15 }; + const v8i16 mask2 = { 0, 4, 8, 12, 1, 5, 9, 13 }; + const v8i16 mask3 = { 3, 7, 11, 15, 2, 6, 10, 14 }; + + LW4(in, BPS, in0_m, in1_m, in2_m, in3_m); + INSERT_W4_SB(in0_m, in1_m, in2_m, in3_m, src0); + ILVRL_B2_SH(zero, src0, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask2, mask3, in0, in1); + ADDSUB2(in0, in1, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + ADDSUB2(tmp2, tmp3, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask2, mask3, in0, in1); + ADDSUB2(in0, in1, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + ADDSUB2(tmp2, tmp3, tmp0, tmp1); + tmp0 = __msa_add_a_h(tmp0, (v8i16)zero); + tmp1 = __msa_add_a_h(tmp1, (v8i16)zero); + LD_SH2(w, 8, tmp2, tmp3); + DOTP_SH2_SW(tmp0, tmp1, tmp2, tmp3, dst0, dst1); + dst0 = dst0 + dst1; + sum = HADD_SW_S32(dst0); + return sum; +} + +static int Disto4x4(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + const int sum1 = TTransform(a, w); + const int sum2 = TTransform(b, w); + return abs(sum2 - sum1) >> 5; +} + +static int Disto16x16(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4(a + x + y, b + x + y, w); + } + } + return D; +} + +//------------------------------------------------------------------------------ +// Histogram + +static void CollectHistogram(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + VP8FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + { + int k; + v8i16 coeff0, coeff1; + const v8i16 zero = { 0 }; + const v8i16 max_coeff_thr = __msa_ldi_h(MAX_COEFF_THRESH); + LD_SH2(&out[0], 8, coeff0, coeff1); + coeff0 = __msa_add_a_h(coeff0, zero); + coeff1 = __msa_add_a_h(coeff1, zero); + SRAI_H2_SH(coeff0, coeff1, 3); + coeff0 = __msa_min_s_h(coeff0, max_coeff_thr); + coeff1 = __msa_min_s_h(coeff1, max_coeff_thr); + ST_SH2(coeff0, coeff1, &out[0], 8); + for (k = 0; k < 16; ++k) { + ++distribution[out[k]]; + } + } + } + VP8SetHistogramData(distribution, histo); +} + +//------------------------------------------------------------------------------ +// Intra predictions + +// luma 4x4 prediction + +#define DST(x, y) dst[(x) + (y) * BPS] +#define AVG3(a, b, c) (((a) + 2 * (b) + (c) + 2) >> 2) +#define AVG2(a, b) (((a) + (b) + 1) >> 1) + +static WEBP_INLINE void VE4(uint8_t* dst, const uint8_t* top) { // vertical + const uint64_t val_m = LD(top - 1); + const v16u8 A = (v16u8)__msa_insert_d((v2i64)A, 0, val_m); + const v16u8 B = SLDI_UB(A, A, 1); + const v16u8 C = SLDI_UB(A, A, 2); + const v16u8 AC = __msa_ave_u_b(A, C); + const v16u8 B2 = __msa_ave_u_b(B, B); + const v16u8 R = __msa_aver_u_b(AC, B2); + const uint32_t out = __msa_copy_s_w((v4i32)R, 0); + SW4(out, out, out, out, dst, BPS); +} + +static WEBP_INLINE void HE4(uint8_t* dst, const uint8_t* top) { // horizontal + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(X, I, J)); + WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(I, J, K)); + WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(J, K, L)); + WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(K, L, L)); +} + +static WEBP_INLINE void DC4(uint8_t* dst, const uint8_t* top) { + uint32_t dc = 4; + int i; + for (i = 0; i < 4; ++i) dc += top[i] + top[-5 + i]; + dc >>= 3; + dc = dc | (dc << 8) | (dc << 16) | (dc << 24); + SW4(dc, dc, dc, dc, dst, BPS); +} + +static WEBP_INLINE void RD4(uint8_t* dst, const uint8_t* top) { + const uint64_t val_m = LD(top - 5); + const v16u8 A1 = (v16u8)__msa_insert_d((v2i64)A1, 0, val_m); + const v16u8 A = (v16u8)__msa_insert_b((v16i8)A1, 8, top[3]); + const v16u8 B = SLDI_UB(A, A, 1); + const v16u8 C = SLDI_UB(A, A, 2); + const v16u8 AC = __msa_ave_u_b(A, C); + const v16u8 B2 = __msa_ave_u_b(B, B); + const v16u8 R0 = __msa_aver_u_b(AC, B2); + const v16u8 R1 = SLDI_UB(R0, R0, 1); + const v16u8 R2 = SLDI_UB(R1, R1, 1); + const v16u8 R3 = SLDI_UB(R2, R2, 1); + const uint32_t val0 = __msa_copy_s_w((v4i32)R0, 0); + const uint32_t val1 = __msa_copy_s_w((v4i32)R1, 0); + const uint32_t val2 = __msa_copy_s_w((v4i32)R2, 0); + const uint32_t val3 = __msa_copy_s_w((v4i32)R3, 0); + SW4(val3, val2, val1, val0, dst, BPS); +} + +static WEBP_INLINE void LD4(uint8_t* dst, const uint8_t* top) { + const uint64_t val_m = LD(top); + const v16u8 A = (v16u8)__msa_insert_d((v2i64)A, 0, val_m); + const v16u8 B = SLDI_UB(A, A, 1); + const v16u8 C1 = SLDI_UB(A, A, 2); + const v16u8 C = (v16u8)__msa_insert_b((v16i8)C1, 6, top[7]); + const v16u8 AC = __msa_ave_u_b(A, C); + const v16u8 B2 = __msa_ave_u_b(B, B); + const v16u8 R0 = __msa_aver_u_b(AC, B2); + const v16u8 R1 = SLDI_UB(R0, R0, 1); + const v16u8 R2 = SLDI_UB(R1, R1, 1); + const v16u8 R3 = SLDI_UB(R2, R2, 1); + const uint32_t val0 = __msa_copy_s_w((v4i32)R0, 0); + const uint32_t val1 = __msa_copy_s_w((v4i32)R1, 0); + const uint32_t val2 = __msa_copy_s_w((v4i32)R2, 0); + const uint32_t val3 = __msa_copy_s_w((v4i32)R3, 0); + SW4(val0, val1, val2, val3, dst, BPS); +} + +static WEBP_INLINE void VR4(uint8_t* dst, const uint8_t* top) { + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + const int D = top[3]; + DST(0, 0) = DST(1, 2) = AVG2(X, A); + DST(1, 0) = DST(2, 2) = AVG2(A, B); + DST(2, 0) = DST(3, 2) = AVG2(B, C); + DST(3, 0) = AVG2(C, D); + DST(0, 3) = AVG3(K, J, I); + DST(0, 2) = AVG3(J, I, X); + DST(0, 1) = DST(1, 3) = AVG3(I, X, A); + DST(1, 1) = DST(2, 3) = AVG3(X, A, B); + DST(2, 1) = DST(3, 3) = AVG3(A, B, C); + DST(3, 1) = AVG3(B, C, D); +} + +static WEBP_INLINE void VL4(uint8_t* dst, const uint8_t* top) { + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + const int D = top[3]; + const int E = top[4]; + const int F = top[5]; + const int G = top[6]; + const int H = top[7]; + DST(0, 0) = AVG2(A, B); + DST(1, 0) = DST(0, 2) = AVG2(B, C); + DST(2, 0) = DST(1, 2) = AVG2(C, D); + DST(3, 0) = DST(2, 2) = AVG2(D, E); + DST(0, 1) = AVG3(A, B, C); + DST(1, 1) = DST(0, 3) = AVG3(B, C, D); + DST(2, 1) = DST(1, 3) = AVG3(C, D, E); + DST(3, 1) = DST(2, 3) = AVG3(D, E, F); + DST(3, 2) = AVG3(E, F, G); + DST(3, 3) = AVG3(F, G, H); +} + +static WEBP_INLINE void HU4(uint8_t* dst, const uint8_t* top) { + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + DST(0, 0) = AVG2(I, J); + DST(2, 0) = DST(0, 1) = AVG2(J, K); + DST(2, 1) = DST(0, 2) = AVG2(K, L); + DST(1, 0) = AVG3(I, J, K); + DST(3, 0) = DST(1, 1) = AVG3(J, K, L); + DST(3, 1) = DST(1, 2) = AVG3(K, L, L); + DST(3, 2) = DST(2, 2) = + DST(0, 3) = DST(1, 3) = DST(2, 3) = DST(3, 3) = L; +} + +static WEBP_INLINE void HD4(uint8_t* dst, const uint8_t* top) { + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + DST(0, 0) = DST(2, 1) = AVG2(I, X); + DST(0, 1) = DST(2, 2) = AVG2(J, I); + DST(0, 2) = DST(2, 3) = AVG2(K, J); + DST(0, 3) = AVG2(L, K); + DST(3, 0) = AVG3(A, B, C); + DST(2, 0) = AVG3(X, A, B); + DST(1, 0) = DST(3, 1) = AVG3(I, X, A); + DST(1, 1) = DST(3, 2) = AVG3(J, I, X); + DST(1, 2) = DST(3, 3) = AVG3(K, J, I); + DST(1, 3) = AVG3(L, K, J); +} + +static WEBP_INLINE void TM4(uint8_t* dst, const uint8_t* top) { + const v16i8 zero = { 0 }; + const v8i16 TL = (v8i16)__msa_fill_h(top[-1]); + const v8i16 L0 = (v8i16)__msa_fill_h(top[-2]); + const v8i16 L1 = (v8i16)__msa_fill_h(top[-3]); + const v8i16 L2 = (v8i16)__msa_fill_h(top[-4]); + const v8i16 L3 = (v8i16)__msa_fill_h(top[-5]); + const v16u8 T1 = LD_UB(top); + const v8i16 T = (v8i16)__msa_ilvr_b(zero, (v16i8)T1); + const v8i16 d = T - TL; + v8i16 r0, r1, r2, r3; + ADD4(d, L0, d, L1, d, L2, d, L3, r0, r1, r2, r3); + CLIP_SH4_0_255(r0, r1, r2, r3); + PCKEV_ST4x4_UB(r0, r1, r2, r3, dst, BPS); +} + +#undef DST +#undef AVG3 +#undef AVG2 + +static void Intra4Preds(uint8_t* dst, const uint8_t* top) { + DC4(I4DC4 + dst, top); + TM4(I4TM4 + dst, top); + VE4(I4VE4 + dst, top); + HE4(I4HE4 + dst, top); + RD4(I4RD4 + dst, top); + VR4(I4VR4 + dst, top); + LD4(I4LD4 + dst, top); + VL4(I4VL4 + dst, top); + HD4(I4HD4 + dst, top); + HU4(I4HU4 + dst, top); +} + +// luma 16x16 prediction + +#define STORE16x16(out, dst) do { \ + ST_UB8(out, out, out, out, out, out, out, out, dst + 0 * BPS, BPS); \ + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); \ +} while (0) + +static WEBP_INLINE void VerticalPred16x16(uint8_t* dst, const uint8_t* top) { + if (top != NULL) { + const v16u8 out = LD_UB(top); + STORE16x16(out, dst); + } else { + const v16u8 out = (v16u8)__msa_fill_b(0x7f); + STORE16x16(out, dst); + } +} + +static WEBP_INLINE void HorizontalPred16x16(uint8_t* dst, + const uint8_t* left) { + if (left != NULL) { + int j; + for (j = 0; j < 16; j += 4) { + const v16u8 L0 = (v16u8)__msa_fill_b(left[0]); + const v16u8 L1 = (v16u8)__msa_fill_b(left[1]); + const v16u8 L2 = (v16u8)__msa_fill_b(left[2]); + const v16u8 L3 = (v16u8)__msa_fill_b(left[3]); + ST_UB4(L0, L1, L2, L3, dst, BPS); + dst += 4 * BPS; + left += 4; + } + } else { + const v16u8 out = (v16u8)__msa_fill_b(0x81); + STORE16x16(out, dst); + } +} + +static WEBP_INLINE void TrueMotion16x16(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + if (left != NULL) { + if (top != NULL) { + int j; + v8i16 d1, d2; + const v16i8 zero = { 0 }; + const v8i16 TL = (v8i16)__msa_fill_h(left[-1]); + const v16u8 T = LD_UB(top); + ILVRL_B2_SH(zero, T, d1, d2); + SUB2(d1, TL, d2, TL, d1, d2); + for (j = 0; j < 16; j += 4) { + v16i8 t0, t1, t2, t3; + v8i16 r0, r1, r2, r3, r4, r5, r6, r7; + const v8i16 L0 = (v8i16)__msa_fill_h(left[j + 0]); + const v8i16 L1 = (v8i16)__msa_fill_h(left[j + 1]); + const v8i16 L2 = (v8i16)__msa_fill_h(left[j + 2]); + const v8i16 L3 = (v8i16)__msa_fill_h(left[j + 3]); + ADD4(d1, L0, d1, L1, d1, L2, d1, L3, r0, r1, r2, r3); + ADD4(d2, L0, d2, L1, d2, L2, d2, L3, r4, r5, r6, r7); + CLIP_SH4_0_255(r0, r1, r2, r3); + CLIP_SH4_0_255(r4, r5, r6, r7); + PCKEV_B4_SB(r4, r0, r5, r1, r6, r2, r7, r3, t0, t1, t2, t3); + ST_SB4(t0, t1, t2, t3, dst, BPS); + dst += 4 * BPS; + } + } else { + HorizontalPred16x16(dst, left); + } + } else { + if (top != NULL) { + VerticalPred16x16(dst, top); + } else { + const v16u8 out = (v16u8)__msa_fill_b(0x81); + STORE16x16(out, dst); + } + } +} + +static WEBP_INLINE void DCMode16x16(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + int DC; + v16u8 out; + if (top != NULL && left != NULL) { + const v16u8 rtop = LD_UB(top); + const v8u16 dctop = __msa_hadd_u_h(rtop, rtop); + const v16u8 rleft = LD_UB(left); + const v8u16 dcleft = __msa_hadd_u_h(rleft, rleft); + const v8u16 dctemp = dctop + dcleft; + DC = HADD_UH_U32(dctemp); + DC = (DC + 16) >> 5; + } else if (left != NULL) { // left but no top + const v16u8 rleft = LD_UB(left); + const v8u16 dcleft = __msa_hadd_u_h(rleft, rleft); + DC = HADD_UH_U32(dcleft); + DC = (DC + DC + 16) >> 5; + } else if (top != NULL) { // top but no left + const v16u8 rtop = LD_UB(top); + const v8u16 dctop = __msa_hadd_u_h(rtop, rtop); + DC = HADD_UH_U32(dctop); + DC = (DC + DC + 16) >> 5; + } else { // no top, no left, nothing. + DC = 0x80; + } + out = (v16u8)__msa_fill_b(DC); + STORE16x16(out, dst); +} + +static void Intra16Preds(uint8_t* dst, + const uint8_t* left, const uint8_t* top) { + DCMode16x16(I16DC16 + dst, left, top); + VerticalPred16x16(I16VE16 + dst, top); + HorizontalPred16x16(I16HE16 + dst, left); + TrueMotion16x16(I16TM16 + dst, left, top); +} + +// Chroma 8x8 prediction + +#define CALC_DC8(in, out) do { \ + const v8u16 temp0 = __msa_hadd_u_h(in, in); \ + const v4u32 temp1 = __msa_hadd_u_w(temp0, temp0); \ + const v2i64 temp2 = (v2i64)__msa_hadd_u_d(temp1, temp1); \ + const v2i64 temp3 = __msa_splati_d(temp2, 1); \ + const v2i64 temp4 = temp3 + temp2; \ + const v16i8 temp5 = (v16i8)__msa_srari_d(temp4, 4); \ + const v2i64 temp6 = (v2i64)__msa_splati_b(temp5, 0); \ + out = __msa_copy_s_d(temp6, 0); \ +} while (0) + +#define STORE8x8(out, dst) do { \ + SD4(out, out, out, out, dst + 0 * BPS, BPS); \ + SD4(out, out, out, out, dst + 4 * BPS, BPS); \ +} while (0) + +static WEBP_INLINE void VerticalPred8x8(uint8_t* dst, const uint8_t* top) { + if (top != NULL) { + const uint64_t out = LD(top); + STORE8x8(out, dst); + } else { + const uint64_t out = 0x7f7f7f7f7f7f7f7fULL; + STORE8x8(out, dst); + } +} + +static WEBP_INLINE void HorizontalPred8x8(uint8_t* dst, const uint8_t* left) { + if (left != NULL) { + int j; + for (j = 0; j < 8; j += 4) { + const v16u8 L0 = (v16u8)__msa_fill_b(left[0]); + const v16u8 L1 = (v16u8)__msa_fill_b(left[1]); + const v16u8 L2 = (v16u8)__msa_fill_b(left[2]); + const v16u8 L3 = (v16u8)__msa_fill_b(left[3]); + const uint64_t out0 = __msa_copy_s_d((v2i64)L0, 0); + const uint64_t out1 = __msa_copy_s_d((v2i64)L1, 0); + const uint64_t out2 = __msa_copy_s_d((v2i64)L2, 0); + const uint64_t out3 = __msa_copy_s_d((v2i64)L3, 0); + SD4(out0, out1, out2, out3, dst, BPS); + dst += 4 * BPS; + left += 4; + } + } else { + const uint64_t out = 0x8181818181818181ULL; + STORE8x8(out, dst); + } +} + +static WEBP_INLINE void TrueMotion8x8(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + if (left != NULL) { + if (top != NULL) { + int j; + const v8i16 TL = (v8i16)__msa_fill_h(left[-1]); + const v16u8 T1 = LD_UB(top); + const v16i8 zero = { 0 }; + const v8i16 T = (v8i16)__msa_ilvr_b(zero, (v16i8)T1); + const v8i16 d = T - TL; + for (j = 0; j < 8; j += 4) { + uint64_t out0, out1, out2, out3; + v16i8 t0, t1; + v8i16 r0 = (v8i16)__msa_fill_h(left[j + 0]); + v8i16 r1 = (v8i16)__msa_fill_h(left[j + 1]); + v8i16 r2 = (v8i16)__msa_fill_h(left[j + 2]); + v8i16 r3 = (v8i16)__msa_fill_h(left[j + 3]); + ADD4(d, r0, d, r1, d, r2, d, r3, r0, r1, r2, r3); + CLIP_SH4_0_255(r0, r1, r2, r3); + PCKEV_B2_SB(r1, r0, r3, r2, t0, t1); + out0 = __msa_copy_s_d((v2i64)t0, 0); + out1 = __msa_copy_s_d((v2i64)t0, 1); + out2 = __msa_copy_s_d((v2i64)t1, 0); + out3 = __msa_copy_s_d((v2i64)t1, 1); + SD4(out0, out1, out2, out3, dst, BPS); + dst += 4 * BPS; + } + } else { + HorizontalPred8x8(dst, left); + } + } else { + if (top != NULL) { + VerticalPred8x8(dst, top); + } else { + const uint64_t out = 0x8181818181818181ULL; + STORE8x8(out, dst); + } + } +} + +static WEBP_INLINE void DCMode8x8(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + uint64_t out; + v16u8 src; + if (top != NULL && left != NULL) { + const uint64_t left_m = LD(left); + const uint64_t top_m = LD(top); + INSERT_D2_UB(left_m, top_m, src); + CALC_DC8(src, out); + } else if (left != NULL) { // left but no top + const uint64_t left_m = LD(left); + INSERT_D2_UB(left_m, left_m, src); + CALC_DC8(src, out); + } else if (top != NULL) { // top but no left + const uint64_t top_m = LD(top); + INSERT_D2_UB(top_m, top_m, src); + CALC_DC8(src, out); + } else { // no top, no left, nothing. + src = (v16u8)__msa_fill_b(0x80); + out = __msa_copy_s_d((v2i64)src, 0); + } + STORE8x8(out, dst); +} + +static void IntraChromaPreds(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + // U block + DCMode8x8(C8DC8 + dst, left, top); + VerticalPred8x8(C8VE8 + dst, top); + HorizontalPred8x8(C8HE8 + dst, left); + TrueMotion8x8(C8TM8 + dst, left, top); + // V block + dst += 8; + if (top != NULL) top += 8; + if (left != NULL) left += 16; + DCMode8x8(C8DC8 + dst, left, top); + VerticalPred8x8(C8VE8 + dst, top); + HorizontalPred8x8(C8HE8 + dst, left); + TrueMotion8x8(C8TM8 + dst, left, top); +} + +//------------------------------------------------------------------------------ +// Metric + +#define PACK_DOTP_UB4_SW(in0, in1, in2, in3, out0, out1, out2, out3) do { \ + v16u8 tmp0, tmp1; \ + v8i16 tmp2, tmp3; \ + ILVRL_B2_UB(in0, in1, tmp0, tmp1); \ + HSUB_UB2_SH(tmp0, tmp1, tmp2, tmp3); \ + DOTP_SH2_SW(tmp2, tmp3, tmp2, tmp3, out0, out1); \ + ILVRL_B2_UB(in2, in3, tmp0, tmp1); \ + HSUB_UB2_SH(tmp0, tmp1, tmp2, tmp3); \ + DOTP_SH2_SW(tmp2, tmp3, tmp2, tmp3, out2, out3); \ +} while (0) + +#define PACK_DPADD_UB4_SW(in0, in1, in2, in3, out0, out1, out2, out3) do { \ + v16u8 tmp0, tmp1; \ + v8i16 tmp2, tmp3; \ + ILVRL_B2_UB(in0, in1, tmp0, tmp1); \ + HSUB_UB2_SH(tmp0, tmp1, tmp2, tmp3); \ + DPADD_SH2_SW(tmp2, tmp3, tmp2, tmp3, out0, out1); \ + ILVRL_B2_UB(in2, in3, tmp0, tmp1); \ + HSUB_UB2_SH(tmp0, tmp1, tmp2, tmp3); \ + DPADD_SH2_SW(tmp2, tmp3, tmp2, tmp3, out2, out3); \ +} while (0) + +static int SSE16x16(const uint8_t* a, const uint8_t* b) { + uint32_t sum; + v16u8 src0, src1, src2, src3, src4, src5, src6, src7; + v16u8 ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7; + v4i32 out0, out1, out2, out3; + + LD_UB8(a, BPS, src0, src1, src2, src3, src4, src5, src6, src7); + LD_UB8(b, BPS, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7); + PACK_DOTP_UB4_SW(src0, ref0, src1, ref1, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src2, ref2, src3, ref3, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src4, ref4, src5, ref5, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src6, ref6, src7, ref7, out0, out1, out2, out3); + a += 8 * BPS; + b += 8 * BPS; + LD_UB8(a, BPS, src0, src1, src2, src3, src4, src5, src6, src7); + LD_UB8(b, BPS, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7); + PACK_DPADD_UB4_SW(src0, ref0, src1, ref1, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src2, ref2, src3, ref3, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src4, ref4, src5, ref5, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src6, ref6, src7, ref7, out0, out1, out2, out3); + out0 += out1; + out2 += out3; + out0 += out2; + sum = HADD_SW_S32(out0); + return sum; +} + +static int SSE16x8(const uint8_t* a, const uint8_t* b) { + uint32_t sum; + v16u8 src0, src1, src2, src3, src4, src5, src6, src7; + v16u8 ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7; + v4i32 out0, out1, out2, out3; + + LD_UB8(a, BPS, src0, src1, src2, src3, src4, src5, src6, src7); + LD_UB8(b, BPS, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7); + PACK_DOTP_UB4_SW(src0, ref0, src1, ref1, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src2, ref2, src3, ref3, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src4, ref4, src5, ref5, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src6, ref6, src7, ref7, out0, out1, out2, out3); + out0 += out1; + out2 += out3; + out0 += out2; + sum = HADD_SW_S32(out0); + return sum; +} + +static int SSE8x8(const uint8_t* a, const uint8_t* b) { + uint32_t sum; + v16u8 src0, src1, src2, src3, src4, src5, src6, src7; + v16u8 ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7; + v16u8 t0, t1, t2, t3; + v4i32 out0, out1, out2, out3; + + LD_UB8(a, BPS, src0, src1, src2, src3, src4, src5, src6, src7); + LD_UB8(b, BPS, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7); + ILVR_B4_UB(src0, src1, src2, src3, ref0, ref1, ref2, ref3, t0, t1, t2, t3); + PACK_DOTP_UB4_SW(t0, t2, t1, t3, out0, out1, out2, out3); + ILVR_B4_UB(src4, src5, src6, src7, ref4, ref5, ref6, ref7, t0, t1, t2, t3); + PACK_DPADD_UB4_SW(t0, t2, t1, t3, out0, out1, out2, out3); + out0 += out1; + out2 += out3; + out0 += out2; + sum = HADD_SW_S32(out0); + return sum; +} + +static int SSE4x4(const uint8_t* a, const uint8_t* b) { + uint32_t sum = 0; + uint32_t src0, src1, src2, src3, ref0, ref1, ref2, ref3; + v16u8 src, ref, tmp0, tmp1; + v8i16 diff0, diff1; + v4i32 out0, out1; + + LW4(a, BPS, src0, src1, src2, src3); + LW4(b, BPS, ref0, ref1, ref2, ref3); + INSERT_W4_UB(src0, src1, src2, src3, src); + INSERT_W4_UB(ref0, ref1, ref2, ref3, ref); + ILVRL_B2_UB(src, ref, tmp0, tmp1); + HSUB_UB2_SH(tmp0, tmp1, diff0, diff1); + DOTP_SH2_SW(diff0, diff1, diff0, diff1, out0, out1); + out0 += out1; + sum = HADD_SW_S32(out0); + return sum; +} + +//------------------------------------------------------------------------------ +// Quantization + +static int QuantizeBlock(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + int sum; + v8i16 in0, in1, sh0, sh1, out0, out1; + v8i16 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, sign0, sign1; + v4i32 s0, s1, s2, s3, b0, b1, b2, b3, t0, t1, t2, t3; + const v8i16 zero = { 0 }; + const v8i16 zigzag0 = { 0, 1, 4, 8, 5, 2, 3, 6 }; + const v8i16 zigzag1 = { 9, 12, 13, 10, 7, 11, 14, 15 }; + const v8i16 maxlevel = __msa_fill_h(MAX_LEVEL); + + LD_SH2(&in[0], 8, in0, in1); + LD_SH2(&mtx->sharpen_[0], 8, sh0, sh1); + tmp4 = __msa_add_a_h(in0, zero); + tmp5 = __msa_add_a_h(in1, zero); + ILVRL_H2_SH(sh0, tmp4, tmp0, tmp1); + ILVRL_H2_SH(sh1, tmp5, tmp2, tmp3); + HADD_SH4_SW(tmp0, tmp1, tmp2, tmp3, s0, s1, s2, s3); + sign0 = (in0 < zero); + sign1 = (in1 < zero); // sign + LD_SH2(&mtx->iq_[0], 8, tmp0, tmp1); // iq + ILVRL_H2_SW(zero, tmp0, t0, t1); + ILVRL_H2_SW(zero, tmp1, t2, t3); + LD_SW4(&mtx->bias_[0], 4, b0, b1, b2, b3); // bias + MUL4(t0, s0, t1, s1, t2, s2, t3, s3, t0, t1, t2, t3); + ADD4(b0, t0, b1, t1, b2, t2, b3, t3, b0, b1, b2, b3); + SRAI_W4_SW(b0, b1, b2, b3, 17); + PCKEV_H2_SH(b1, b0, b3, b2, tmp2, tmp3); + tmp0 = (tmp2 > maxlevel); + tmp1 = (tmp3 > maxlevel); + tmp2 = (v8i16)__msa_bmnz_v((v16u8)tmp2, (v16u8)maxlevel, (v16u8)tmp0); + tmp3 = (v8i16)__msa_bmnz_v((v16u8)tmp3, (v16u8)maxlevel, (v16u8)tmp1); + SUB2(0, tmp2, 0, tmp3, tmp0, tmp1); + tmp2 = (v8i16)__msa_bmnz_v((v16u8)tmp2, (v16u8)tmp0, (v16u8)sign0); + tmp3 = (v8i16)__msa_bmnz_v((v16u8)tmp3, (v16u8)tmp1, (v16u8)sign1); + LD_SW4(&mtx->zthresh_[0], 4, t0, t1, t2, t3); // zthresh + t0 = (s0 > t0); + t1 = (s1 > t1); + t2 = (s2 > t2); + t3 = (s3 > t3); + PCKEV_H2_SH(t1, t0, t3, t2, tmp0, tmp1); + tmp4 = (v8i16)__msa_bmnz_v((v16u8)zero, (v16u8)tmp2, (v16u8)tmp0); + tmp5 = (v8i16)__msa_bmnz_v((v16u8)zero, (v16u8)tmp3, (v16u8)tmp1); + LD_SH2(&mtx->q_[0], 8, tmp0, tmp1); + MUL2(tmp4, tmp0, tmp5, tmp1, in0, in1); + VSHF_H2_SH(tmp4, tmp5, tmp4, tmp5, zigzag0, zigzag1, out0, out1); + ST_SH2(in0, in1, &in[0], 8); + ST_SH2(out0, out1, &out[0], 8); + out0 = __msa_add_a_h(out0, out1); + sum = HADD_SH_S32(out0); + return (sum > 0); +} + +static int Quantize2Blocks(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = VP8EncQuantizeBlock(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= VP8EncQuantizeBlock(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitMSA(void) { + VP8ITransform = ITransform; + VP8FTransform = FTransform; + VP8FTransformWHT = FTransformWHT; + + VP8TDisto4x4 = Disto4x4; + VP8TDisto16x16 = Disto16x16; + VP8CollectHistogram = CollectHistogram; + + VP8EncPredLuma4 = Intra4Preds; + VP8EncPredLuma16 = Intra16Preds; + VP8EncPredChroma8 = IntraChromaPreds; + + VP8SSE16x16 = SSE16x16; + VP8SSE16x8 = SSE16x8; + VP8SSE8x8 = SSE8x8; + VP8SSE4x4 = SSE4x4; + + VP8EncQuantizeBlock = QuantizeBlock; + VP8EncQuantize2Blocks = Quantize2Blocks; + VP8EncQuantizeBlockWHT = QuantizeBlock; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8EncDspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/3rdparty/libwebp/dsp/enc_neon.c b/3rdparty/libwebp/dsp/enc_neon.c index eb256e681d..6a078d632d 100644 --- a/3rdparty/libwebp/dsp/enc_neon.c +++ b/3rdparty/libwebp/dsp/enc_neon.c @@ -13,24 +13,124 @@ #include "./dsp.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - #if defined(WEBP_USE_NEON) -#include "../enc/vp8enci.h" +#include + +#include "./neon.h" +#include "../enc/vp8i_enc.h" //------------------------------------------------------------------------------ // Transforms (Paragraph 14.4) // Inverse transform. -// This code is pretty much the same as TransformOneNEON in the decoder, except +// This code is pretty much the same as TransformOne in the dec_neon.c, except // for subtraction to *ref. See the comments there for algorithmic explanations. + +static const int16_t kC1 = 20091; +static const int16_t kC2 = 17734; // half of kC2, actually. See comment above. + +// This code works but is *slower* than the inlined-asm version below +// (with gcc-4.6). So we disable it for now. Later, it'll be conditional to +// WEBP_USE_INTRINSICS define. +// With gcc-4.8, it's a little faster speed than inlined-assembly. +#if defined(WEBP_USE_INTRINSICS) + +// Treats 'v' as an uint8x8_t and zero extends to an int16x8_t. +static WEBP_INLINE int16x8_t ConvertU8ToS16(uint32x2_t v) { + return vreinterpretq_s16_u16(vmovl_u8(vreinterpret_u8_u32(v))); +} + +// Performs unsigned 8b saturation on 'dst01' and 'dst23' storing the result +// to the corresponding rows of 'dst'. +static WEBP_INLINE void SaturateAndStore4x4(uint8_t* const dst, + const int16x8_t dst01, + const int16x8_t dst23) { + // Unsigned saturate to 8b. + const uint8x8_t dst01_u8 = vqmovun_s16(dst01); + const uint8x8_t dst23_u8 = vqmovun_s16(dst23); + + // Store the results. + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), vreinterpret_u32_u8(dst01_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), vreinterpret_u32_u8(dst01_u8), 1); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), vreinterpret_u32_u8(dst23_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), vreinterpret_u32_u8(dst23_u8), 1); +} + +static WEBP_INLINE void Add4x4(const int16x8_t row01, const int16x8_t row23, + const uint8_t* const ref, uint8_t* const dst) { + uint32x2_t dst01 = vdup_n_u32(0); + uint32x2_t dst23 = vdup_n_u32(0); + + // Load the source pixels. + dst01 = vld1_lane_u32((uint32_t*)(ref + 0 * BPS), dst01, 0); + dst23 = vld1_lane_u32((uint32_t*)(ref + 2 * BPS), dst23, 0); + dst01 = vld1_lane_u32((uint32_t*)(ref + 1 * BPS), dst01, 1); + dst23 = vld1_lane_u32((uint32_t*)(ref + 3 * BPS), dst23, 1); + + { + // Convert to 16b. + const int16x8_t dst01_s16 = ConvertU8ToS16(dst01); + const int16x8_t dst23_s16 = ConvertU8ToS16(dst23); + + // Descale with rounding. + const int16x8_t out01 = vrsraq_n_s16(dst01_s16, row01, 3); + const int16x8_t out23 = vrsraq_n_s16(dst23_s16, row23, 3); + // Add the inverse transform. + SaturateAndStore4x4(dst, out01, out23); + } +} + +static WEBP_INLINE void Transpose8x2(const int16x8_t in0, const int16x8_t in1, + int16x8x2_t* const out) { + // a0 a1 a2 a3 | b0 b1 b2 b3 => a0 b0 c0 d0 | a1 b1 c1 d1 + // c0 c1 c2 c3 | d0 d1 d2 d3 a2 b2 c2 d2 | a3 b3 c3 d3 + const int16x8x2_t tmp0 = vzipq_s16(in0, in1); // a0 c0 a1 c1 a2 c2 ... + // b0 d0 b1 d1 b2 d2 ... + *out = vzipq_s16(tmp0.val[0], tmp0.val[1]); +} + +static WEBP_INLINE void TransformPass(int16x8x2_t* const rows) { + // {rows} = in0 | in4 + // in8 | in12 + // B1 = in4 | in12 + const int16x8_t B1 = + vcombine_s16(vget_high_s16(rows->val[0]), vget_high_s16(rows->val[1])); + // C0 = kC1 * in4 | kC1 * in12 + // C1 = kC2 * in4 | kC2 * in12 + const int16x8_t C0 = vsraq_n_s16(B1, vqdmulhq_n_s16(B1, kC1), 1); + const int16x8_t C1 = vqdmulhq_n_s16(B1, kC2); + const int16x4_t a = vqadd_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 + in8 + const int16x4_t b = vqsub_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 - in8 + // c = kC2 * in4 - kC1 * in12 + // d = kC1 * in4 + kC2 * in12 + const int16x4_t c = vqsub_s16(vget_low_s16(C1), vget_high_s16(C0)); + const int16x4_t d = vqadd_s16(vget_low_s16(C0), vget_high_s16(C1)); + const int16x8_t D0 = vcombine_s16(a, b); // D0 = a | b + const int16x8_t D1 = vcombine_s16(d, c); // D1 = d | c + const int16x8_t E0 = vqaddq_s16(D0, D1); // a+d | b+c + const int16x8_t E_tmp = vqsubq_s16(D0, D1); // a-d | b-c + const int16x8_t E1 = vcombine_s16(vget_high_s16(E_tmp), vget_low_s16(E_tmp)); + Transpose8x2(E0, E1, rows); +} + +static void ITransformOne(const uint8_t* ref, + const int16_t* in, uint8_t* dst) { + int16x8x2_t rows; + INIT_VECTOR2(rows, vld1q_s16(in + 0), vld1q_s16(in + 8)); + TransformPass(&rows); + TransformPass(&rows); + Add4x4(rows.val[0], rows.val[1], ref, dst); +} + +#else + static void ITransformOne(const uint8_t* ref, const int16_t* in, uint8_t* dst) { const int kBPS = BPS; - const int16_t kC1C2[] = { 20091, 17734, 0, 0 }; // kC1 / (kC2 >> 1) / 0 / 0 + const int16_t kC1C2[] = { kC1, kC2, 0, 0 }; __asm__ volatile ( "vld1.16 {q1, q2}, [%[in]] \n" @@ -141,6 +241,8 @@ static void ITransformOne(const uint8_t* ref, ); } +#endif // WEBP_USE_INTRINSICS + static void ITransform(const uint8_t* ref, const int16_t* in, uint8_t* dst, int do_two) { ITransformOne(ref, in, dst); @@ -149,76 +251,102 @@ static void ITransform(const uint8_t* ref, } } -// Same code as dec_neon.c -static void ITransformWHT(const int16_t* in, int16_t* out) { - const int kStep = 32; // The store is only incrementing the pointer as if we - // had stored a single byte. - __asm__ volatile ( - // part 1 - // load data into q0, q1 - "vld1.16 {q0, q1}, [%[in]] \n" - - "vaddl.s16 q2, d0, d3 \n" // a0 = in[0] + in[12] - "vaddl.s16 q3, d1, d2 \n" // a1 = in[4] + in[8] - "vsubl.s16 q4, d1, d2 \n" // a2 = in[4] - in[8] - "vsubl.s16 q5, d0, d3 \n" // a3 = in[0] - in[12] - - "vadd.s32 q0, q2, q3 \n" // tmp[0] = a0 + a1 - "vsub.s32 q2, q2, q3 \n" // tmp[8] = a0 - a1 - "vadd.s32 q1, q5, q4 \n" // tmp[4] = a3 + a2 - "vsub.s32 q3, q5, q4 \n" // tmp[12] = a3 - a2 - - // Transpose - // q0 = tmp[0, 4, 8, 12], q1 = tmp[2, 6, 10, 14] - // q2 = tmp[1, 5, 9, 13], q3 = tmp[3, 7, 11, 15] - "vswp d1, d4 \n" // vtrn.64 q0, q2 - "vswp d3, d6 \n" // vtrn.64 q1, q3 - "vtrn.32 q0, q1 \n" - "vtrn.32 q2, q3 \n" - - "vmov.s32 q4, #3 \n" // dc = 3 - "vadd.s32 q0, q0, q4 \n" // dc = tmp[0] + 3 - "vadd.s32 q6, q0, q3 \n" // a0 = dc + tmp[3] - "vadd.s32 q7, q1, q2 \n" // a1 = tmp[1] + tmp[2] - "vsub.s32 q8, q1, q2 \n" // a2 = tmp[1] - tmp[2] - "vsub.s32 q9, q0, q3 \n" // a3 = dc - tmp[3] - - "vadd.s32 q0, q6, q7 \n" - "vshrn.s32 d0, q0, #3 \n" // (a0 + a1) >> 3 - "vadd.s32 q1, q9, q8 \n" - "vshrn.s32 d1, q1, #3 \n" // (a3 + a2) >> 3 - "vsub.s32 q2, q6, q7 \n" - "vshrn.s32 d2, q2, #3 \n" // (a0 - a1) >> 3 - "vsub.s32 q3, q9, q8 \n" - "vshrn.s32 d3, q3, #3 \n" // (a3 - a2) >> 3 - - // set the results to output - "vst1.16 d0[0], [%[out]], %[kStep] \n" - "vst1.16 d1[0], [%[out]], %[kStep] \n" - "vst1.16 d2[0], [%[out]], %[kStep] \n" - "vst1.16 d3[0], [%[out]], %[kStep] \n" - "vst1.16 d0[1], [%[out]], %[kStep] \n" - "vst1.16 d1[1], [%[out]], %[kStep] \n" - "vst1.16 d2[1], [%[out]], %[kStep] \n" - "vst1.16 d3[1], [%[out]], %[kStep] \n" - "vst1.16 d0[2], [%[out]], %[kStep] \n" - "vst1.16 d1[2], [%[out]], %[kStep] \n" - "vst1.16 d2[2], [%[out]], %[kStep] \n" - "vst1.16 d3[2], [%[out]], %[kStep] \n" - "vst1.16 d0[3], [%[out]], %[kStep] \n" - "vst1.16 d1[3], [%[out]], %[kStep] \n" - "vst1.16 d2[3], [%[out]], %[kStep] \n" - "vst1.16 d3[3], [%[out]], %[kStep] \n" - - : [out] "+r"(out) // modified registers - : [in] "r"(in), [kStep] "r"(kStep) // constants - : "memory", "q0", "q1", "q2", "q3", "q4", - "q5", "q6", "q7", "q8", "q9" // clobbered - ); +// Load all 4x4 pixels into a single uint8x16_t variable. +static uint8x16_t Load4x4(const uint8_t* src) { + uint32x4_t out = vdupq_n_u32(0); + out = vld1q_lane_u32((const uint32_t*)(src + 0 * BPS), out, 0); + out = vld1q_lane_u32((const uint32_t*)(src + 1 * BPS), out, 1); + out = vld1q_lane_u32((const uint32_t*)(src + 2 * BPS), out, 2); + out = vld1q_lane_u32((const uint32_t*)(src + 3 * BPS), out, 3); + return vreinterpretq_u8_u32(out); } // Forward transform. +#if defined(WEBP_USE_INTRINSICS) + +static WEBP_INLINE void Transpose4x4_S16(const int16x4_t A, const int16x4_t B, + const int16x4_t C, const int16x4_t D, + int16x8_t* const out01, + int16x8_t* const out32) { + const int16x4x2_t AB = vtrn_s16(A, B); + const int16x4x2_t CD = vtrn_s16(C, D); + const int32x2x2_t tmp02 = vtrn_s32(vreinterpret_s32_s16(AB.val[0]), + vreinterpret_s32_s16(CD.val[0])); + const int32x2x2_t tmp13 = vtrn_s32(vreinterpret_s32_s16(AB.val[1]), + vreinterpret_s32_s16(CD.val[1])); + *out01 = vreinterpretq_s16_s64( + vcombine_s64(vreinterpret_s64_s32(tmp02.val[0]), + vreinterpret_s64_s32(tmp13.val[0]))); + *out32 = vreinterpretq_s16_s64( + vcombine_s64(vreinterpret_s64_s32(tmp13.val[1]), + vreinterpret_s64_s32(tmp02.val[1]))); +} + +static WEBP_INLINE int16x8_t DiffU8ToS16(const uint8x8_t a, + const uint8x8_t b) { + return vreinterpretq_s16_u16(vsubl_u8(a, b)); +} + +static void FTransform(const uint8_t* src, const uint8_t* ref, + int16_t* out) { + int16x8_t d0d1, d3d2; // working 4x4 int16 variables + { + const uint8x16_t S0 = Load4x4(src); + const uint8x16_t R0 = Load4x4(ref); + const int16x8_t D0D1 = DiffU8ToS16(vget_low_u8(S0), vget_low_u8(R0)); + const int16x8_t D2D3 = DiffU8ToS16(vget_high_u8(S0), vget_high_u8(R0)); + const int16x4_t D0 = vget_low_s16(D0D1); + const int16x4_t D1 = vget_high_s16(D0D1); + const int16x4_t D2 = vget_low_s16(D2D3); + const int16x4_t D3 = vget_high_s16(D2D3); + Transpose4x4_S16(D0, D1, D2, D3, &d0d1, &d3d2); + } + { // 1rst pass + const int32x4_t kCst937 = vdupq_n_s32(937); + const int32x4_t kCst1812 = vdupq_n_s32(1812); + const int16x8_t a0a1 = vaddq_s16(d0d1, d3d2); // d0+d3 | d1+d2 (=a0|a1) + const int16x8_t a3a2 = vsubq_s16(d0d1, d3d2); // d0-d3 | d1-d2 (=a3|a2) + const int16x8_t a0a1_2 = vshlq_n_s16(a0a1, 3); + const int16x4_t tmp0 = vadd_s16(vget_low_s16(a0a1_2), + vget_high_s16(a0a1_2)); + const int16x4_t tmp2 = vsub_s16(vget_low_s16(a0a1_2), + vget_high_s16(a0a1_2)); + const int32x4_t a3_2217 = vmull_n_s16(vget_low_s16(a3a2), 2217); + const int32x4_t a2_2217 = vmull_n_s16(vget_high_s16(a3a2), 2217); + const int32x4_t a2_p_a3 = vmlal_n_s16(a2_2217, vget_low_s16(a3a2), 5352); + const int32x4_t a3_m_a2 = vmlsl_n_s16(a3_2217, vget_high_s16(a3a2), 5352); + const int16x4_t tmp1 = vshrn_n_s32(vaddq_s32(a2_p_a3, kCst1812), 9); + const int16x4_t tmp3 = vshrn_n_s32(vaddq_s32(a3_m_a2, kCst937), 9); + Transpose4x4_S16(tmp0, tmp1, tmp2, tmp3, &d0d1, &d3d2); + } + { // 2nd pass + // the (1<<16) addition is for the replacement: a3!=0 <-> 1-(a3==0) + const int32x4_t kCst12000 = vdupq_n_s32(12000 + (1 << 16)); + const int32x4_t kCst51000 = vdupq_n_s32(51000); + const int16x8_t a0a1 = vaddq_s16(d0d1, d3d2); // d0+d3 | d1+d2 (=a0|a1) + const int16x8_t a3a2 = vsubq_s16(d0d1, d3d2); // d0-d3 | d1-d2 (=a3|a2) + const int16x4_t a0_k7 = vadd_s16(vget_low_s16(a0a1), vdup_n_s16(7)); + const int16x4_t out0 = vshr_n_s16(vadd_s16(a0_k7, vget_high_s16(a0a1)), 4); + const int16x4_t out2 = vshr_n_s16(vsub_s16(a0_k7, vget_high_s16(a0a1)), 4); + const int32x4_t a3_2217 = vmull_n_s16(vget_low_s16(a3a2), 2217); + const int32x4_t a2_2217 = vmull_n_s16(vget_high_s16(a3a2), 2217); + const int32x4_t a2_p_a3 = vmlal_n_s16(a2_2217, vget_low_s16(a3a2), 5352); + const int32x4_t a3_m_a2 = vmlsl_n_s16(a3_2217, vget_high_s16(a3a2), 5352); + const int16x4_t tmp1 = vaddhn_s32(a2_p_a3, kCst12000); + const int16x4_t out3 = vaddhn_s32(a3_m_a2, kCst51000); + const int16x4_t a3_eq_0 = + vreinterpret_s16_u16(vceq_s16(vget_low_s16(a3a2), vdup_n_s16(0))); + const int16x4_t out1 = vadd_s16(tmp1, a3_eq_0); + vst1_s16(out + 0, out0); + vst1_s16(out + 4, out1); + vst1_s16(out + 8, out2); + vst1_s16(out + 12, out3); + } +} + +#else + // adapted from vp8/encoder/arm/neon/shortfdct_neon.asm static const int16_t kCoeff16[] = { 5352, 5352, 5352, 5352, 2217, 2217, 2217, 2217 @@ -343,69 +471,76 @@ static void FTransform(const uint8_t* src, const uint8_t* ref, ); } -static void FTransformWHT(const int16_t* in, int16_t* out) { - const int kStep = 32; - __asm__ volatile ( - // d0 = in[0 * 16] , d1 = in[1 * 16] - // d2 = in[2 * 16] , d3 = in[3 * 16] - "vld1.16 d0[0], [%[in]], %[kStep] \n" - "vld1.16 d1[0], [%[in]], %[kStep] \n" - "vld1.16 d2[0], [%[in]], %[kStep] \n" - "vld1.16 d3[0], [%[in]], %[kStep] \n" - "vld1.16 d0[1], [%[in]], %[kStep] \n" - "vld1.16 d1[1], [%[in]], %[kStep] \n" - "vld1.16 d2[1], [%[in]], %[kStep] \n" - "vld1.16 d3[1], [%[in]], %[kStep] \n" - "vld1.16 d0[2], [%[in]], %[kStep] \n" - "vld1.16 d1[2], [%[in]], %[kStep] \n" - "vld1.16 d2[2], [%[in]], %[kStep] \n" - "vld1.16 d3[2], [%[in]], %[kStep] \n" - "vld1.16 d0[3], [%[in]], %[kStep] \n" - "vld1.16 d1[3], [%[in]], %[kStep] \n" - "vld1.16 d2[3], [%[in]], %[kStep] \n" - "vld1.16 d3[3], [%[in]], %[kStep] \n" +#endif - "vaddl.s16 q2, d0, d2 \n" // a0=(in[0*16]+in[2*16]) - "vaddl.s16 q3, d1, d3 \n" // a1=(in[1*16]+in[3*16]) - "vsubl.s16 q4, d1, d3 \n" // a2=(in[1*16]-in[3*16]) - "vsubl.s16 q5, d0, d2 \n" // a3=(in[0*16]-in[2*16]) +#define LOAD_LANE_16b(VALUE, LANE) do { \ + (VALUE) = vld1_lane_s16(src, (VALUE), (LANE)); \ + src += stride; \ +} while (0) - "vqadd.s32 q6, q2, q3 \n" // a0 + a1 - "vqadd.s32 q7, q5, q4 \n" // a3 + a2 - "vqsub.s32 q8, q5, q4 \n" // a3 - a2 - "vqsub.s32 q9, q2, q3 \n" // a0 - a1 +static void FTransformWHT(const int16_t* src, int16_t* out) { + const int stride = 16; + const int16x4_t zero = vdup_n_s16(0); + int32x4x4_t tmp0; + int16x4x4_t in; + INIT_VECTOR4(in, zero, zero, zero, zero); + LOAD_LANE_16b(in.val[0], 0); + LOAD_LANE_16b(in.val[1], 0); + LOAD_LANE_16b(in.val[2], 0); + LOAD_LANE_16b(in.val[3], 0); + LOAD_LANE_16b(in.val[0], 1); + LOAD_LANE_16b(in.val[1], 1); + LOAD_LANE_16b(in.val[2], 1); + LOAD_LANE_16b(in.val[3], 1); + LOAD_LANE_16b(in.val[0], 2); + LOAD_LANE_16b(in.val[1], 2); + LOAD_LANE_16b(in.val[2], 2); + LOAD_LANE_16b(in.val[3], 2); + LOAD_LANE_16b(in.val[0], 3); + LOAD_LANE_16b(in.val[1], 3); + LOAD_LANE_16b(in.val[2], 3); + LOAD_LANE_16b(in.val[3], 3); - // Transpose - // q6 = tmp[0, 1, 2, 3] ; q7 = tmp[ 4, 5, 6, 7] - // q8 = tmp[8, 9, 10, 11] ; q9 = tmp[12, 13, 14, 15] - "vswp d13, d16 \n" // vtrn.64 q0, q2 - "vswp d15, d18 \n" // vtrn.64 q1, q3 - "vtrn.32 q6, q7 \n" - "vtrn.32 q8, q9 \n" + { + // a0 = in[0 * 16] + in[2 * 16] + // a1 = in[1 * 16] + in[3 * 16] + // a2 = in[1 * 16] - in[3 * 16] + // a3 = in[0 * 16] - in[2 * 16] + const int32x4_t a0 = vaddl_s16(in.val[0], in.val[2]); + const int32x4_t a1 = vaddl_s16(in.val[1], in.val[3]); + const int32x4_t a2 = vsubl_s16(in.val[1], in.val[3]); + const int32x4_t a3 = vsubl_s16(in.val[0], in.val[2]); + tmp0.val[0] = vaddq_s32(a0, a1); + tmp0.val[1] = vaddq_s32(a3, a2); + tmp0.val[2] = vsubq_s32(a3, a2); + tmp0.val[3] = vsubq_s32(a0, a1); + } + { + const int32x4x4_t tmp1 = Transpose4x4(tmp0); + // a0 = tmp[0 + i] + tmp[ 8 + i] + // a1 = tmp[4 + i] + tmp[12 + i] + // a2 = tmp[4 + i] - tmp[12 + i] + // a3 = tmp[0 + i] - tmp[ 8 + i] + const int32x4_t a0 = vaddq_s32(tmp1.val[0], tmp1.val[2]); + const int32x4_t a1 = vaddq_s32(tmp1.val[1], tmp1.val[3]); + const int32x4_t a2 = vsubq_s32(tmp1.val[1], tmp1.val[3]); + const int32x4_t a3 = vsubq_s32(tmp1.val[0], tmp1.val[2]); + const int32x4_t b0 = vhaddq_s32(a0, a1); // (a0 + a1) >> 1 + const int32x4_t b1 = vhaddq_s32(a3, a2); // (a3 + a2) >> 1 + const int32x4_t b2 = vhsubq_s32(a3, a2); // (a3 - a2) >> 1 + const int32x4_t b3 = vhsubq_s32(a0, a1); // (a0 - a1) >> 1 + const int16x4_t out0 = vmovn_s32(b0); + const int16x4_t out1 = vmovn_s32(b1); + const int16x4_t out2 = vmovn_s32(b2); + const int16x4_t out3 = vmovn_s32(b3); - "vqadd.s32 q0, q6, q8 \n" // a0 = tmp[0] + tmp[8] - "vqadd.s32 q1, q7, q9 \n" // a1 = tmp[4] + tmp[12] - "vqsub.s32 q2, q7, q9 \n" // a2 = tmp[4] - tmp[12] - "vqsub.s32 q3, q6, q8 \n" // a3 = tmp[0] - tmp[8] - - "vqadd.s32 q4, q0, q1 \n" // b0 = a0 + a1 - "vqadd.s32 q5, q3, q2 \n" // b1 = a3 + a2 - "vqsub.s32 q6, q3, q2 \n" // b2 = a3 - a2 - "vqsub.s32 q7, q0, q1 \n" // b3 = a0 - a1 - - "vshrn.s32 d18, q4, #1 \n" // b0 >> 1 - "vshrn.s32 d19, q5, #1 \n" // b1 >> 1 - "vshrn.s32 d20, q6, #1 \n" // b2 >> 1 - "vshrn.s32 d21, q7, #1 \n" // b3 >> 1 - - "vst1.16 {q9, q10}, [%[out]] \n" - - : [in] "+r"(in) - : [kStep] "r"(kStep), [out] "r"(out) - : "memory", "q0", "q1", "q2", "q3", "q4", "q5", - "q6", "q7", "q8", "q9", "q10" // clobbered - ) ; + vst1_s16(out + 0, out0); + vst1_s16(out + 4, out1); + vst1_s16(out + 8, out2); + vst1_s16(out + 12, out3); + } } +#undef LOAD_LANE_16b //------------------------------------------------------------------------------ // Texture distortion @@ -413,194 +548,151 @@ static void FTransformWHT(const int16_t* in, int16_t* out) { // We try to match the spectral content (weighted) between source and // reconstructed samples. +// a 0123, b 0123 +// a 4567, b 4567 +// a 89ab, b 89ab +// a cdef, b cdef +// +// transpose +// +// a 048c, b 048c +// a 159d, b 159d +// a 26ae, b 26ae +// a 37bf, b 37bf +// +static WEBP_INLINE int16x8x4_t DistoTranspose4x4S16(int16x8x4_t q4_in) { + const int16x8x2_t q2_tmp0 = vtrnq_s16(q4_in.val[0], q4_in.val[1]); + const int16x8x2_t q2_tmp1 = vtrnq_s16(q4_in.val[2], q4_in.val[3]); + const int32x4x2_t q2_tmp2 = vtrnq_s32(vreinterpretq_s32_s16(q2_tmp0.val[0]), + vreinterpretq_s32_s16(q2_tmp1.val[0])); + const int32x4x2_t q2_tmp3 = vtrnq_s32(vreinterpretq_s32_s16(q2_tmp0.val[1]), + vreinterpretq_s32_s16(q2_tmp1.val[1])); + q4_in.val[0] = vreinterpretq_s16_s32(q2_tmp2.val[0]); + q4_in.val[2] = vreinterpretq_s16_s32(q2_tmp2.val[1]); + q4_in.val[1] = vreinterpretq_s16_s32(q2_tmp3.val[0]); + q4_in.val[3] = vreinterpretq_s16_s32(q2_tmp3.val[1]); + return q4_in; +} + +static WEBP_INLINE int16x8x4_t DistoHorizontalPass(const int16x8x4_t q4_in) { + // {a0, a1} = {in[0] + in[2], in[1] + in[3]} + // {a3, a2} = {in[0] - in[2], in[1] - in[3]} + const int16x8_t q_a0 = vaddq_s16(q4_in.val[0], q4_in.val[2]); + const int16x8_t q_a1 = vaddq_s16(q4_in.val[1], q4_in.val[3]); + const int16x8_t q_a3 = vsubq_s16(q4_in.val[0], q4_in.val[2]); + const int16x8_t q_a2 = vsubq_s16(q4_in.val[1], q4_in.val[3]); + int16x8x4_t q4_out; + // tmp[0] = a0 + a1 + // tmp[1] = a3 + a2 + // tmp[2] = a3 - a2 + // tmp[3] = a0 - a1 + INIT_VECTOR4(q4_out, + vabsq_s16(vaddq_s16(q_a0, q_a1)), + vabsq_s16(vaddq_s16(q_a3, q_a2)), + vabdq_s16(q_a3, q_a2), vabdq_s16(q_a0, q_a1)); + return q4_out; +} + +static WEBP_INLINE int16x8x4_t DistoVerticalPass(const uint8x8x4_t q4_in) { + const int16x8_t q_a0 = vreinterpretq_s16_u16(vaddl_u8(q4_in.val[0], + q4_in.val[2])); + const int16x8_t q_a1 = vreinterpretq_s16_u16(vaddl_u8(q4_in.val[1], + q4_in.val[3])); + const int16x8_t q_a2 = vreinterpretq_s16_u16(vsubl_u8(q4_in.val[1], + q4_in.val[3])); + const int16x8_t q_a3 = vreinterpretq_s16_u16(vsubl_u8(q4_in.val[0], + q4_in.val[2])); + int16x8x4_t q4_out; + + INIT_VECTOR4(q4_out, + vaddq_s16(q_a0, q_a1), vaddq_s16(q_a3, q_a2), + vsubq_s16(q_a3, q_a2), vsubq_s16(q_a0, q_a1)); + return q4_out; +} + +static WEBP_INLINE int16x4x4_t DistoLoadW(const uint16_t* w) { + const uint16x8_t q_w07 = vld1q_u16(&w[0]); + const uint16x8_t q_w8f = vld1q_u16(&w[8]); + int16x4x4_t d4_w; + INIT_VECTOR4(d4_w, + vget_low_s16(vreinterpretq_s16_u16(q_w07)), + vget_high_s16(vreinterpretq_s16_u16(q_w07)), + vget_low_s16(vreinterpretq_s16_u16(q_w8f)), + vget_high_s16(vreinterpretq_s16_u16(q_w8f))); + return d4_w; +} + +static WEBP_INLINE int32x2_t DistoSum(const int16x8x4_t q4_in, + const int16x4x4_t d4_w) { + int32x2_t d_sum; + // sum += w[ 0] * abs(b0); + // sum += w[ 4] * abs(b1); + // sum += w[ 8] * abs(b2); + // sum += w[12] * abs(b3); + int32x4_t q_sum0 = vmull_s16(d4_w.val[0], vget_low_s16(q4_in.val[0])); + int32x4_t q_sum1 = vmull_s16(d4_w.val[1], vget_low_s16(q4_in.val[1])); + int32x4_t q_sum2 = vmull_s16(d4_w.val[2], vget_low_s16(q4_in.val[2])); + int32x4_t q_sum3 = vmull_s16(d4_w.val[3], vget_low_s16(q4_in.val[3])); + q_sum0 = vmlsl_s16(q_sum0, d4_w.val[0], vget_high_s16(q4_in.val[0])); + q_sum1 = vmlsl_s16(q_sum1, d4_w.val[1], vget_high_s16(q4_in.val[1])); + q_sum2 = vmlsl_s16(q_sum2, d4_w.val[2], vget_high_s16(q4_in.val[2])); + q_sum3 = vmlsl_s16(q_sum3, d4_w.val[3], vget_high_s16(q4_in.val[3])); + + q_sum0 = vaddq_s32(q_sum0, q_sum1); + q_sum2 = vaddq_s32(q_sum2, q_sum3); + q_sum2 = vaddq_s32(q_sum0, q_sum2); + d_sum = vpadd_s32(vget_low_s32(q_sum2), vget_high_s32(q_sum2)); + d_sum = vpadd_s32(d_sum, d_sum); + return d_sum; +} + +#define LOAD_LANE_32b(src, VALUE, LANE) \ + (VALUE) = vld1_lane_u32((const uint32_t*)(src), (VALUE), (LANE)) + // Hadamard transform // Returns the weighted sum of the absolute value of transformed coefficients. -// This uses a TTransform helper function in C +// w[] contains a row-major 4 by 4 symmetric matrix. static int Disto4x4(const uint8_t* const a, const uint8_t* const b, const uint16_t* const w) { - const int kBPS = BPS; - const uint8_t* A = a; - const uint8_t* B = b; - const uint16_t* W = w; - int sum; - __asm__ volatile ( - "vld1.32 d0[0], [%[a]], %[kBPS] \n" - "vld1.32 d0[1], [%[a]], %[kBPS] \n" - "vld1.32 d2[0], [%[a]], %[kBPS] \n" - "vld1.32 d2[1], [%[a]] \n" + uint32x2_t d_in_ab_0123 = vdup_n_u32(0); + uint32x2_t d_in_ab_4567 = vdup_n_u32(0); + uint32x2_t d_in_ab_89ab = vdup_n_u32(0); + uint32x2_t d_in_ab_cdef = vdup_n_u32(0); + uint8x8x4_t d4_in; - "vld1.32 d1[0], [%[b]], %[kBPS] \n" - "vld1.32 d1[1], [%[b]], %[kBPS] \n" - "vld1.32 d3[0], [%[b]], %[kBPS] \n" - "vld1.32 d3[1], [%[b]] \n" + // load data a, b + LOAD_LANE_32b(a + 0 * BPS, d_in_ab_0123, 0); + LOAD_LANE_32b(a + 1 * BPS, d_in_ab_4567, 0); + LOAD_LANE_32b(a + 2 * BPS, d_in_ab_89ab, 0); + LOAD_LANE_32b(a + 3 * BPS, d_in_ab_cdef, 0); + LOAD_LANE_32b(b + 0 * BPS, d_in_ab_0123, 1); + LOAD_LANE_32b(b + 1 * BPS, d_in_ab_4567, 1); + LOAD_LANE_32b(b + 2 * BPS, d_in_ab_89ab, 1); + LOAD_LANE_32b(b + 3 * BPS, d_in_ab_cdef, 1); + INIT_VECTOR4(d4_in, + vreinterpret_u8_u32(d_in_ab_0123), + vreinterpret_u8_u32(d_in_ab_4567), + vreinterpret_u8_u32(d_in_ab_89ab), + vreinterpret_u8_u32(d_in_ab_cdef)); - // a d0/d2, b d1/d3 - // d0/d1: 01 01 01 01 - // d2/d3: 23 23 23 23 - // But: it goes 01 45 23 67 - // Notice the middle values are transposed - "vtrn.16 q0, q1 \n" + { + // Vertical pass first to avoid a transpose (vertical and horizontal passes + // are commutative because w/kWeightY is symmetric) and subsequent + // transpose. + const int16x8x4_t q4_v = DistoVerticalPass(d4_in); + const int16x4x4_t d4_w = DistoLoadW(w); + // horizontal pass + const int16x8x4_t q4_t = DistoTranspose4x4S16(q4_v); + const int16x8x4_t q4_h = DistoHorizontalPass(q4_t); + int32x2_t d_sum = DistoSum(q4_h, d4_w); - // {a0, a1} = {in[0] + in[2], in[1] + in[3]} - "vaddl.u8 q2, d0, d2 \n" - "vaddl.u8 q10, d1, d3 \n" - // {a3, a2} = {in[0] - in[2], in[1] - in[3]} - "vsubl.u8 q3, d0, d2 \n" - "vsubl.u8 q11, d1, d3 \n" - - // tmp[0] = a0 + a1 - "vpaddl.s16 q0, q2 \n" - "vpaddl.s16 q8, q10 \n" - - // tmp[1] = a3 + a2 - "vpaddl.s16 q1, q3 \n" - "vpaddl.s16 q9, q11 \n" - - // No pair subtract - // q2 = {a0, a3} - // q3 = {a1, a2} - "vtrn.16 q2, q3 \n" - "vtrn.16 q10, q11 \n" - - // {tmp[3], tmp[2]} = {a0 - a1, a3 - a2} - "vsubl.s16 q12, d4, d6 \n" - "vsubl.s16 q13, d5, d7 \n" - "vsubl.s16 q14, d20, d22 \n" - "vsubl.s16 q15, d21, d23 \n" - - // separate tmp[3] and tmp[2] - // q12 = tmp[3] - // q13 = tmp[2] - "vtrn.32 q12, q13 \n" - "vtrn.32 q14, q15 \n" - - // Transpose tmp for a - "vswp d1, d26 \n" // vtrn.64 - "vswp d3, d24 \n" // vtrn.64 - "vtrn.32 q0, q1 \n" - "vtrn.32 q13, q12 \n" - - // Transpose tmp for b - "vswp d17, d30 \n" // vtrn.64 - "vswp d19, d28 \n" // vtrn.64 - "vtrn.32 q8, q9 \n" - "vtrn.32 q15, q14 \n" - - // The first Q register is a, the second b. - // q0/8 tmp[0-3] - // q13/15 tmp[4-7] - // q1/9 tmp[8-11] - // q12/14 tmp[12-15] - - // These are still in 01 45 23 67 order. We fix it easily in the addition - // case but the subtraction propegates them. - "vswp d3, d27 \n" - "vswp d19, d31 \n" - - // a0 = tmp[0] + tmp[8] - "vadd.s32 q2, q0, q1 \n" - "vadd.s32 q3, q8, q9 \n" - - // a1 = tmp[4] + tmp[12] - "vadd.s32 q10, q13, q12 \n" - "vadd.s32 q11, q15, q14 \n" - - // a2 = tmp[4] - tmp[12] - "vsub.s32 q13, q13, q12 \n" - "vsub.s32 q15, q15, q14 \n" - - // a3 = tmp[0] - tmp[8] - "vsub.s32 q0, q0, q1 \n" - "vsub.s32 q8, q8, q9 \n" - - // b0 = a0 + a1 - "vadd.s32 q1, q2, q10 \n" - "vadd.s32 q9, q3, q11 \n" - - // b1 = a3 + a2 - "vadd.s32 q12, q0, q13 \n" - "vadd.s32 q14, q8, q15 \n" - - // b2 = a3 - a2 - "vsub.s32 q0, q0, q13 \n" - "vsub.s32 q8, q8, q15 \n" - - // b3 = a0 - a1 - "vsub.s32 q2, q2, q10 \n" - "vsub.s32 q3, q3, q11 \n" - - "vld1.64 {q10, q11}, [%[w]] \n" - - // abs(b0) - "vabs.s32 q1, q1 \n" - "vabs.s32 q9, q9 \n" - // abs(b1) - "vabs.s32 q12, q12 \n" - "vabs.s32 q14, q14 \n" - // abs(b2) - "vabs.s32 q0, q0 \n" - "vabs.s32 q8, q8 \n" - // abs(b3) - "vabs.s32 q2, q2 \n" - "vabs.s32 q3, q3 \n" - - // expand w before using. - "vmovl.u16 q13, d20 \n" - "vmovl.u16 q15, d21 \n" - - // w[0] * abs(b0) - "vmul.u32 q1, q1, q13 \n" - "vmul.u32 q9, q9, q13 \n" - - // w[4] * abs(b1) - "vmla.u32 q1, q12, q15 \n" - "vmla.u32 q9, q14, q15 \n" - - // expand w before using. - "vmovl.u16 q13, d22 \n" - "vmovl.u16 q15, d23 \n" - - // w[8] * abs(b1) - "vmla.u32 q1, q0, q13 \n" - "vmla.u32 q9, q8, q13 \n" - - // w[12] * abs(b1) - "vmla.u32 q1, q2, q15 \n" - "vmla.u32 q9, q3, q15 \n" - - // Sum the arrays - "vpaddl.u32 q1, q1 \n" - "vpaddl.u32 q9, q9 \n" - "vadd.u64 d2, d3 \n" - "vadd.u64 d18, d19 \n" - - // Hadamard transform needs 4 bits of extra precision (2 bits in each - // direction) for dynamic raw. Weights w[] are 16bits at max, so the maximum - // precision for coeff is 8bit of input + 4bits of Hadamard transform + - // 16bits for w[] + 2 bits of abs() summation. - // - // This uses a maximum of 31 bits (signed). Discarding the top 32 bits is - // A-OK. - - // sum2 - sum1 - "vsub.u32 d0, d2, d18 \n" - // abs(sum2 - sum1) - "vabs.s32 d0, d0 \n" // abs(sum2 - sum1) >> 5 - "vshr.u32 d0, #5 \n" - - // It would be better to move the value straight into r0 but I'm not - // entirely sure how this works with inline assembly. - "vmov.32 %[sum], d0[0] \n" - - : [sum] "=r"(sum), [a] "+r"(A), [b] "+r"(B), [w] "+r"(W) - : [kBPS] "r"(kBPS) - : "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", - "q10", "q11", "q12", "q13", "q14", "q15" // clobbered - ) ; - - return sum; + d_sum = vabs_s32(d_sum); + d_sum = vshr_n_s32(d_sum, 5); + return vget_lane_s32(d_sum, 0); + } } +#undef LOAD_LANE_32b static int Disto16x16(const uint8_t* const a, const uint8_t* const b, const uint16_t* const w) { @@ -614,26 +706,227 @@ static int Disto16x16(const uint8_t* const a, const uint8_t* const b, return D; } -#endif // WEBP_USE_NEON +//------------------------------------------------------------------------------ + +static void CollectHistogram(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + const uint16x8_t max_coeff_thresh = vdupq_n_u16(MAX_COEFF_THRESH); + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + { + int k; + const int16x8_t a0 = vld1q_s16(out + 0); + const int16x8_t b0 = vld1q_s16(out + 8); + const uint16x8_t a1 = vreinterpretq_u16_s16(vabsq_s16(a0)); + const uint16x8_t b1 = vreinterpretq_u16_s16(vabsq_s16(b0)); + const uint16x8_t a2 = vshrq_n_u16(a1, 3); + const uint16x8_t b2 = vshrq_n_u16(b1, 3); + const uint16x8_t a3 = vminq_u16(a2, max_coeff_thresh); + const uint16x8_t b3 = vminq_u16(b2, max_coeff_thresh); + vst1q_s16(out + 0, vreinterpretq_s16_u16(a3)); + vst1q_s16(out + 8, vreinterpretq_s16_u16(b3)); + // Convert coefficients to bin. + for (k = 0; k < 16; ++k) { + ++distribution[out[k]]; + } + } + } + VP8SetHistogramData(distribution, histo); +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE void AccumulateSSE16(const uint8_t* const a, + const uint8_t* const b, + uint32x4_t* const sum) { + const uint8x16_t a0 = vld1q_u8(a); + const uint8x16_t b0 = vld1q_u8(b); + const uint8x16_t abs_diff = vabdq_u8(a0, b0); + const uint16x8_t prod1 = vmull_u8(vget_low_u8(abs_diff), + vget_low_u8(abs_diff)); + const uint16x8_t prod2 = vmull_u8(vget_high_u8(abs_diff), + vget_high_u8(abs_diff)); + /* pair-wise adds and widen */ + const uint32x4_t sum1 = vpaddlq_u16(prod1); + const uint32x4_t sum2 = vpaddlq_u16(prod2); + *sum = vaddq_u32(*sum, vaddq_u32(sum1, sum2)); +} + +// Horizontal sum of all four uint32_t values in 'sum'. +static int SumToInt(uint32x4_t sum) { + const uint64x2_t sum2 = vpaddlq_u32(sum); + const uint64_t sum3 = vgetq_lane_u64(sum2, 0) + vgetq_lane_u64(sum2, 1); + return (int)sum3; +} + +static int SSE16x16_NEON(const uint8_t* a, const uint8_t* b) { + uint32x4_t sum = vdupq_n_u32(0); + int y; + for (y = 0; y < 16; ++y) { + AccumulateSSE16(a + y * BPS, b + y * BPS, &sum); + } + return SumToInt(sum); +} + +static int SSE16x8_NEON(const uint8_t* a, const uint8_t* b) { + uint32x4_t sum = vdupq_n_u32(0); + int y; + for (y = 0; y < 8; ++y) { + AccumulateSSE16(a + y * BPS, b + y * BPS, &sum); + } + return SumToInt(sum); +} + +static int SSE8x8_NEON(const uint8_t* a, const uint8_t* b) { + uint32x4_t sum = vdupq_n_u32(0); + int y; + for (y = 0; y < 8; ++y) { + const uint8x8_t a0 = vld1_u8(a + y * BPS); + const uint8x8_t b0 = vld1_u8(b + y * BPS); + const uint8x8_t abs_diff = vabd_u8(a0, b0); + const uint16x8_t prod = vmull_u8(abs_diff, abs_diff); + sum = vpadalq_u16(sum, prod); + } + return SumToInt(sum); +} + +static int SSE4x4_NEON(const uint8_t* a, const uint8_t* b) { + const uint8x16_t a0 = Load4x4(a); + const uint8x16_t b0 = Load4x4(b); + const uint8x16_t abs_diff = vabdq_u8(a0, b0); + const uint16x8_t prod1 = vmull_u8(vget_low_u8(abs_diff), + vget_low_u8(abs_diff)); + const uint16x8_t prod2 = vmull_u8(vget_high_u8(abs_diff), + vget_high_u8(abs_diff)); + /* pair-wise adds and widen */ + const uint32x4_t sum1 = vpaddlq_u16(prod1); + const uint32x4_t sum2 = vpaddlq_u16(prod2); + return SumToInt(vaddq_u32(sum1, sum2)); +} + +//------------------------------------------------------------------------------ + +// Compilation with gcc-4.6.x is problematic for now. +#if !defined(WORK_AROUND_GCC) + +static int16x8_t Quantize(int16_t* const in, + const VP8Matrix* const mtx, int offset) { + const uint16x8_t sharp = vld1q_u16(&mtx->sharpen_[offset]); + const uint16x8_t q = vld1q_u16(&mtx->q_[offset]); + const uint16x8_t iq = vld1q_u16(&mtx->iq_[offset]); + const uint32x4_t bias0 = vld1q_u32(&mtx->bias_[offset + 0]); + const uint32x4_t bias1 = vld1q_u32(&mtx->bias_[offset + 4]); + + const int16x8_t a = vld1q_s16(in + offset); // in + const uint16x8_t b = vreinterpretq_u16_s16(vabsq_s16(a)); // coeff = abs(in) + const int16x8_t sign = vshrq_n_s16(a, 15); // sign + const uint16x8_t c = vaddq_u16(b, sharp); // + sharpen + const uint32x4_t m0 = vmull_u16(vget_low_u16(c), vget_low_u16(iq)); + const uint32x4_t m1 = vmull_u16(vget_high_u16(c), vget_high_u16(iq)); + const uint32x4_t m2 = vhaddq_u32(m0, bias0); + const uint32x4_t m3 = vhaddq_u32(m1, bias1); // (coeff * iQ + bias) >> 1 + const uint16x8_t c0 = vcombine_u16(vshrn_n_u32(m2, 16), + vshrn_n_u32(m3, 16)); // QFIX=17 = 16+1 + const uint16x8_t c1 = vminq_u16(c0, vdupq_n_u16(MAX_LEVEL)); + const int16x8_t c2 = veorq_s16(vreinterpretq_s16_u16(c1), sign); + const int16x8_t c3 = vsubq_s16(c2, sign); // restore sign + const int16x8_t c4 = vmulq_s16(c3, vreinterpretq_s16_u16(q)); + vst1q_s16(in + offset, c4); + assert(QFIX == 17); // this function can't work as is if QFIX != 16+1 + return c3; +} + +static const uint8_t kShuffles[4][8] = { + { 0, 1, 2, 3, 8, 9, 16, 17 }, + { 10, 11, 4, 5, 6, 7, 12, 13 }, + { 18, 19, 24, 25, 26, 27, 20, 21 }, + { 14, 15, 22, 23, 28, 29, 30, 31 } +}; + +static int QuantizeBlock(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + const int16x8_t out0 = Quantize(in, mtx, 0); + const int16x8_t out1 = Quantize(in, mtx, 8); + uint8x8x4_t shuffles; + // vtbl?_u8 are marked unavailable for iOS arm64 with Xcode < 6.3, use + // non-standard versions there. +#if defined(__APPLE__) && defined(__aarch64__) && \ + defined(__apple_build_version__) && (__apple_build_version__< 6020037) + uint8x16x2_t all_out; + INIT_VECTOR2(all_out, vreinterpretq_u8_s16(out0), vreinterpretq_u8_s16(out1)); + INIT_VECTOR4(shuffles, + vtbl2q_u8(all_out, vld1_u8(kShuffles[0])), + vtbl2q_u8(all_out, vld1_u8(kShuffles[1])), + vtbl2q_u8(all_out, vld1_u8(kShuffles[2])), + vtbl2q_u8(all_out, vld1_u8(kShuffles[3]))); +#else + uint8x8x4_t all_out; + INIT_VECTOR4(all_out, + vreinterpret_u8_s16(vget_low_s16(out0)), + vreinterpret_u8_s16(vget_high_s16(out0)), + vreinterpret_u8_s16(vget_low_s16(out1)), + vreinterpret_u8_s16(vget_high_s16(out1))); + INIT_VECTOR4(shuffles, + vtbl4_u8(all_out, vld1_u8(kShuffles[0])), + vtbl4_u8(all_out, vld1_u8(kShuffles[1])), + vtbl4_u8(all_out, vld1_u8(kShuffles[2])), + vtbl4_u8(all_out, vld1_u8(kShuffles[3]))); +#endif + // Zigzag reordering + vst1_u8((uint8_t*)(out + 0), shuffles.val[0]); + vst1_u8((uint8_t*)(out + 4), shuffles.val[1]); + vst1_u8((uint8_t*)(out + 8), shuffles.val[2]); + vst1_u8((uint8_t*)(out + 12), shuffles.val[3]); + // test zeros + if (*(uint64_t*)(out + 0) != 0) return 1; + if (*(uint64_t*)(out + 4) != 0) return 1; + if (*(uint64_t*)(out + 8) != 0) return 1; + if (*(uint64_t*)(out + 12) != 0) return 1; + return 0; +} + +static int Quantize2Blocks(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = QuantizeBlock(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= QuantizeBlock(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} + +#endif // !WORK_AROUND_GCC //------------------------------------------------------------------------------ // Entry point extern void VP8EncDspInitNEON(void); -void VP8EncDspInitNEON(void) { -#if defined(WEBP_USE_NEON) +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitNEON(void) { VP8ITransform = ITransform; VP8FTransform = FTransform; - VP8ITransformWHT = ITransformWHT; VP8FTransformWHT = FTransformWHT; VP8TDisto4x4 = Disto4x4; VP8TDisto16x16 = Disto16x16; -#endif // WEBP_USE_NEON + VP8CollectHistogram = CollectHistogram; + + VP8SSE16x16 = SSE16x16_NEON; + VP8SSE16x8 = SSE16x8_NEON; + VP8SSE8x8 = SSE8x8_NEON; + VP8SSE4x4 = SSE4x4_NEON; + +#if !defined(WORK_AROUND_GCC) + VP8EncQuantizeBlock = QuantizeBlock; + VP8EncQuantize2Blocks = Quantize2Blocks; +#endif } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8EncDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/3rdparty/libwebp/dsp/enc_sse2.c b/3rdparty/libwebp/dsp/enc_sse2.c index 032e990762..2026a74c91 100644 --- a/3rdparty/libwebp/dsp/enc_sse2.c +++ b/3rdparty/libwebp/dsp/enc_sse2.c @@ -13,97 +13,21 @@ #include "./dsp.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - #if defined(WEBP_USE_SSE2) +#include #include // for abs() #include -#include "../enc/vp8enci.h" - -//------------------------------------------------------------------------------ -// Quite useful macro for debugging. Left here for convenience. - -#if 0 -#include -static void PrintReg(const __m128i r, const char* const name, int size) { - int n; - union { - __m128i r; - uint8_t i8[16]; - uint16_t i16[8]; - uint32_t i32[4]; - uint64_t i64[2]; - } tmp; - tmp.r = r; - printf("%s\t: ", name); - if (size == 8) { - for (n = 0; n < 16; ++n) printf("%.2x ", tmp.i8[n]); - } else if (size == 16) { - for (n = 0; n < 8; ++n) printf("%.4x ", tmp.i16[n]); - } else if (size == 32) { - for (n = 0; n < 4; ++n) printf("%.8x ", tmp.i32[n]); - } else { - for (n = 0; n < 2; ++n) printf("%.16lx ", tmp.i64[n]); - } - printf("\n"); -} -#endif - -//------------------------------------------------------------------------------ -// Compute susceptibility based on DCT-coeff histograms: -// the higher, the "easier" the macroblock is to compress. - -static void CollectHistogramSSE2(const uint8_t* ref, const uint8_t* pred, - int start_block, int end_block, - VP8Histogram* const histo) { - const __m128i max_coeff_thresh = _mm_set1_epi16(MAX_COEFF_THRESH); - int j; - for (j = start_block; j < end_block; ++j) { - int16_t out[16]; - int k; - - VP8FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); - - // Convert coefficients to bin (within out[]). - { - // Load. - const __m128i out0 = _mm_loadu_si128((__m128i*)&out[0]); - const __m128i out1 = _mm_loadu_si128((__m128i*)&out[8]); - // sign(out) = out >> 15 (0x0000 if positive, 0xffff if negative) - const __m128i sign0 = _mm_srai_epi16(out0, 15); - const __m128i sign1 = _mm_srai_epi16(out1, 15); - // abs(out) = (out ^ sign) - sign - const __m128i xor0 = _mm_xor_si128(out0, sign0); - const __m128i xor1 = _mm_xor_si128(out1, sign1); - const __m128i abs0 = _mm_sub_epi16(xor0, sign0); - const __m128i abs1 = _mm_sub_epi16(xor1, sign1); - // v = abs(out) >> 3 - const __m128i v0 = _mm_srai_epi16(abs0, 3); - const __m128i v1 = _mm_srai_epi16(abs1, 3); - // bin = min(v, MAX_COEFF_THRESH) - const __m128i bin0 = _mm_min_epi16(v0, max_coeff_thresh); - const __m128i bin1 = _mm_min_epi16(v1, max_coeff_thresh); - // Store. - _mm_storeu_si128((__m128i*)&out[0], bin0); - _mm_storeu_si128((__m128i*)&out[8], bin1); - } - - // Convert coefficients to bin. - for (k = 0; k < 16; ++k) { - histo->distribution[out[k]]++; - } - } -} +#include "./common_sse2.h" +#include "../enc/cost_enc.h" +#include "../enc/vp8i_enc.h" //------------------------------------------------------------------------------ // Transforms (Paragraph 14.4) // Does one or two inverse transforms. -static void ITransformSSE2(const uint8_t* ref, const int16_t* in, uint8_t* dst, - int do_two) { +static void ITransform(const uint8_t* ref, const int16_t* in, uint8_t* dst, + int do_two) { // This implementation makes use of 16-bit fixed point versions of two // multiply constants: // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 @@ -130,19 +54,19 @@ static void ITransformSSE2(const uint8_t* ref, const int16_t* in, uint8_t* dst, // use nor store. __m128i in0, in1, in2, in3; { - in0 = _mm_loadl_epi64((__m128i*)&in[0]); - in1 = _mm_loadl_epi64((__m128i*)&in[4]); - in2 = _mm_loadl_epi64((__m128i*)&in[8]); - in3 = _mm_loadl_epi64((__m128i*)&in[12]); + in0 = _mm_loadl_epi64((const __m128i*)&in[0]); + in1 = _mm_loadl_epi64((const __m128i*)&in[4]); + in2 = _mm_loadl_epi64((const __m128i*)&in[8]); + in3 = _mm_loadl_epi64((const __m128i*)&in[12]); // a00 a10 a20 a30 x x x x // a01 a11 a21 a31 x x x x // a02 a12 a22 a32 x x x x // a03 a13 a23 a33 x x x x if (do_two) { - const __m128i inB0 = _mm_loadl_epi64((__m128i*)&in[16]); - const __m128i inB1 = _mm_loadl_epi64((__m128i*)&in[20]); - const __m128i inB2 = _mm_loadl_epi64((__m128i*)&in[24]); - const __m128i inB3 = _mm_loadl_epi64((__m128i*)&in[28]); + const __m128i inB0 = _mm_loadl_epi64((const __m128i*)&in[16]); + const __m128i inB1 = _mm_loadl_epi64((const __m128i*)&in[20]); + const __m128i inB2 = _mm_loadl_epi64((const __m128i*)&in[24]); + const __m128i inB3 = _mm_loadl_epi64((const __m128i*)&in[28]); in0 = _mm_unpacklo_epi64(in0, inB0); in1 = _mm_unpacklo_epi64(in1, inB1); in2 = _mm_unpacklo_epi64(in2, inB2); @@ -180,34 +104,7 @@ static void ITransformSSE2(const uint8_t* ref, const int16_t* in, uint8_t* dst, const __m128i tmp3 = _mm_sub_epi16(a, d); // Transpose the two 4x4. - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - const __m128i transpose0_0 = _mm_unpacklo_epi16(tmp0, tmp1); - const __m128i transpose0_1 = _mm_unpacklo_epi16(tmp2, tmp3); - const __m128i transpose0_2 = _mm_unpackhi_epi16(tmp0, tmp1); - const __m128i transpose0_3 = _mm_unpackhi_epi16(tmp2, tmp3); - // a00 a10 a01 a11 a02 a12 a03 a13 - // a20 a30 a21 a31 a22 a32 a23 a33 - // b00 b10 b01 b11 b02 b12 b03 b13 - // b20 b30 b21 b31 b22 b32 b23 b33 - const __m128i transpose1_0 = _mm_unpacklo_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_1 = _mm_unpacklo_epi32(transpose0_2, transpose0_3); - const __m128i transpose1_2 = _mm_unpackhi_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_3 = _mm_unpackhi_epi32(transpose0_2, transpose0_3); - // a00 a10 a20 a30 a01 a11 a21 a31 - // b00 b10 b20 b30 b01 b11 b21 b31 - // a02 a12 a22 a32 a03 a13 a23 a33 - // b02 b12 a22 b32 b03 b13 b23 b33 - T0 = _mm_unpacklo_epi64(transpose1_0, transpose1_1); - T1 = _mm_unpackhi_epi64(transpose1_0, transpose1_1); - T2 = _mm_unpacklo_epi64(transpose1_2, transpose1_3); - T3 = _mm_unpackhi_epi64(transpose1_2, transpose1_3); - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 + VP8Transpose_2_4x4_16b(&tmp0, &tmp1, &tmp2, &tmp3, &T0, &T1, &T2, &T3); } // Horizontal pass and subsequent transpose. @@ -242,34 +139,8 @@ static void ITransformSSE2(const uint8_t* ref, const int16_t* in, uint8_t* dst, const __m128i shifted3 = _mm_srai_epi16(tmp3, 3); // Transpose the two 4x4. - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - const __m128i transpose0_0 = _mm_unpacklo_epi16(shifted0, shifted1); - const __m128i transpose0_1 = _mm_unpacklo_epi16(shifted2, shifted3); - const __m128i transpose0_2 = _mm_unpackhi_epi16(shifted0, shifted1); - const __m128i transpose0_3 = _mm_unpackhi_epi16(shifted2, shifted3); - // a00 a10 a01 a11 a02 a12 a03 a13 - // a20 a30 a21 a31 a22 a32 a23 a33 - // b00 b10 b01 b11 b02 b12 b03 b13 - // b20 b30 b21 b31 b22 b32 b23 b33 - const __m128i transpose1_0 = _mm_unpacklo_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_1 = _mm_unpacklo_epi32(transpose0_2, transpose0_3); - const __m128i transpose1_2 = _mm_unpackhi_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_3 = _mm_unpackhi_epi32(transpose0_2, transpose0_3); - // a00 a10 a20 a30 a01 a11 a21 a31 - // b00 b10 b20 b30 b01 b11 b21 b31 - // a02 a12 a22 a32 a03 a13 a23 a33 - // b02 b12 a22 b32 b03 b13 b23 b33 - T0 = _mm_unpacklo_epi64(transpose1_0, transpose1_1); - T1 = _mm_unpackhi_epi64(transpose1_0, transpose1_1); - T2 = _mm_unpacklo_epi64(transpose1_2, transpose1_3); - T3 = _mm_unpackhi_epi64(transpose1_2, transpose1_3); - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 + VP8Transpose_2_4x4_16b(&shifted0, &shifted1, &shifted2, &shifted3, &T0, &T1, + &T2, &T3); } // Add inverse transform to 'ref' and store. @@ -279,16 +150,16 @@ static void ITransformSSE2(const uint8_t* ref, const int16_t* in, uint8_t* dst, __m128i ref0, ref1, ref2, ref3; if (do_two) { // Load eight bytes/pixels per line. - ref0 = _mm_loadl_epi64((__m128i*)&ref[0 * BPS]); - ref1 = _mm_loadl_epi64((__m128i*)&ref[1 * BPS]); - ref2 = _mm_loadl_epi64((__m128i*)&ref[2 * BPS]); - ref3 = _mm_loadl_epi64((__m128i*)&ref[3 * BPS]); + ref0 = _mm_loadl_epi64((const __m128i*)&ref[0 * BPS]); + ref1 = _mm_loadl_epi64((const __m128i*)&ref[1 * BPS]); + ref2 = _mm_loadl_epi64((const __m128i*)&ref[2 * BPS]); + ref3 = _mm_loadl_epi64((const __m128i*)&ref[3 * BPS]); } else { // Load four bytes/pixels per line. - ref0 = _mm_cvtsi32_si128(*(int*)&ref[0 * BPS]); - ref1 = _mm_cvtsi32_si128(*(int*)&ref[1 * BPS]); - ref2 = _mm_cvtsi32_si128(*(int*)&ref[2 * BPS]); - ref3 = _mm_cvtsi32_si128(*(int*)&ref[3 * BPS]); + ref0 = _mm_cvtsi32_si128(WebPMemToUint32(&ref[0 * BPS])); + ref1 = _mm_cvtsi32_si128(WebPMemToUint32(&ref[1 * BPS])); + ref2 = _mm_cvtsi32_si128(WebPMemToUint32(&ref[2 * BPS])); + ref3 = _mm_cvtsi32_si128(WebPMemToUint32(&ref[3 * BPS])); } // Convert to 16b. ref0 = _mm_unpacklo_epi8(ref0, zero); @@ -314,321 +185,898 @@ static void ITransformSSE2(const uint8_t* ref, const int16_t* in, uint8_t* dst, _mm_storel_epi64((__m128i*)&dst[3 * BPS], ref3); } else { // Store four bytes/pixels per line. - *((int32_t *)&dst[0 * BPS]) = _mm_cvtsi128_si32(ref0); - *((int32_t *)&dst[1 * BPS]) = _mm_cvtsi128_si32(ref1); - *((int32_t *)&dst[2 * BPS]) = _mm_cvtsi128_si32(ref2); - *((int32_t *)&dst[3 * BPS]) = _mm_cvtsi128_si32(ref3); + WebPUint32ToMem(&dst[0 * BPS], _mm_cvtsi128_si32(ref0)); + WebPUint32ToMem(&dst[1 * BPS], _mm_cvtsi128_si32(ref1)); + WebPUint32ToMem(&dst[2 * BPS], _mm_cvtsi128_si32(ref2)); + WebPUint32ToMem(&dst[3 * BPS], _mm_cvtsi128_si32(ref3)); } } } -static void FTransformSSE2(const uint8_t* src, const uint8_t* ref, - int16_t* out) { - const __m128i zero = _mm_setzero_si128(); - const __m128i seven = _mm_set1_epi16(7); +static void FTransformPass1(const __m128i* const in01, + const __m128i* const in23, + __m128i* const out01, + __m128i* const out32) { const __m128i k937 = _mm_set1_epi32(937); const __m128i k1812 = _mm_set1_epi32(1812); - const __m128i k51000 = _mm_set1_epi32(51000); - const __m128i k12000_plus_one = _mm_set1_epi32(12000 + (1 << 16)); - const __m128i k5352_2217 = _mm_set_epi16(5352, 2217, 5352, 2217, - 5352, 2217, 5352, 2217); - const __m128i k2217_5352 = _mm_set_epi16(2217, -5352, 2217, -5352, - 2217, -5352, 2217, -5352); + const __m128i k88p = _mm_set_epi16(8, 8, 8, 8, 8, 8, 8, 8); const __m128i k88m = _mm_set_epi16(-8, 8, -8, 8, -8, 8, -8, 8); const __m128i k5352_2217p = _mm_set_epi16(2217, 5352, 2217, 5352, 2217, 5352, 2217, 5352); const __m128i k5352_2217m = _mm_set_epi16(-5352, 2217, -5352, 2217, -5352, 2217, -5352, 2217); + + // *in01 = 00 01 10 11 02 03 12 13 + // *in23 = 20 21 30 31 22 23 32 33 + const __m128i shuf01_p = _mm_shufflehi_epi16(*in01, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i shuf23_p = _mm_shufflehi_epi16(*in23, _MM_SHUFFLE(2, 3, 0, 1)); + // 00 01 10 11 03 02 13 12 + // 20 21 30 31 23 22 33 32 + const __m128i s01 = _mm_unpacklo_epi64(shuf01_p, shuf23_p); + const __m128i s32 = _mm_unpackhi_epi64(shuf01_p, shuf23_p); + // 00 01 10 11 20 21 30 31 + // 03 02 13 12 23 22 33 32 + const __m128i a01 = _mm_add_epi16(s01, s32); + const __m128i a32 = _mm_sub_epi16(s01, s32); + // [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ] + // [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] + + const __m128i tmp0 = _mm_madd_epi16(a01, k88p); // [ (a0 + a1) << 3, ... ] + const __m128i tmp2 = _mm_madd_epi16(a01, k88m); // [ (a0 - a1) << 3, ... ] + const __m128i tmp1_1 = _mm_madd_epi16(a32, k5352_2217p); + const __m128i tmp3_1 = _mm_madd_epi16(a32, k5352_2217m); + const __m128i tmp1_2 = _mm_add_epi32(tmp1_1, k1812); + const __m128i tmp3_2 = _mm_add_epi32(tmp3_1, k937); + const __m128i tmp1 = _mm_srai_epi32(tmp1_2, 9); + const __m128i tmp3 = _mm_srai_epi32(tmp3_2, 9); + const __m128i s03 = _mm_packs_epi32(tmp0, tmp2); + const __m128i s12 = _mm_packs_epi32(tmp1, tmp3); + const __m128i s_lo = _mm_unpacklo_epi16(s03, s12); // 0 1 0 1 0 1... + const __m128i s_hi = _mm_unpackhi_epi16(s03, s12); // 2 3 2 3 2 3 + const __m128i v23 = _mm_unpackhi_epi32(s_lo, s_hi); + *out01 = _mm_unpacklo_epi32(s_lo, s_hi); + *out32 = _mm_shuffle_epi32(v23, _MM_SHUFFLE(1, 0, 3, 2)); // 3 2 3 2 3 2.. +} + +static void FTransformPass2(const __m128i* const v01, const __m128i* const v32, + int16_t* out) { + const __m128i zero = _mm_setzero_si128(); + const __m128i seven = _mm_set1_epi16(7); + const __m128i k5352_2217 = _mm_set_epi16(5352, 2217, 5352, 2217, + 5352, 2217, 5352, 2217); + const __m128i k2217_5352 = _mm_set_epi16(2217, -5352, 2217, -5352, + 2217, -5352, 2217, -5352); + const __m128i k12000_plus_one = _mm_set1_epi32(12000 + (1 << 16)); + const __m128i k51000 = _mm_set1_epi32(51000); + + // Same operations are done on the (0,3) and (1,2) pairs. + // a3 = v0 - v3 + // a2 = v1 - v2 + const __m128i a32 = _mm_sub_epi16(*v01, *v32); + const __m128i a22 = _mm_unpackhi_epi64(a32, a32); + + const __m128i b23 = _mm_unpacklo_epi16(a22, a32); + const __m128i c1 = _mm_madd_epi16(b23, k5352_2217); + const __m128i c3 = _mm_madd_epi16(b23, k2217_5352); + const __m128i d1 = _mm_add_epi32(c1, k12000_plus_one); + const __m128i d3 = _mm_add_epi32(c3, k51000); + const __m128i e1 = _mm_srai_epi32(d1, 16); + const __m128i e3 = _mm_srai_epi32(d3, 16); + // f1 = ((b3 * 5352 + b2 * 2217 + 12000) >> 16) + // f3 = ((b3 * 2217 - b2 * 5352 + 51000) >> 16) + const __m128i f1 = _mm_packs_epi32(e1, e1); + const __m128i f3 = _mm_packs_epi32(e3, e3); + // g1 = f1 + (a3 != 0); + // The compare will return (0xffff, 0) for (==0, !=0). To turn that into the + // desired (0, 1), we add one earlier through k12000_plus_one. + // -> g1 = f1 + 1 - (a3 == 0) + const __m128i g1 = _mm_add_epi16(f1, _mm_cmpeq_epi16(a32, zero)); + + // a0 = v0 + v3 + // a1 = v1 + v2 + const __m128i a01 = _mm_add_epi16(*v01, *v32); + const __m128i a01_plus_7 = _mm_add_epi16(a01, seven); + const __m128i a11 = _mm_unpackhi_epi64(a01, a01); + const __m128i c0 = _mm_add_epi16(a01_plus_7, a11); + const __m128i c2 = _mm_sub_epi16(a01_plus_7, a11); + // d0 = (a0 + a1 + 7) >> 4; + // d2 = (a0 - a1 + 7) >> 4; + const __m128i d0 = _mm_srai_epi16(c0, 4); + const __m128i d2 = _mm_srai_epi16(c2, 4); + + const __m128i d0_g1 = _mm_unpacklo_epi64(d0, g1); + const __m128i d2_f3 = _mm_unpacklo_epi64(d2, f3); + _mm_storeu_si128((__m128i*)&out[0], d0_g1); + _mm_storeu_si128((__m128i*)&out[8], d2_f3); +} + +static void FTransform(const uint8_t* src, const uint8_t* ref, int16_t* out) { + const __m128i zero = _mm_setzero_si128(); + // Load src. + const __m128i src0 = _mm_loadl_epi64((const __m128i*)&src[0 * BPS]); + const __m128i src1 = _mm_loadl_epi64((const __m128i*)&src[1 * BPS]); + const __m128i src2 = _mm_loadl_epi64((const __m128i*)&src[2 * BPS]); + const __m128i src3 = _mm_loadl_epi64((const __m128i*)&src[3 * BPS]); + // 00 01 02 03 * + // 10 11 12 13 * + // 20 21 22 23 * + // 30 31 32 33 * + // Shuffle. + const __m128i src_0 = _mm_unpacklo_epi16(src0, src1); + const __m128i src_1 = _mm_unpacklo_epi16(src2, src3); + // 00 01 10 11 02 03 12 13 * * ... + // 20 21 30 31 22 22 32 33 * * ... + + // Load ref. + const __m128i ref0 = _mm_loadl_epi64((const __m128i*)&ref[0 * BPS]); + const __m128i ref1 = _mm_loadl_epi64((const __m128i*)&ref[1 * BPS]); + const __m128i ref2 = _mm_loadl_epi64((const __m128i*)&ref[2 * BPS]); + const __m128i ref3 = _mm_loadl_epi64((const __m128i*)&ref[3 * BPS]); + const __m128i ref_0 = _mm_unpacklo_epi16(ref0, ref1); + const __m128i ref_1 = _mm_unpacklo_epi16(ref2, ref3); + + // Convert both to 16 bit. + const __m128i src_0_16b = _mm_unpacklo_epi8(src_0, zero); + const __m128i src_1_16b = _mm_unpacklo_epi8(src_1, zero); + const __m128i ref_0_16b = _mm_unpacklo_epi8(ref_0, zero); + const __m128i ref_1_16b = _mm_unpacklo_epi8(ref_1, zero); + + // Compute the difference. + const __m128i row01 = _mm_sub_epi16(src_0_16b, ref_0_16b); + const __m128i row23 = _mm_sub_epi16(src_1_16b, ref_1_16b); __m128i v01, v32; - - // Difference between src and ref and initial transpose. - { - // Load src and convert to 16b. - const __m128i src0 = _mm_loadl_epi64((__m128i*)&src[0 * BPS]); - const __m128i src1 = _mm_loadl_epi64((__m128i*)&src[1 * BPS]); - const __m128i src2 = _mm_loadl_epi64((__m128i*)&src[2 * BPS]); - const __m128i src3 = _mm_loadl_epi64((__m128i*)&src[3 * BPS]); - const __m128i src_0 = _mm_unpacklo_epi8(src0, zero); - const __m128i src_1 = _mm_unpacklo_epi8(src1, zero); - const __m128i src_2 = _mm_unpacklo_epi8(src2, zero); - const __m128i src_3 = _mm_unpacklo_epi8(src3, zero); - // Load ref and convert to 16b. - const __m128i ref0 = _mm_loadl_epi64((__m128i*)&ref[0 * BPS]); - const __m128i ref1 = _mm_loadl_epi64((__m128i*)&ref[1 * BPS]); - const __m128i ref2 = _mm_loadl_epi64((__m128i*)&ref[2 * BPS]); - const __m128i ref3 = _mm_loadl_epi64((__m128i*)&ref[3 * BPS]); - const __m128i ref_0 = _mm_unpacklo_epi8(ref0, zero); - const __m128i ref_1 = _mm_unpacklo_epi8(ref1, zero); - const __m128i ref_2 = _mm_unpacklo_epi8(ref2, zero); - const __m128i ref_3 = _mm_unpacklo_epi8(ref3, zero); - // Compute difference. -> 00 01 02 03 00 00 00 00 - const __m128i diff0 = _mm_sub_epi16(src_0, ref_0); - const __m128i diff1 = _mm_sub_epi16(src_1, ref_1); - const __m128i diff2 = _mm_sub_epi16(src_2, ref_2); - const __m128i diff3 = _mm_sub_epi16(src_3, ref_3); - - - // Unpack and shuffle - // 00 01 02 03 0 0 0 0 - // 10 11 12 13 0 0 0 0 - // 20 21 22 23 0 0 0 0 - // 30 31 32 33 0 0 0 0 - const __m128i shuf01 = _mm_unpacklo_epi32(diff0, diff1); - const __m128i shuf23 = _mm_unpacklo_epi32(diff2, diff3); - // 00 01 10 11 02 03 12 13 - // 20 21 30 31 22 23 32 33 - const __m128i shuf01_p = - _mm_shufflehi_epi16(shuf01, _MM_SHUFFLE(2, 3, 0, 1)); - const __m128i shuf23_p = - _mm_shufflehi_epi16(shuf23, _MM_SHUFFLE(2, 3, 0, 1)); - // 00 01 10 11 03 02 13 12 - // 20 21 30 31 23 22 33 32 - const __m128i s01 = _mm_unpacklo_epi64(shuf01_p, shuf23_p); - const __m128i s32 = _mm_unpackhi_epi64(shuf01_p, shuf23_p); - // 00 01 10 11 20 21 30 31 - // 03 02 13 12 23 22 33 32 - const __m128i a01 = _mm_add_epi16(s01, s32); - const __m128i a32 = _mm_sub_epi16(s01, s32); - // [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ] - // [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] - - const __m128i tmp0 = _mm_madd_epi16(a01, k88p); // [ (a0 + a1) << 3, ... ] - const __m128i tmp2 = _mm_madd_epi16(a01, k88m); // [ (a0 - a1) << 3, ... ] - const __m128i tmp1_1 = _mm_madd_epi16(a32, k5352_2217p); - const __m128i tmp3_1 = _mm_madd_epi16(a32, k5352_2217m); - const __m128i tmp1_2 = _mm_add_epi32(tmp1_1, k1812); - const __m128i tmp3_2 = _mm_add_epi32(tmp3_1, k937); - const __m128i tmp1 = _mm_srai_epi32(tmp1_2, 9); - const __m128i tmp3 = _mm_srai_epi32(tmp3_2, 9); - const __m128i s03 = _mm_packs_epi32(tmp0, tmp2); - const __m128i s12 = _mm_packs_epi32(tmp1, tmp3); - const __m128i s_lo = _mm_unpacklo_epi16(s03, s12); // 0 1 0 1 0 1... - const __m128i s_hi = _mm_unpackhi_epi16(s03, s12); // 2 3 2 3 2 3 - const __m128i v23 = _mm_unpackhi_epi32(s_lo, s_hi); - v01 = _mm_unpacklo_epi32(s_lo, s_hi); - v32 = _mm_shuffle_epi32(v23, _MM_SHUFFLE(1, 0, 3, 2)); // 3 2 3 2 3 2.. - } + // First pass + FTransformPass1(&row01, &row23, &v01, &v32); // Second pass + FTransformPass2(&v01, &v32, out); +} + +static void FTransform2(const uint8_t* src, const uint8_t* ref, int16_t* out) { + const __m128i zero = _mm_setzero_si128(); + + // Load src and convert to 16b. + const __m128i src0 = _mm_loadl_epi64((const __m128i*)&src[0 * BPS]); + const __m128i src1 = _mm_loadl_epi64((const __m128i*)&src[1 * BPS]); + const __m128i src2 = _mm_loadl_epi64((const __m128i*)&src[2 * BPS]); + const __m128i src3 = _mm_loadl_epi64((const __m128i*)&src[3 * BPS]); + const __m128i src_0 = _mm_unpacklo_epi8(src0, zero); + const __m128i src_1 = _mm_unpacklo_epi8(src1, zero); + const __m128i src_2 = _mm_unpacklo_epi8(src2, zero); + const __m128i src_3 = _mm_unpacklo_epi8(src3, zero); + // Load ref and convert to 16b. + const __m128i ref0 = _mm_loadl_epi64((const __m128i*)&ref[0 * BPS]); + const __m128i ref1 = _mm_loadl_epi64((const __m128i*)&ref[1 * BPS]); + const __m128i ref2 = _mm_loadl_epi64((const __m128i*)&ref[2 * BPS]); + const __m128i ref3 = _mm_loadl_epi64((const __m128i*)&ref[3 * BPS]); + const __m128i ref_0 = _mm_unpacklo_epi8(ref0, zero); + const __m128i ref_1 = _mm_unpacklo_epi8(ref1, zero); + const __m128i ref_2 = _mm_unpacklo_epi8(ref2, zero); + const __m128i ref_3 = _mm_unpacklo_epi8(ref3, zero); + // Compute difference. -> 00 01 02 03 00' 01' 02' 03' + const __m128i diff0 = _mm_sub_epi16(src_0, ref_0); + const __m128i diff1 = _mm_sub_epi16(src_1, ref_1); + const __m128i diff2 = _mm_sub_epi16(src_2, ref_2); + const __m128i diff3 = _mm_sub_epi16(src_3, ref_3); + + // Unpack and shuffle + // 00 01 02 03 0 0 0 0 + // 10 11 12 13 0 0 0 0 + // 20 21 22 23 0 0 0 0 + // 30 31 32 33 0 0 0 0 + const __m128i shuf01l = _mm_unpacklo_epi32(diff0, diff1); + const __m128i shuf23l = _mm_unpacklo_epi32(diff2, diff3); + const __m128i shuf01h = _mm_unpackhi_epi32(diff0, diff1); + const __m128i shuf23h = _mm_unpackhi_epi32(diff2, diff3); + __m128i v01l, v32l; + __m128i v01h, v32h; + + // First pass + FTransformPass1(&shuf01l, &shuf23l, &v01l, &v32l); + FTransformPass1(&shuf01h, &shuf23h, &v01h, &v32h); + + // Second pass + FTransformPass2(&v01l, &v32l, out + 0); + FTransformPass2(&v01h, &v32h, out + 16); +} + +static void FTransformWHTRow(const int16_t* const in, __m128i* const out) { + const __m128i kMult = _mm_set_epi16(-1, 1, -1, 1, 1, 1, 1, 1); + const __m128i src0 = _mm_loadl_epi64((__m128i*)&in[0 * 16]); + const __m128i src1 = _mm_loadl_epi64((__m128i*)&in[1 * 16]); + const __m128i src2 = _mm_loadl_epi64((__m128i*)&in[2 * 16]); + const __m128i src3 = _mm_loadl_epi64((__m128i*)&in[3 * 16]); + const __m128i A01 = _mm_unpacklo_epi16(src0, src1); // A0 A1 | ... + const __m128i A23 = _mm_unpacklo_epi16(src2, src3); // A2 A3 | ... + const __m128i B0 = _mm_adds_epi16(A01, A23); // a0 | a1 | ... + const __m128i B1 = _mm_subs_epi16(A01, A23); // a3 | a2 | ... + const __m128i C0 = _mm_unpacklo_epi32(B0, B1); // a0 | a1 | a3 | a2 | ... + const __m128i C1 = _mm_unpacklo_epi32(B1, B0); // a3 | a2 | a0 | a1 | ... + const __m128i D = _mm_unpacklo_epi64(C0, C1); // a0 a1 a3 a2 a3 a2 a0 a1 + *out = _mm_madd_epi16(D, kMult); +} + +static void FTransformWHT(const int16_t* in, int16_t* out) { + // Input is 12b signed. + __m128i row0, row1, row2, row3; + // Rows are 14b signed. + FTransformWHTRow(in + 0 * 64, &row0); + FTransformWHTRow(in + 1 * 64, &row1); + FTransformWHTRow(in + 2 * 64, &row2); + FTransformWHTRow(in + 3 * 64, &row3); + { - // Same operations are done on the (0,3) and (1,2) pairs. - // a0 = v0 + v3 - // a1 = v1 + v2 - // a3 = v0 - v3 - // a2 = v1 - v2 - const __m128i a01 = _mm_add_epi16(v01, v32); - const __m128i a32 = _mm_sub_epi16(v01, v32); - const __m128i a11 = _mm_unpackhi_epi64(a01, a01); - const __m128i a22 = _mm_unpackhi_epi64(a32, a32); - const __m128i a01_plus_7 = _mm_add_epi16(a01, seven); + // The a* are 15b signed. + const __m128i a0 = _mm_add_epi32(row0, row2); + const __m128i a1 = _mm_add_epi32(row1, row3); + const __m128i a2 = _mm_sub_epi32(row1, row3); + const __m128i a3 = _mm_sub_epi32(row0, row2); + const __m128i a0a3 = _mm_packs_epi32(a0, a3); + const __m128i a1a2 = _mm_packs_epi32(a1, a2); - // d0 = (a0 + a1 + 7) >> 4; - // d2 = (a0 - a1 + 7) >> 4; - const __m128i c0 = _mm_add_epi16(a01_plus_7, a11); - const __m128i c2 = _mm_sub_epi16(a01_plus_7, a11); - const __m128i d0 = _mm_srai_epi16(c0, 4); - const __m128i d2 = _mm_srai_epi16(c2, 4); + // The b* are 16b signed. + const __m128i b0b1 = _mm_add_epi16(a0a3, a1a2); + const __m128i b3b2 = _mm_sub_epi16(a0a3, a1a2); + const __m128i tmp_b2b3 = _mm_unpackhi_epi64(b3b2, b3b2); + const __m128i b2b3 = _mm_unpacklo_epi64(tmp_b2b3, b3b2); - // f1 = ((b3 * 5352 + b2 * 2217 + 12000) >> 16) - // f3 = ((b3 * 2217 - b2 * 5352 + 51000) >> 16) - const __m128i b23 = _mm_unpacklo_epi16(a22, a32); - const __m128i c1 = _mm_madd_epi16(b23, k5352_2217); - const __m128i c3 = _mm_madd_epi16(b23, k2217_5352); - const __m128i d1 = _mm_add_epi32(c1, k12000_plus_one); - const __m128i d3 = _mm_add_epi32(c3, k51000); - const __m128i e1 = _mm_srai_epi32(d1, 16); - const __m128i e3 = _mm_srai_epi32(d3, 16); - const __m128i f1 = _mm_packs_epi32(e1, e1); - const __m128i f3 = _mm_packs_epi32(e3, e3); - // f1 = f1 + (a3 != 0); - // The compare will return (0xffff, 0) for (==0, !=0). To turn that into the - // desired (0, 1), we add one earlier through k12000_plus_one. - // -> f1 = f1 + 1 - (a3 == 0) - const __m128i g1 = _mm_add_epi16(f1, _mm_cmpeq_epi16(a32, zero)); - - _mm_storel_epi64((__m128i*)&out[ 0], d0); - _mm_storel_epi64((__m128i*)&out[ 4], g1); - _mm_storel_epi64((__m128i*)&out[ 8], d2); - _mm_storel_epi64((__m128i*)&out[12], f3); + _mm_storeu_si128((__m128i*)&out[0], _mm_srai_epi16(b0b1, 1)); + _mm_storeu_si128((__m128i*)&out[8], _mm_srai_epi16(b2b3, 1)); } } -static void FTransformWHTSSE2(const int16_t* in, int16_t* out) { - int16_t tmp[16]; +//------------------------------------------------------------------------------ +// Compute susceptibility based on DCT-coeff histograms: +// the higher, the "easier" the macroblock is to compress. + +static void CollectHistogram(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + const __m128i zero = _mm_setzero_si128(); + const __m128i max_coeff_thresh = _mm_set1_epi16(MAX_COEFF_THRESH); + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + int k; + + FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + + // Convert coefficients to bin (within out[]). + { + // Load. + const __m128i out0 = _mm_loadu_si128((__m128i*)&out[0]); + const __m128i out1 = _mm_loadu_si128((__m128i*)&out[8]); + const __m128i d0 = _mm_sub_epi16(zero, out0); + const __m128i d1 = _mm_sub_epi16(zero, out1); + const __m128i abs0 = _mm_max_epi16(out0, d0); // abs(v), 16b + const __m128i abs1 = _mm_max_epi16(out1, d1); + // v = abs(out) >> 3 + const __m128i v0 = _mm_srai_epi16(abs0, 3); + const __m128i v1 = _mm_srai_epi16(abs1, 3); + // bin = min(v, MAX_COEFF_THRESH) + const __m128i bin0 = _mm_min_epi16(v0, max_coeff_thresh); + const __m128i bin1 = _mm_min_epi16(v1, max_coeff_thresh); + // Store. + _mm_storeu_si128((__m128i*)&out[0], bin0); + _mm_storeu_si128((__m128i*)&out[8], bin1); + } + + // Convert coefficients to bin. + for (k = 0; k < 16; ++k) { + ++distribution[out[k]]; + } + } + VP8SetHistogramData(distribution, histo); +} + +//------------------------------------------------------------------------------ +// Intra predictions + +// helper for chroma-DC predictions +static WEBP_INLINE void Put8x8uv(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8(v); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), values); + } +} + +static WEBP_INLINE void Put16(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8(v); + for (j = 0; j < 16; ++j) { + _mm_store_si128((__m128i*)(dst + j * BPS), values); + } +} + +static WEBP_INLINE void Fill(uint8_t* dst, int value, int size) { + if (size == 4) { + int j; + for (j = 0; j < 4; ++j) { + memset(dst + j * BPS, value, 4); + } + } else if (size == 8) { + Put8x8uv(value, dst); + } else { + Put16(value, dst); + } +} + +static WEBP_INLINE void VE8uv(uint8_t* dst, const uint8_t* top) { + int j; + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), top_values); + } +} + +static WEBP_INLINE void VE16(uint8_t* dst, const uint8_t* top) { + const __m128i top_values = _mm_load_si128((const __m128i*)top); + int j; + for (j = 0; j < 16; ++j) { + _mm_store_si128((__m128i*)(dst + j * BPS), top_values); + } +} + +static WEBP_INLINE void VerticalPred(uint8_t* dst, + const uint8_t* top, int size) { + if (top != NULL) { + if (size == 8) { + VE8uv(dst, top); + } else { + VE16(dst, top); + } + } else { + Fill(dst, 127, size); + } +} + +static WEBP_INLINE void HE8uv(uint8_t* dst, const uint8_t* left) { + int j; + for (j = 0; j < 8; ++j) { + const __m128i values = _mm_set1_epi8(left[j]); + _mm_storel_epi64((__m128i*)dst, values); + dst += BPS; + } +} + +static WEBP_INLINE void HE16(uint8_t* dst, const uint8_t* left) { + int j; + for (j = 0; j < 16; ++j) { + const __m128i values = _mm_set1_epi8(left[j]); + _mm_store_si128((__m128i*)dst, values); + dst += BPS; + } +} + +static WEBP_INLINE void HorizontalPred(uint8_t* dst, + const uint8_t* left, int size) { + if (left != NULL) { + if (size == 8) { + HE8uv(dst, left); + } else { + HE16(dst, left); + } + } else { + Fill(dst, 129, size); + } +} + +static WEBP_INLINE void TM(uint8_t* dst, const uint8_t* left, + const uint8_t* top, int size) { + const __m128i zero = _mm_setzero_si128(); + int y; + if (size == 8) { + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + for (y = 0; y < 8; ++y, dst += BPS) { + const int val = left[y] - left[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + _mm_storel_epi64((__m128i*)dst, out); + } + } else { + const __m128i top_values = _mm_load_si128((const __m128i*)top); + const __m128i top_base_0 = _mm_unpacklo_epi8(top_values, zero); + const __m128i top_base_1 = _mm_unpackhi_epi8(top_values, zero); + for (y = 0; y < 16; ++y, dst += BPS) { + const int val = left[y] - left[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out_0 = _mm_add_epi16(base, top_base_0); + const __m128i out_1 = _mm_add_epi16(base, top_base_1); + const __m128i out = _mm_packus_epi16(out_0, out_1); + _mm_store_si128((__m128i*)dst, out); + } + } +} + +static WEBP_INLINE void TrueMotion(uint8_t* dst, const uint8_t* left, + const uint8_t* top, int size) { + if (left != NULL) { + if (top != NULL) { + TM(dst, left, top, size); + } else { + HorizontalPred(dst, left, size); + } + } else { + // true motion without left samples (hence: with default 129 value) + // is equivalent to VE prediction where you just copy the top samples. + // Note that if top samples are not available, the default value is + // then 129, and not 127 as in the VerticalPred case. + if (top != NULL) { + VerticalPred(dst, top, size); + } else { + Fill(dst, 129, size); + } + } +} + +static WEBP_INLINE void DC8uv(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + const __m128i left_values = _mm_loadl_epi64((const __m128i*)left); + const __m128i combined = _mm_unpacklo_epi64(top_values, left_values); + const int DC = VP8HorizontalAdd8b(&combined) + 8; + Put8x8uv(DC >> 4, dst); +} + +static WEBP_INLINE void DC8uvNoLeft(uint8_t* dst, const uint8_t* top) { + const __m128i zero = _mm_setzero_si128(); + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + const __m128i sum = _mm_sad_epu8(top_values, zero); + const int DC = _mm_cvtsi128_si32(sum) + 4; + Put8x8uv(DC >> 3, dst); +} + +static WEBP_INLINE void DC8uvNoTop(uint8_t* dst, const uint8_t* left) { + // 'left' is contiguous so we can reuse the top summation. + DC8uvNoLeft(dst, left); +} + +static WEBP_INLINE void DC8uvNoTopLeft(uint8_t* dst) { + Put8x8uv(0x80, dst); +} + +static WEBP_INLINE void DC8uvMode(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + if (top != NULL) { + if (left != NULL) { // top and left present + DC8uv(dst, left, top); + } else { // top, but no left + DC8uvNoLeft(dst, top); + } + } else if (left != NULL) { // left but no top + DC8uvNoTop(dst, left); + } else { // no top, no left, nothing. + DC8uvNoTopLeft(dst); + } +} + +static WEBP_INLINE void DC16(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + const __m128i top_row = _mm_load_si128((const __m128i*)top); + const __m128i left_row = _mm_load_si128((const __m128i*)left); + const int DC = + VP8HorizontalAdd8b(&top_row) + VP8HorizontalAdd8b(&left_row) + 16; + Put16(DC >> 5, dst); +} + +static WEBP_INLINE void DC16NoLeft(uint8_t* dst, const uint8_t* top) { + const __m128i top_row = _mm_load_si128((const __m128i*)top); + const int DC = VP8HorizontalAdd8b(&top_row) + 8; + Put16(DC >> 4, dst); +} + +static WEBP_INLINE void DC16NoTop(uint8_t* dst, const uint8_t* left) { + // 'left' is contiguous so we can reuse the top summation. + DC16NoLeft(dst, left); +} + +static WEBP_INLINE void DC16NoTopLeft(uint8_t* dst) { + Put16(0x80, dst); +} + +static WEBP_INLINE void DC16Mode(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + if (top != NULL) { + if (left != NULL) { // top and left present + DC16(dst, left, top); + } else { // top, but no left + DC16NoLeft(dst, top); + } + } else if (left != NULL) { // left but no top + DC16NoTop(dst, left); + } else { // no top, no left, nothing. + DC16NoTopLeft(dst); + } +} + +//------------------------------------------------------------------------------ +// 4x4 predictions + +#define DST(x, y) dst[(x) + (y) * BPS] +#define AVG3(a, b, c) (((a) + 2 * (b) + (c) + 2) >> 2) +#define AVG2(a, b) (((a) + (b) + 1) >> 1) + +// We use the following 8b-arithmetic tricks: +// (a + 2 * b + c + 2) >> 2 = (AC + b + 1) >> 1 +// where: AC = (a + c) >> 1 = [(a + c + 1) >> 1] - [(a^c) & 1] +// and: +// (a + 2 * b + c + 2) >> 2 = (AB + BC + 1) >> 1 - (ab|bc)&lsb +// where: AC = (a + b + 1) >> 1, BC = (b + c + 1) >> 1 +// and ab = a ^ b, bc = b ^ c, lsb = (AC^BC)&1 + +static WEBP_INLINE void VE4(uint8_t* dst, const uint8_t* top) { // vertical + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(top - 1)); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i a = _mm_avg_epu8(ABCDEFGH, CDEFGH00); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGH00), one); + const __m128i b = _mm_subs_epu8(a, lsb); + const __m128i avg = _mm_avg_epu8(b, BCDEFGH0); + const uint32_t vals = _mm_cvtsi128_si32(avg); int i; - for (i = 0; i < 4; ++i, in += 64) { - const int a0 = (in[0 * 16] + in[2 * 16]); - const int a1 = (in[1 * 16] + in[3 * 16]); - const int a2 = (in[1 * 16] - in[3 * 16]); - const int a3 = (in[0 * 16] - in[2 * 16]); - tmp[0 + i * 4] = a0 + a1; - tmp[1 + i * 4] = a3 + a2; - tmp[2 + i * 4] = a3 - a2; - tmp[3 + i * 4] = a0 - a1; + for (i = 0; i < 4; ++i) { + WebPUint32ToMem(dst + i * BPS, vals); } - { - const __m128i src0 = _mm_loadl_epi64((__m128i*)&tmp[0]); - const __m128i src1 = _mm_loadl_epi64((__m128i*)&tmp[4]); - const __m128i src2 = _mm_loadl_epi64((__m128i*)&tmp[8]); - const __m128i src3 = _mm_loadl_epi64((__m128i*)&tmp[12]); - const __m128i a0 = _mm_add_epi16(src0, src2); - const __m128i a1 = _mm_add_epi16(src1, src3); - const __m128i a2 = _mm_sub_epi16(src1, src3); - const __m128i a3 = _mm_sub_epi16(src0, src2); - const __m128i b0 = _mm_srai_epi16(_mm_adds_epi16(a0, a1), 1); - const __m128i b1 = _mm_srai_epi16(_mm_adds_epi16(a3, a2), 1); - const __m128i b2 = _mm_srai_epi16(_mm_subs_epi16(a3, a2), 1); - const __m128i b3 = _mm_srai_epi16(_mm_subs_epi16(a0, a1), 1); - _mm_storel_epi64((__m128i*)&out[ 0], b0); - _mm_storel_epi64((__m128i*)&out[ 4], b1); - _mm_storel_epi64((__m128i*)&out[ 8], b2); - _mm_storel_epi64((__m128i*)&out[12], b3); +} + +static WEBP_INLINE void HE4(uint8_t* dst, const uint8_t* top) { // horizontal + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(X, I, J)); + WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(I, J, K)); + WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(J, K, L)); + WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(K, L, L)); +} + +static WEBP_INLINE void DC4(uint8_t* dst, const uint8_t* top) { + uint32_t dc = 4; + int i; + for (i = 0; i < 4; ++i) dc += top[i] + top[-5 + i]; + Fill(dst, dc >> 3, 4); +} + +static WEBP_INLINE void LD4(uint8_t* dst, const uint8_t* top) { // Down-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((const __m128i*)top); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i CDEFGHH0 = _mm_insert_epi16(CDEFGH00, top[7], 3); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, CDEFGHH0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGHH0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, BCDEFGH0); + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +static WEBP_INLINE void VR4(uint8_t* dst, + const uint8_t* top) { // Vertical-Right + const __m128i one = _mm_set1_epi8(1); + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int X = top[-1]; + const __m128i XABCD = _mm_loadl_epi64((const __m128i*)(top - 1)); + const __m128i ABCD0 = _mm_srli_si128(XABCD, 1); + const __m128i abcd = _mm_avg_epu8(XABCD, ABCD0); + const __m128i _XABCD = _mm_slli_si128(XABCD, 1); + const __m128i IXABCD = _mm_insert_epi16(_XABCD, I | (X << 8), 0); + const __m128i avg1 = _mm_avg_epu8(IXABCD, ABCD0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(IXABCD, ABCD0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i efgh = _mm_avg_epu8(avg2, XABCD); + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcd )); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( efgh )); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(abcd, 1))); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(efgh, 1))); + + // these two are hard to implement in SSE2, so we keep the C-version: + DST(0, 2) = AVG3(J, I, X); + DST(0, 3) = AVG3(K, J, I); +} + +static WEBP_INLINE void VL4(uint8_t* dst, + const uint8_t* top) { // Vertical-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((const __m128i*)top); + const __m128i BCDEFGH_ = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH__ = _mm_srli_si128(ABCDEFGH, 2); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, BCDEFGH_); + const __m128i avg2 = _mm_avg_epu8(CDEFGH__, BCDEFGH_); + const __m128i avg3 = _mm_avg_epu8(avg1, avg2); + const __m128i lsb1 = _mm_and_si128(_mm_xor_si128(avg1, avg2), one); + const __m128i ab = _mm_xor_si128(ABCDEFGH, BCDEFGH_); + const __m128i bc = _mm_xor_si128(CDEFGH__, BCDEFGH_); + const __m128i abbc = _mm_or_si128(ab, bc); + const __m128i lsb2 = _mm_and_si128(abbc, lsb1); + const __m128i avg4 = _mm_subs_epu8(avg3, lsb2); + const uint32_t extra_out = _mm_cvtsi128_si32(_mm_srli_si128(avg4, 4)); + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( avg1 )); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( avg4 )); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg1, 1))); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg4, 1))); + + // these two are hard to get and irregular + DST(3, 2) = (extra_out >> 0) & 0xff; + DST(3, 3) = (extra_out >> 8) & 0xff; +} + +static WEBP_INLINE void RD4(uint8_t* dst, const uint8_t* top) { // Down-right + const __m128i one = _mm_set1_epi8(1); + const __m128i LKJIXABC = _mm_loadl_epi64((const __m128i*)(top - 5)); + const __m128i LKJIXABCD = _mm_insert_epi16(LKJIXABC, top[3], 4); + const __m128i KJIXABCD_ = _mm_srli_si128(LKJIXABCD, 1); + const __m128i JIXABCD__ = _mm_srli_si128(LKJIXABCD, 2); + const __m128i avg1 = _mm_avg_epu8(JIXABCD__, LKJIXABCD); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(JIXABCD__, LKJIXABCD), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, KJIXABCD_); + WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +static WEBP_INLINE void HU4(uint8_t* dst, const uint8_t* top) { + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + DST(0, 0) = AVG2(I, J); + DST(2, 0) = DST(0, 1) = AVG2(J, K); + DST(2, 1) = DST(0, 2) = AVG2(K, L); + DST(1, 0) = AVG3(I, J, K); + DST(3, 0) = DST(1, 1) = AVG3(J, K, L); + DST(3, 1) = DST(1, 2) = AVG3(K, L, L); + DST(3, 2) = DST(2, 2) = + DST(0, 3) = DST(1, 3) = DST(2, 3) = DST(3, 3) = L; +} + +static WEBP_INLINE void HD4(uint8_t* dst, const uint8_t* top) { + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + + DST(0, 0) = DST(2, 1) = AVG2(I, X); + DST(0, 1) = DST(2, 2) = AVG2(J, I); + DST(0, 2) = DST(2, 3) = AVG2(K, J); + DST(0, 3) = AVG2(L, K); + + DST(3, 0) = AVG3(A, B, C); + DST(2, 0) = AVG3(X, A, B); + DST(1, 0) = DST(3, 1) = AVG3(I, X, A); + DST(1, 1) = DST(3, 2) = AVG3(J, I, X); + DST(1, 2) = DST(3, 3) = AVG3(K, J, I); + DST(1, 3) = AVG3(L, K, J); +} + +static WEBP_INLINE void TM4(uint8_t* dst, const uint8_t* top) { + const __m128i zero = _mm_setzero_si128(); + const __m128i top_values = _mm_cvtsi32_si128(WebPMemToUint32(top)); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + int y; + for (y = 0; y < 4; ++y, dst += BPS) { + const int val = top[-2 - y] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + WebPUint32ToMem(dst, _mm_cvtsi128_si32(out)); } } +#undef DST +#undef AVG3 +#undef AVG2 + +//------------------------------------------------------------------------------ +// luma 4x4 prediction + +// Left samples are top[-5 .. -2], top_left is top[-1], top are +// located at top[0..3], and top right is top[4..7] +static void Intra4Preds(uint8_t* dst, const uint8_t* top) { + DC4(I4DC4 + dst, top); + TM4(I4TM4 + dst, top); + VE4(I4VE4 + dst, top); + HE4(I4HE4 + dst, top); + RD4(I4RD4 + dst, top); + VR4(I4VR4 + dst, top); + LD4(I4LD4 + dst, top); + VL4(I4VL4 + dst, top); + HD4(I4HD4 + dst, top); + HU4(I4HU4 + dst, top); +} + +//------------------------------------------------------------------------------ +// Chroma 8x8 prediction (paragraph 12.2) + +static void IntraChromaPreds(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + // U block + DC8uvMode(C8DC8 + dst, left, top); + VerticalPred(C8VE8 + dst, top, 8); + HorizontalPred(C8HE8 + dst, left, 8); + TrueMotion(C8TM8 + dst, left, top, 8); + // V block + dst += 8; + if (top != NULL) top += 8; + if (left != NULL) left += 16; + DC8uvMode(C8DC8 + dst, left, top); + VerticalPred(C8VE8 + dst, top, 8); + HorizontalPred(C8HE8 + dst, left, 8); + TrueMotion(C8TM8 + dst, left, top, 8); +} + +//------------------------------------------------------------------------------ +// luma 16x16 prediction (paragraph 12.3) + +static void Intra16Preds(uint8_t* dst, + const uint8_t* left, const uint8_t* top) { + DC16Mode(I16DC16 + dst, left, top); + VerticalPred(I16VE16 + dst, top, 16); + HorizontalPred(I16HE16 + dst, left, 16); + TrueMotion(I16TM16 + dst, left, top, 16); +} + //------------------------------------------------------------------------------ // Metric -static int SSE_Nx4SSE2(const uint8_t* a, const uint8_t* b, - int num_quads, int do_16) { +static WEBP_INLINE void SubtractAndAccumulate(const __m128i a, const __m128i b, + __m128i* const sum) { + // take abs(a-b) in 8b + const __m128i a_b = _mm_subs_epu8(a, b); + const __m128i b_a = _mm_subs_epu8(b, a); + const __m128i abs_a_b = _mm_or_si128(a_b, b_a); + // zero-extend to 16b const __m128i zero = _mm_setzero_si128(); - __m128i sum1 = zero; - __m128i sum2 = zero; + const __m128i C0 = _mm_unpacklo_epi8(abs_a_b, zero); + const __m128i C1 = _mm_unpackhi_epi8(abs_a_b, zero); + // multiply with self + const __m128i sum1 = _mm_madd_epi16(C0, C0); + const __m128i sum2 = _mm_madd_epi16(C1, C1); + *sum = _mm_add_epi32(sum1, sum2); +} - while (num_quads-- > 0) { - // Note: for the !do_16 case, we read 16 pixels instead of 8 but that's ok, - // thanks to buffer over-allocation to that effect. - const __m128i a0 = _mm_loadu_si128((__m128i*)&a[BPS * 0]); - const __m128i a1 = _mm_loadu_si128((__m128i*)&a[BPS * 1]); - const __m128i a2 = _mm_loadu_si128((__m128i*)&a[BPS * 2]); - const __m128i a3 = _mm_loadu_si128((__m128i*)&a[BPS * 3]); - const __m128i b0 = _mm_loadu_si128((__m128i*)&b[BPS * 0]); - const __m128i b1 = _mm_loadu_si128((__m128i*)&b[BPS * 1]); - const __m128i b2 = _mm_loadu_si128((__m128i*)&b[BPS * 2]); - const __m128i b3 = _mm_loadu_si128((__m128i*)&b[BPS * 3]); +static WEBP_INLINE int SSE_16xN(const uint8_t* a, const uint8_t* b, + int num_pairs) { + __m128i sum = _mm_setzero_si128(); + int32_t tmp[4]; + int i; - // compute clip0(a-b) and clip0(b-a) - const __m128i a0p = _mm_subs_epu8(a0, b0); - const __m128i a0m = _mm_subs_epu8(b0, a0); - const __m128i a1p = _mm_subs_epu8(a1, b1); - const __m128i a1m = _mm_subs_epu8(b1, a1); - const __m128i a2p = _mm_subs_epu8(a2, b2); - const __m128i a2m = _mm_subs_epu8(b2, a2); - const __m128i a3p = _mm_subs_epu8(a3, b3); - const __m128i a3m = _mm_subs_epu8(b3, a3); - - // compute |a-b| with 8b arithmetic as clip0(a-b) | clip0(b-a) - const __m128i diff0 = _mm_or_si128(a0p, a0m); - const __m128i diff1 = _mm_or_si128(a1p, a1m); - const __m128i diff2 = _mm_or_si128(a2p, a2m); - const __m128i diff3 = _mm_or_si128(a3p, a3m); - - // unpack (only four operations, instead of eight) - const __m128i low0 = _mm_unpacklo_epi8(diff0, zero); - const __m128i low1 = _mm_unpacklo_epi8(diff1, zero); - const __m128i low2 = _mm_unpacklo_epi8(diff2, zero); - const __m128i low3 = _mm_unpacklo_epi8(diff3, zero); - - // multiply with self - const __m128i low_madd0 = _mm_madd_epi16(low0, low0); - const __m128i low_madd1 = _mm_madd_epi16(low1, low1); - const __m128i low_madd2 = _mm_madd_epi16(low2, low2); - const __m128i low_madd3 = _mm_madd_epi16(low3, low3); - - // collect in a cascading way - const __m128i low_sum0 = _mm_add_epi32(low_madd0, low_madd1); - const __m128i low_sum1 = _mm_add_epi32(low_madd2, low_madd3); - sum1 = _mm_add_epi32(sum1, low_sum0); - sum2 = _mm_add_epi32(sum2, low_sum1); - - if (do_16) { // if necessary, process the higher 8 bytes similarly - const __m128i hi0 = _mm_unpackhi_epi8(diff0, zero); - const __m128i hi1 = _mm_unpackhi_epi8(diff1, zero); - const __m128i hi2 = _mm_unpackhi_epi8(diff2, zero); - const __m128i hi3 = _mm_unpackhi_epi8(diff3, zero); - - const __m128i hi_madd0 = _mm_madd_epi16(hi0, hi0); - const __m128i hi_madd1 = _mm_madd_epi16(hi1, hi1); - const __m128i hi_madd2 = _mm_madd_epi16(hi2, hi2); - const __m128i hi_madd3 = _mm_madd_epi16(hi3, hi3); - const __m128i hi_sum0 = _mm_add_epi32(hi_madd0, hi_madd1); - const __m128i hi_sum1 = _mm_add_epi32(hi_madd2, hi_madd3); - sum1 = _mm_add_epi32(sum1, hi_sum0); - sum2 = _mm_add_epi32(sum2, hi_sum1); - } - a += 4 * BPS; - b += 4 * BPS; + for (i = 0; i < num_pairs; ++i) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)&a[BPS * 0]); + const __m128i b0 = _mm_loadu_si128((const __m128i*)&b[BPS * 0]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&a[BPS * 1]); + const __m128i b1 = _mm_loadu_si128((const __m128i*)&b[BPS * 1]); + __m128i sum1, sum2; + SubtractAndAccumulate(a0, b0, &sum1); + SubtractAndAccumulate(a1, b1, &sum2); + sum = _mm_add_epi32(sum, _mm_add_epi32(sum1, sum2)); + a += 2 * BPS; + b += 2 * BPS; } - { - int32_t tmp[4]; - const __m128i sum = _mm_add_epi32(sum1, sum2); - _mm_storeu_si128((__m128i*)tmp, sum); - return (tmp[3] + tmp[2] + tmp[1] + tmp[0]); + _mm_storeu_si128((__m128i*)tmp, sum); + return (tmp[3] + tmp[2] + tmp[1] + tmp[0]); +} + +static int SSE16x16(const uint8_t* a, const uint8_t* b) { + return SSE_16xN(a, b, 8); +} + +static int SSE16x8(const uint8_t* a, const uint8_t* b) { + return SSE_16xN(a, b, 4); +} + +#define LOAD_8x16b(ptr) \ + _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i*)(ptr)), zero) + +static int SSE8x8(const uint8_t* a, const uint8_t* b) { + const __m128i zero = _mm_setzero_si128(); + int num_pairs = 4; + __m128i sum = zero; + int32_t tmp[4]; + while (num_pairs-- > 0) { + const __m128i a0 = LOAD_8x16b(&a[BPS * 0]); + const __m128i a1 = LOAD_8x16b(&a[BPS * 1]); + const __m128i b0 = LOAD_8x16b(&b[BPS * 0]); + const __m128i b1 = LOAD_8x16b(&b[BPS * 1]); + // subtract + const __m128i c0 = _mm_subs_epi16(a0, b0); + const __m128i c1 = _mm_subs_epi16(a1, b1); + // multiply/accumulate with self + const __m128i d0 = _mm_madd_epi16(c0, c0); + const __m128i d1 = _mm_madd_epi16(c1, c1); + // collect + const __m128i sum01 = _mm_add_epi32(d0, d1); + sum = _mm_add_epi32(sum, sum01); + a += 2 * BPS; + b += 2 * BPS; } + _mm_storeu_si128((__m128i*)tmp, sum); + return (tmp[3] + tmp[2] + tmp[1] + tmp[0]); } +#undef LOAD_8x16b -static int SSE16x16SSE2(const uint8_t* a, const uint8_t* b) { - return SSE_Nx4SSE2(a, b, 4, 1); -} - -static int SSE16x8SSE2(const uint8_t* a, const uint8_t* b) { - return SSE_Nx4SSE2(a, b, 2, 1); -} - -static int SSE8x8SSE2(const uint8_t* a, const uint8_t* b) { - return SSE_Nx4SSE2(a, b, 2, 0); -} - -static int SSE4x4SSE2(const uint8_t* a, const uint8_t* b) { +static int SSE4x4(const uint8_t* a, const uint8_t* b) { const __m128i zero = _mm_setzero_si128(); // Load values. Note that we read 8 pixels instead of 4, // but the a/b buffers are over-allocated to that effect. - const __m128i a0 = _mm_loadl_epi64((__m128i*)&a[BPS * 0]); - const __m128i a1 = _mm_loadl_epi64((__m128i*)&a[BPS * 1]); - const __m128i a2 = _mm_loadl_epi64((__m128i*)&a[BPS * 2]); - const __m128i a3 = _mm_loadl_epi64((__m128i*)&a[BPS * 3]); - const __m128i b0 = _mm_loadl_epi64((__m128i*)&b[BPS * 0]); - const __m128i b1 = _mm_loadl_epi64((__m128i*)&b[BPS * 1]); - const __m128i b2 = _mm_loadl_epi64((__m128i*)&b[BPS * 2]); - const __m128i b3 = _mm_loadl_epi64((__m128i*)&b[BPS * 3]); - - // Combine pair of lines and convert to 16b. + const __m128i a0 = _mm_loadl_epi64((const __m128i*)&a[BPS * 0]); + const __m128i a1 = _mm_loadl_epi64((const __m128i*)&a[BPS * 1]); + const __m128i a2 = _mm_loadl_epi64((const __m128i*)&a[BPS * 2]); + const __m128i a3 = _mm_loadl_epi64((const __m128i*)&a[BPS * 3]); + const __m128i b0 = _mm_loadl_epi64((const __m128i*)&b[BPS * 0]); + const __m128i b1 = _mm_loadl_epi64((const __m128i*)&b[BPS * 1]); + const __m128i b2 = _mm_loadl_epi64((const __m128i*)&b[BPS * 2]); + const __m128i b3 = _mm_loadl_epi64((const __m128i*)&b[BPS * 3]); + // Combine pair of lines. const __m128i a01 = _mm_unpacklo_epi32(a0, a1); const __m128i a23 = _mm_unpacklo_epi32(a2, a3); const __m128i b01 = _mm_unpacklo_epi32(b0, b1); const __m128i b23 = _mm_unpacklo_epi32(b2, b3); + // Convert to 16b. const __m128i a01s = _mm_unpacklo_epi8(a01, zero); const __m128i a23s = _mm_unpacklo_epi8(a23, zero); const __m128i b01s = _mm_unpacklo_epi8(b01, zero); const __m128i b23s = _mm_unpacklo_epi8(b23, zero); - - // Compute differences; (a-b)^2 = (abs(a-b))^2 = (sat8(a-b) + sat8(b-a))^2 - // TODO(cduvivier): Dissassemble and figure out why this is fastest. We don't - // need absolute values, there is no need to do calculation - // in 8bit as we are already in 16bit, ... Yet this is what - // benchmarks the fastest! - const __m128i d0 = _mm_subs_epu8(a01s, b01s); - const __m128i d1 = _mm_subs_epu8(b01s, a01s); - const __m128i d2 = _mm_subs_epu8(a23s, b23s); - const __m128i d3 = _mm_subs_epu8(b23s, a23s); - - // Square and add them all together. - const __m128i madd0 = _mm_madd_epi16(d0, d0); - const __m128i madd1 = _mm_madd_epi16(d1, d1); - const __m128i madd2 = _mm_madd_epi16(d2, d2); - const __m128i madd3 = _mm_madd_epi16(d3, d3); - const __m128i sum0 = _mm_add_epi32(madd0, madd1); - const __m128i sum1 = _mm_add_epi32(madd2, madd3); - const __m128i sum2 = _mm_add_epi32(sum0, sum1); + // subtract, square and accumulate + const __m128i d0 = _mm_subs_epi16(a01s, b01s); + const __m128i d1 = _mm_subs_epi16(a23s, b23s); + const __m128i e0 = _mm_madd_epi16(d0, d0); + const __m128i e1 = _mm_madd_epi16(d1, d1); + const __m128i sum = _mm_add_epi32(e0, e1); int32_t tmp[4]; - _mm_storeu_si128((__m128i*)tmp, sum2); + _mm_storeu_si128((__m128i*)tmp, sum); return (tmp[3] + tmp[2] + tmp[1] + tmp[0]); } +//------------------------------------------------------------------------------ + +static void Mean16x4(const uint8_t* ref, uint32_t dc[4]) { + const __m128i mask = _mm_set1_epi16(0x00ff); + const __m128i a0 = _mm_loadu_si128((const __m128i*)&ref[BPS * 0]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&ref[BPS * 1]); + const __m128i a2 = _mm_loadu_si128((const __m128i*)&ref[BPS * 2]); + const __m128i a3 = _mm_loadu_si128((const __m128i*)&ref[BPS * 3]); + const __m128i b0 = _mm_srli_epi16(a0, 8); // hi byte + const __m128i b1 = _mm_srli_epi16(a1, 8); + const __m128i b2 = _mm_srli_epi16(a2, 8); + const __m128i b3 = _mm_srli_epi16(a3, 8); + const __m128i c0 = _mm_and_si128(a0, mask); // lo byte + const __m128i c1 = _mm_and_si128(a1, mask); + const __m128i c2 = _mm_and_si128(a2, mask); + const __m128i c3 = _mm_and_si128(a3, mask); + const __m128i d0 = _mm_add_epi32(b0, c0); + const __m128i d1 = _mm_add_epi32(b1, c1); + const __m128i d2 = _mm_add_epi32(b2, c2); + const __m128i d3 = _mm_add_epi32(b3, c3); + const __m128i e0 = _mm_add_epi32(d0, d1); + const __m128i e1 = _mm_add_epi32(d2, d3); + const __m128i f0 = _mm_add_epi32(e0, e1); + uint16_t tmp[8]; + _mm_storeu_si128((__m128i*)tmp, f0); + dc[0] = tmp[0] + tmp[1]; + dc[1] = tmp[2] + tmp[3]; + dc[2] = tmp[4] + tmp[5]; + dc[3] = tmp[6] + tmp[7]; +} + //------------------------------------------------------------------------------ // Texture distortion // @@ -636,57 +1084,42 @@ static int SSE4x4SSE2(const uint8_t* a, const uint8_t* b) { // reconstructed samples. // Hadamard transform -// Returns the difference between the weighted sum of the absolute value of -// transformed coefficients. -static int TTransformSSE2(const uint8_t* inA, const uint8_t* inB, - const uint16_t* const w) { +// Returns the weighted sum of the absolute value of transformed coefficients. +// w[] contains a row-major 4 by 4 symmetric matrix. +static int TTransform(const uint8_t* inA, const uint8_t* inB, + const uint16_t* const w) { int32_t sum[4]; __m128i tmp_0, tmp_1, tmp_2, tmp_3; const __m128i zero = _mm_setzero_si128(); - // Load, combine and tranpose inputs. + // Load and combine inputs. { - const __m128i inA_0 = _mm_loadl_epi64((__m128i*)&inA[BPS * 0]); - const __m128i inA_1 = _mm_loadl_epi64((__m128i*)&inA[BPS * 1]); - const __m128i inA_2 = _mm_loadl_epi64((__m128i*)&inA[BPS * 2]); - const __m128i inA_3 = _mm_loadl_epi64((__m128i*)&inA[BPS * 3]); - const __m128i inB_0 = _mm_loadl_epi64((__m128i*)&inB[BPS * 0]); - const __m128i inB_1 = _mm_loadl_epi64((__m128i*)&inB[BPS * 1]); - const __m128i inB_2 = _mm_loadl_epi64((__m128i*)&inB[BPS * 2]); - const __m128i inB_3 = _mm_loadl_epi64((__m128i*)&inB[BPS * 3]); + const __m128i inA_0 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 0]); + const __m128i inA_1 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 1]); + const __m128i inA_2 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 2]); + const __m128i inA_3 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 3]); + const __m128i inB_0 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 0]); + const __m128i inB_1 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 1]); + const __m128i inB_2 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 2]); + const __m128i inB_3 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 3]); // Combine inA and inB (we'll do two transforms in parallel). - const __m128i inAB_0 = _mm_unpacklo_epi8(inA_0, inB_0); - const __m128i inAB_1 = _mm_unpacklo_epi8(inA_1, inB_1); - const __m128i inAB_2 = _mm_unpacklo_epi8(inA_2, inB_2); - const __m128i inAB_3 = _mm_unpacklo_epi8(inA_3, inB_3); - // a00 b00 a01 b01 a02 b03 a03 b03 0 0 0 0 0 0 0 0 - // a10 b10 a11 b11 a12 b12 a13 b13 0 0 0 0 0 0 0 0 - // a20 b20 a21 b21 a22 b22 a23 b23 0 0 0 0 0 0 0 0 - // a30 b30 a31 b31 a32 b32 a33 b33 0 0 0 0 0 0 0 0 - - // Transpose the two 4x4, discarding the filling zeroes. - const __m128i transpose0_0 = _mm_unpacklo_epi8(inAB_0, inAB_2); - const __m128i transpose0_1 = _mm_unpacklo_epi8(inAB_1, inAB_3); - // a00 a20 b00 b20 a01 a21 b01 b21 a02 a22 b02 b22 a03 a23 b03 b23 - // a10 a30 b10 b30 a11 a31 b11 b31 a12 a32 b12 b32 a13 a33 b13 b33 - const __m128i transpose1_0 = _mm_unpacklo_epi8(transpose0_0, transpose0_1); - const __m128i transpose1_1 = _mm_unpackhi_epi8(transpose0_0, transpose0_1); - // a00 a10 a20 a30 b00 b10 b20 b30 a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 a03 a13 a23 a33 b03 b13 b23 b33 - - // Convert to 16b. - tmp_0 = _mm_unpacklo_epi8(transpose1_0, zero); - tmp_1 = _mm_unpackhi_epi8(transpose1_0, zero); - tmp_2 = _mm_unpacklo_epi8(transpose1_1, zero); - tmp_3 = _mm_unpackhi_epi8(transpose1_1, zero); - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 + const __m128i inAB_0 = _mm_unpacklo_epi32(inA_0, inB_0); + const __m128i inAB_1 = _mm_unpacklo_epi32(inA_1, inB_1); + const __m128i inAB_2 = _mm_unpacklo_epi32(inA_2, inB_2); + const __m128i inAB_3 = _mm_unpacklo_epi32(inA_3, inB_3); + tmp_0 = _mm_unpacklo_epi8(inAB_0, zero); + tmp_1 = _mm_unpacklo_epi8(inAB_1, zero); + tmp_2 = _mm_unpacklo_epi8(inAB_2, zero); + tmp_3 = _mm_unpacklo_epi8(inAB_3, zero); + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 } - // Horizontal pass and subsequent transpose. + // Vertical pass first to avoid a transpose (vertical and horizontal passes + // are commutative because w/kWeightY is symmetric) and subsequent transpose. { // Calculate a and b (two 4x4 at once). const __m128i a0 = _mm_add_epi16(tmp_0, tmp_2); @@ -703,39 +1136,14 @@ static int TTransformSSE2(const uint8_t* inA, const uint8_t* inB, // a30 a31 a32 a33 b30 b31 b32 b33 // Transpose the two 4x4. - const __m128i transpose0_0 = _mm_unpacklo_epi16(b0, b1); - const __m128i transpose0_1 = _mm_unpacklo_epi16(b2, b3); - const __m128i transpose0_2 = _mm_unpackhi_epi16(b0, b1); - const __m128i transpose0_3 = _mm_unpackhi_epi16(b2, b3); - // a00 a10 a01 a11 a02 a12 a03 a13 - // a20 a30 a21 a31 a22 a32 a23 a33 - // b00 b10 b01 b11 b02 b12 b03 b13 - // b20 b30 b21 b31 b22 b32 b23 b33 - const __m128i transpose1_0 = _mm_unpacklo_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_1 = _mm_unpacklo_epi32(transpose0_2, transpose0_3); - const __m128i transpose1_2 = _mm_unpackhi_epi32(transpose0_0, transpose0_1); - const __m128i transpose1_3 = _mm_unpackhi_epi32(transpose0_2, transpose0_3); - // a00 a10 a20 a30 a01 a11 a21 a31 - // b00 b10 b20 b30 b01 b11 b21 b31 - // a02 a12 a22 a32 a03 a13 a23 a33 - // b02 b12 a22 b32 b03 b13 b23 b33 - tmp_0 = _mm_unpacklo_epi64(transpose1_0, transpose1_1); - tmp_1 = _mm_unpackhi_epi64(transpose1_0, transpose1_1); - tmp_2 = _mm_unpacklo_epi64(transpose1_2, transpose1_3); - tmp_3 = _mm_unpackhi_epi64(transpose1_2, transpose1_3); - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 + VP8Transpose_2_4x4_16b(&b0, &b1, &b2, &b3, &tmp_0, &tmp_1, &tmp_2, &tmp_3); } - // Vertical pass and difference of weighted sums. + // Horizontal pass and difference of weighted sums. { // Load all inputs. - // TODO(cduvivier): Make variable declarations and allocations aligned so - // we can use _mm_load_si128 instead of _mm_loadu_si128. - const __m128i w_0 = _mm_loadu_si128((__m128i*)&w[0]); - const __m128i w_8 = _mm_loadu_si128((__m128i*)&w[8]); + const __m128i w_0 = _mm_loadu_si128((const __m128i*)&w[0]); + const __m128i w_8 = _mm_loadu_si128((const __m128i*)&w[8]); // Calculate a and b (two 4x4 at once). const __m128i a0 = _mm_add_epi16(tmp_0, tmp_2); @@ -754,21 +1162,14 @@ static int TTransformSSE2(const uint8_t* inA, const uint8_t* inB, __m128i B_b2 = _mm_unpackhi_epi64(b2, b3); { - // sign(b) = b >> 15 (0x0000 if positive, 0xffff if negative) - const __m128i sign_A_b0 = _mm_srai_epi16(A_b0, 15); - const __m128i sign_A_b2 = _mm_srai_epi16(A_b2, 15); - const __m128i sign_B_b0 = _mm_srai_epi16(B_b0, 15); - const __m128i sign_B_b2 = _mm_srai_epi16(B_b2, 15); - - // b = abs(b) = (b ^ sign) - sign - A_b0 = _mm_xor_si128(A_b0, sign_A_b0); - A_b2 = _mm_xor_si128(A_b2, sign_A_b2); - B_b0 = _mm_xor_si128(B_b0, sign_B_b0); - B_b2 = _mm_xor_si128(B_b2, sign_B_b2); - A_b0 = _mm_sub_epi16(A_b0, sign_A_b0); - A_b2 = _mm_sub_epi16(A_b2, sign_A_b2); - B_b0 = _mm_sub_epi16(B_b0, sign_B_b0); - B_b2 = _mm_sub_epi16(B_b2, sign_B_b2); + const __m128i d0 = _mm_sub_epi16(zero, A_b0); + const __m128i d1 = _mm_sub_epi16(zero, A_b2); + const __m128i d2 = _mm_sub_epi16(zero, B_b0); + const __m128i d3 = _mm_sub_epi16(zero, B_b2); + A_b0 = _mm_max_epi16(A_b0, d0); // abs(v), 16b + A_b2 = _mm_max_epi16(A_b2, d1); + B_b0 = _mm_max_epi16(B_b0, d2); + B_b2 = _mm_max_epi16(B_b2, d3); } // weighted sums @@ -786,19 +1187,19 @@ static int TTransformSSE2(const uint8_t* inA, const uint8_t* inB, return sum[0] + sum[1] + sum[2] + sum[3]; } -static int Disto4x4SSE2(const uint8_t* const a, const uint8_t* const b, - const uint16_t* const w) { - const int diff_sum = TTransformSSE2(a, b, w); +static int Disto4x4(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + const int diff_sum = TTransform(a, b, w); return abs(diff_sum) >> 5; } -static int Disto16x16SSE2(const uint8_t* const a, const uint8_t* const b, - const uint16_t* const w) { +static int Disto16x16(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { int D = 0; int x, y; for (y = 0; y < 16 * BPS; y += 4 * BPS) { for (x = 0; x < 16; x += 4) { - D += Disto4x4SSE2(a + x + y, b + x + y, w); + D += Disto4x4(a + x + y, b + x + y, w); } } return D; @@ -808,9 +1209,9 @@ static int Disto16x16SSE2(const uint8_t* const a, const uint8_t* const b, // Quantization // -// Simple quantization -static int QuantizeBlockSSE2(int16_t in[16], int16_t out[16], - int n, const VP8Matrix* const mtx) { +static WEBP_INLINE int DoQuantizeBlock(int16_t in[16], int16_t out[16], + const uint16_t* const sharpen, + const VP8Matrix* const mtx) { const __m128i max_coeff_2047 = _mm_set1_epi16(MAX_LEVEL); const __m128i zero = _mm_setzero_si128(); __m128i coeff0, coeff8; @@ -818,24 +1219,16 @@ static int QuantizeBlockSSE2(int16_t in[16], int16_t out[16], __m128i packed_out; // Load all inputs. - // TODO(cduvivier): Make variable declarations and allocations aligned so that - // we can use _mm_load_si128 instead of _mm_loadu_si128. __m128i in0 = _mm_loadu_si128((__m128i*)&in[0]); __m128i in8 = _mm_loadu_si128((__m128i*)&in[8]); - const __m128i sharpen0 = _mm_loadu_si128((__m128i*)&mtx->sharpen_[0]); - const __m128i sharpen8 = _mm_loadu_si128((__m128i*)&mtx->sharpen_[8]); - const __m128i iq0 = _mm_loadu_si128((__m128i*)&mtx->iq_[0]); - const __m128i iq8 = _mm_loadu_si128((__m128i*)&mtx->iq_[8]); - const __m128i bias0 = _mm_loadu_si128((__m128i*)&mtx->bias_[0]); - const __m128i bias8 = _mm_loadu_si128((__m128i*)&mtx->bias_[8]); - const __m128i q0 = _mm_loadu_si128((__m128i*)&mtx->q_[0]); - const __m128i q8 = _mm_loadu_si128((__m128i*)&mtx->q_[8]); - const __m128i zthresh0 = _mm_loadu_si128((__m128i*)&mtx->zthresh_[0]); - const __m128i zthresh8 = _mm_loadu_si128((__m128i*)&mtx->zthresh_[8]); + const __m128i iq0 = _mm_loadu_si128((const __m128i*)&mtx->iq_[0]); + const __m128i iq8 = _mm_loadu_si128((const __m128i*)&mtx->iq_[8]); + const __m128i q0 = _mm_loadu_si128((const __m128i*)&mtx->q_[0]); + const __m128i q8 = _mm_loadu_si128((const __m128i*)&mtx->q_[8]); - // sign(in) = in >> 15 (0x0000 if positive, 0xffff if negative) - const __m128i sign0 = _mm_srai_epi16(in0, 15); - const __m128i sign8 = _mm_srai_epi16(in8, 15); + // extract sign(in) (0x0000 if positive, 0xffff if negative) + const __m128i sign0 = _mm_cmpgt_epi16(zero, in0); + const __m128i sign8 = _mm_cmpgt_epi16(zero, in8); // coeff = abs(in) = (in ^ sign) - sign coeff0 = _mm_xor_si128(in0, sign0); @@ -844,32 +1237,35 @@ static int QuantizeBlockSSE2(int16_t in[16], int16_t out[16], coeff8 = _mm_sub_epi16(coeff8, sign8); // coeff = abs(in) + sharpen - coeff0 = _mm_add_epi16(coeff0, sharpen0); - coeff8 = _mm_add_epi16(coeff8, sharpen8); + if (sharpen != NULL) { + const __m128i sharpen0 = _mm_loadu_si128((const __m128i*)&sharpen[0]); + const __m128i sharpen8 = _mm_loadu_si128((const __m128i*)&sharpen[8]); + coeff0 = _mm_add_epi16(coeff0, sharpen0); + coeff8 = _mm_add_epi16(coeff8, sharpen8); + } - // out = (coeff * iQ + B) >> QFIX; + // out = (coeff * iQ + B) >> QFIX { // doing calculations with 32b precision (QFIX=17) // out = (coeff * iQ) - __m128i coeff_iQ0H = _mm_mulhi_epu16(coeff0, iq0); - __m128i coeff_iQ0L = _mm_mullo_epi16(coeff0, iq0); - __m128i coeff_iQ8H = _mm_mulhi_epu16(coeff8, iq8); - __m128i coeff_iQ8L = _mm_mullo_epi16(coeff8, iq8); + const __m128i coeff_iQ0H = _mm_mulhi_epu16(coeff0, iq0); + const __m128i coeff_iQ0L = _mm_mullo_epi16(coeff0, iq0); + const __m128i coeff_iQ8H = _mm_mulhi_epu16(coeff8, iq8); + const __m128i coeff_iQ8L = _mm_mullo_epi16(coeff8, iq8); __m128i out_00 = _mm_unpacklo_epi16(coeff_iQ0L, coeff_iQ0H); __m128i out_04 = _mm_unpackhi_epi16(coeff_iQ0L, coeff_iQ0H); __m128i out_08 = _mm_unpacklo_epi16(coeff_iQ8L, coeff_iQ8H); __m128i out_12 = _mm_unpackhi_epi16(coeff_iQ8L, coeff_iQ8H); - // expand bias from 16b to 32b - __m128i bias_00 = _mm_unpacklo_epi16(bias0, zero); - __m128i bias_04 = _mm_unpackhi_epi16(bias0, zero); - __m128i bias_08 = _mm_unpacklo_epi16(bias8, zero); - __m128i bias_12 = _mm_unpackhi_epi16(bias8, zero); // out = (coeff * iQ + B) + const __m128i bias_00 = _mm_loadu_si128((const __m128i*)&mtx->bias_[0]); + const __m128i bias_04 = _mm_loadu_si128((const __m128i*)&mtx->bias_[4]); + const __m128i bias_08 = _mm_loadu_si128((const __m128i*)&mtx->bias_[8]); + const __m128i bias_12 = _mm_loadu_si128((const __m128i*)&mtx->bias_[12]); out_00 = _mm_add_epi32(out_00, bias_00); out_04 = _mm_add_epi32(out_04, bias_04); out_08 = _mm_add_epi32(out_08, bias_08); out_12 = _mm_add_epi32(out_12, bias_12); - // out = (coeff * iQ + B) >> QFIX; + // out = QUANTDIV(coeff, iQ, B, QFIX) out_00 = _mm_srai_epi32(out_00, QFIX); out_04 = _mm_srai_epi32(out_04, QFIX); out_08 = _mm_srai_epi32(out_08, QFIX); @@ -894,17 +1290,8 @@ static int QuantizeBlockSSE2(int16_t in[16], int16_t out[16], in0 = _mm_mullo_epi16(out0, q0); in8 = _mm_mullo_epi16(out8, q8); - // if (coeff <= mtx->zthresh_) {in=0; out=0;} - { - __m128i cmp0 = _mm_cmpgt_epi16(coeff0, zthresh0); - __m128i cmp8 = _mm_cmpgt_epi16(coeff8, zthresh8); - in0 = _mm_and_si128(in0, cmp0); - in8 = _mm_and_si128(in8, cmp8); - _mm_storeu_si128((__m128i*)&in[0], in0); - _mm_storeu_si128((__m128i*)&in[8], in8); - out0 = _mm_and_si128(out0, cmp0); - out8 = _mm_and_si128(out8, cmp8); - } + _mm_storeu_si128((__m128i*)&in[0], in0); + _mm_storeu_si128((__m128i*)&in[8], in8); // zigzag the output before storing it. // @@ -931,39 +1318,167 @@ static int QuantizeBlockSSE2(int16_t in[16], int16_t out[16], } // detect if all 'out' values are zeroes or not - { - int32_t tmp[4]; - _mm_storeu_si128((__m128i*)tmp, packed_out); - if (n) { - tmp[0] &= ~0xff; - } - return (tmp[3] || tmp[2] || tmp[1] || tmp[0]); - } + return (_mm_movemask_epi8(_mm_cmpeq_epi8(packed_out, zero)) != 0xffff); } -#endif // WEBP_USE_SSE2 +static int QuantizeBlock(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + return DoQuantizeBlock(in, out, &mtx->sharpen_[0], mtx); +} + +static int QuantizeBlockWHT(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + return DoQuantizeBlock(in, out, NULL, mtx); +} + +static int Quantize2Blocks(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + const uint16_t* const sharpen = &mtx->sharpen_[0]; + nz = DoQuantizeBlock(in + 0 * 16, out + 0 * 16, sharpen, mtx) << 0; + nz |= DoQuantizeBlock(in + 1 * 16, out + 1 * 16, sharpen, mtx) << 1; + return nz; +} //------------------------------------------------------------------------------ // Entry point extern void VP8EncDspInitSSE2(void); -void VP8EncDspInitSSE2(void) { -#if defined(WEBP_USE_SSE2) - VP8CollectHistogram = CollectHistogramSSE2; - VP8EncQuantizeBlock = QuantizeBlockSSE2; - VP8ITransform = ITransformSSE2; - VP8FTransform = FTransformSSE2; - VP8FTransformWHT = FTransformWHTSSE2; - VP8SSE16x16 = SSE16x16SSE2; - VP8SSE16x8 = SSE16x8SSE2; - VP8SSE8x8 = SSE8x8SSE2; - VP8SSE4x4 = SSE4x4SSE2; - VP8TDisto4x4 = Disto4x4SSE2; - VP8TDisto16x16 = Disto16x16SSE2; -#endif // WEBP_USE_SSE2 +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitSSE2(void) { + VP8CollectHistogram = CollectHistogram; + VP8EncPredLuma16 = Intra16Preds; + VP8EncPredChroma8 = IntraChromaPreds; + VP8EncPredLuma4 = Intra4Preds; + VP8EncQuantizeBlock = QuantizeBlock; + VP8EncQuantize2Blocks = Quantize2Blocks; + VP8EncQuantizeBlockWHT = QuantizeBlockWHT; + VP8ITransform = ITransform; + VP8FTransform = FTransform; + VP8FTransform2 = FTransform2; + VP8FTransformWHT = FTransformWHT; + VP8SSE16x16 = SSE16x16; + VP8SSE16x8 = SSE16x8; + VP8SSE8x8 = SSE8x8; + VP8SSE4x4 = SSE4x4; + VP8TDisto4x4 = Disto4x4; + VP8TDisto16x16 = Disto16x16; + VP8Mean16x4 = Mean16x4; } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif +//------------------------------------------------------------------------------ +// SSIM / PSNR entry point (TODO(skal): move to its own file later) + +static uint32_t AccumulateSSE_SSE2(const uint8_t* src1, + const uint8_t* src2, int len) { + int i = 0; + uint32_t sse2 = 0; + if (len >= 16) { + const int limit = len - 32; + int32_t tmp[4]; + __m128i sum1; + __m128i sum = _mm_setzero_si128(); + __m128i a0 = _mm_loadu_si128((const __m128i*)&src1[i]); + __m128i b0 = _mm_loadu_si128((const __m128i*)&src2[i]); + i += 16; + while (i <= limit) { + const __m128i a1 = _mm_loadu_si128((const __m128i*)&src1[i]); + const __m128i b1 = _mm_loadu_si128((const __m128i*)&src2[i]); + __m128i sum2; + i += 16; + SubtractAndAccumulate(a0, b0, &sum1); + sum = _mm_add_epi32(sum, sum1); + a0 = _mm_loadu_si128((const __m128i*)&src1[i]); + b0 = _mm_loadu_si128((const __m128i*)&src2[i]); + i += 16; + SubtractAndAccumulate(a1, b1, &sum2); + sum = _mm_add_epi32(sum, sum2); + } + SubtractAndAccumulate(a0, b0, &sum1); + sum = _mm_add_epi32(sum, sum1); + _mm_storeu_si128((__m128i*)tmp, sum); + sse2 += (tmp[3] + tmp[2] + tmp[1] + tmp[0]); + } + + for (; i < len; ++i) { + const int32_t diff = src1[i] - src2[i]; + sse2 += diff * diff; + } + return sse2; +} + +static uint32_t HorizontalAdd16b(const __m128i* const m) { + uint16_t tmp[8]; + const __m128i a = _mm_srli_si128(*m, 8); + const __m128i b = _mm_add_epi16(*m, a); + _mm_storeu_si128((__m128i*)tmp, b); + return (uint32_t)tmp[3] + tmp[2] + tmp[1] + tmp[0]; +} + +static uint32_t HorizontalAdd32b(const __m128i* const m) { + const __m128i a = _mm_srli_si128(*m, 8); + const __m128i b = _mm_add_epi32(*m, a); + const __m128i c = _mm_add_epi32(b, _mm_srli_si128(b, 4)); + return (uint32_t)_mm_cvtsi128_si32(c); +} + +static const uint16_t kWeight[] = { 1, 2, 3, 4, 3, 2, 1, 0 }; + +#define ACCUMULATE_ROW(WEIGHT) do { \ + /* compute row weight (Wx * Wy) */ \ + const __m128i Wy = _mm_set1_epi16((WEIGHT)); \ + const __m128i W = _mm_mullo_epi16(Wx, Wy); \ + /* process 8 bytes at a time (7 bytes, actually) */ \ + const __m128i a0 = _mm_loadl_epi64((const __m128i*)src1); \ + const __m128i b0 = _mm_loadl_epi64((const __m128i*)src2); \ + /* convert to 16b and multiply by weight */ \ + const __m128i a1 = _mm_unpacklo_epi8(a0, zero); \ + const __m128i b1 = _mm_unpacklo_epi8(b0, zero); \ + const __m128i wa1 = _mm_mullo_epi16(a1, W); \ + const __m128i wb1 = _mm_mullo_epi16(b1, W); \ + /* accumulate */ \ + xm = _mm_add_epi16(xm, wa1); \ + ym = _mm_add_epi16(ym, wb1); \ + xxm = _mm_add_epi32(xxm, _mm_madd_epi16(a1, wa1)); \ + xym = _mm_add_epi32(xym, _mm_madd_epi16(a1, wb1)); \ + yym = _mm_add_epi32(yym, _mm_madd_epi16(b1, wb1)); \ + src1 += stride1; \ + src2 += stride2; \ +} while (0) + +static double SSIMGet_SSE2(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2) { + VP8DistoStats stats; + const __m128i zero = _mm_setzero_si128(); + __m128i xm = zero, ym = zero; // 16b accums + __m128i xxm = zero, yym = zero, xym = zero; // 32b accum + const __m128i Wx = _mm_loadu_si128((const __m128i*)kWeight); + assert(2 * VP8_SSIM_KERNEL + 1 == 7); + ACCUMULATE_ROW(1); + ACCUMULATE_ROW(2); + ACCUMULATE_ROW(3); + ACCUMULATE_ROW(4); + ACCUMULATE_ROW(3); + ACCUMULATE_ROW(2); + ACCUMULATE_ROW(1); + stats.xm = HorizontalAdd16b(&xm); + stats.ym = HorizontalAdd16b(&ym); + stats.xxm = HorizontalAdd32b(&xxm); + stats.xym = HorizontalAdd32b(&xym); + stats.yym = HorizontalAdd32b(&yym); + return VP8SSIMFromStats(&stats); +} + +extern void VP8SSIMDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8SSIMDspInitSSE2(void) { + VP8AccumulateSSE = AccumulateSSE_SSE2; + VP8SSIMGet = SSIMGet_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8EncDspInitSSE2) +WEBP_DSP_INIT_STUB(VP8SSIMDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/dsp/enc_sse41.c b/3rdparty/libwebp/dsp/enc_sse41.c new file mode 100644 index 0000000000..e32086d9fd --- /dev/null +++ b/3rdparty/libwebp/dsp/enc_sse41.c @@ -0,0 +1,339 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4 version of some encoding functions. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE41) +#include +#include // for abs() + +#include "./common_sse2.h" +#include "../enc/vp8i_enc.h" + +//------------------------------------------------------------------------------ +// Compute susceptibility based on DCT-coeff histograms. + +static void CollectHistogram(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + const __m128i max_coeff_thresh = _mm_set1_epi16(MAX_COEFF_THRESH); + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + int k; + + VP8FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + + // Convert coefficients to bin (within out[]). + { + // Load. + const __m128i out0 = _mm_loadu_si128((__m128i*)&out[0]); + const __m128i out1 = _mm_loadu_si128((__m128i*)&out[8]); + // v = abs(out) >> 3 + const __m128i abs0 = _mm_abs_epi16(out0); + const __m128i abs1 = _mm_abs_epi16(out1); + const __m128i v0 = _mm_srai_epi16(abs0, 3); + const __m128i v1 = _mm_srai_epi16(abs1, 3); + // bin = min(v, MAX_COEFF_THRESH) + const __m128i bin0 = _mm_min_epi16(v0, max_coeff_thresh); + const __m128i bin1 = _mm_min_epi16(v1, max_coeff_thresh); + // Store. + _mm_storeu_si128((__m128i*)&out[0], bin0); + _mm_storeu_si128((__m128i*)&out[8], bin1); + } + + // Convert coefficients to bin. + for (k = 0; k < 16; ++k) { + ++distribution[out[k]]; + } + } + VP8SetHistogramData(distribution, histo); +} + +//------------------------------------------------------------------------------ +// Texture distortion +// +// We try to match the spectral content (weighted) between source and +// reconstructed samples. + +// Hadamard transform +// Returns the weighted sum of the absolute value of transformed coefficients. +// w[] contains a row-major 4 by 4 symmetric matrix. +static int TTransform(const uint8_t* inA, const uint8_t* inB, + const uint16_t* const w) { + int32_t sum[4]; + __m128i tmp_0, tmp_1, tmp_2, tmp_3; + + // Load and combine inputs. + { + const __m128i inA_0 = _mm_loadu_si128((const __m128i*)&inA[BPS * 0]); + const __m128i inA_1 = _mm_loadu_si128((const __m128i*)&inA[BPS * 1]); + const __m128i inA_2 = _mm_loadu_si128((const __m128i*)&inA[BPS * 2]); + // In SSE4.1, with gcc 4.8 at least (maybe other versions), + // _mm_loadu_si128 is faster than _mm_loadl_epi64. But for the last lump + // of inA and inB, _mm_loadl_epi64 is still used not to have an out of + // bound read. + const __m128i inA_3 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 3]); + const __m128i inB_0 = _mm_loadu_si128((const __m128i*)&inB[BPS * 0]); + const __m128i inB_1 = _mm_loadu_si128((const __m128i*)&inB[BPS * 1]); + const __m128i inB_2 = _mm_loadu_si128((const __m128i*)&inB[BPS * 2]); + const __m128i inB_3 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 3]); + + // Combine inA and inB (we'll do two transforms in parallel). + const __m128i inAB_0 = _mm_unpacklo_epi32(inA_0, inB_0); + const __m128i inAB_1 = _mm_unpacklo_epi32(inA_1, inB_1); + const __m128i inAB_2 = _mm_unpacklo_epi32(inA_2, inB_2); + const __m128i inAB_3 = _mm_unpacklo_epi32(inA_3, inB_3); + tmp_0 = _mm_cvtepu8_epi16(inAB_0); + tmp_1 = _mm_cvtepu8_epi16(inAB_1); + tmp_2 = _mm_cvtepu8_epi16(inAB_2); + tmp_3 = _mm_cvtepu8_epi16(inAB_3); + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + } + + // Vertical pass first to avoid a transpose (vertical and horizontal passes + // are commutative because w/kWeightY is symmetric) and subsequent transpose. + { + // Calculate a and b (two 4x4 at once). + const __m128i a0 = _mm_add_epi16(tmp_0, tmp_2); + const __m128i a1 = _mm_add_epi16(tmp_1, tmp_3); + const __m128i a2 = _mm_sub_epi16(tmp_1, tmp_3); + const __m128i a3 = _mm_sub_epi16(tmp_0, tmp_2); + const __m128i b0 = _mm_add_epi16(a0, a1); + const __m128i b1 = _mm_add_epi16(a3, a2); + const __m128i b2 = _mm_sub_epi16(a3, a2); + const __m128i b3 = _mm_sub_epi16(a0, a1); + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + + // Transpose the two 4x4. + VP8Transpose_2_4x4_16b(&b0, &b1, &b2, &b3, &tmp_0, &tmp_1, &tmp_2, &tmp_3); + } + + // Horizontal pass and difference of weighted sums. + { + // Load all inputs. + const __m128i w_0 = _mm_loadu_si128((const __m128i*)&w[0]); + const __m128i w_8 = _mm_loadu_si128((const __m128i*)&w[8]); + + // Calculate a and b (two 4x4 at once). + const __m128i a0 = _mm_add_epi16(tmp_0, tmp_2); + const __m128i a1 = _mm_add_epi16(tmp_1, tmp_3); + const __m128i a2 = _mm_sub_epi16(tmp_1, tmp_3); + const __m128i a3 = _mm_sub_epi16(tmp_0, tmp_2); + const __m128i b0 = _mm_add_epi16(a0, a1); + const __m128i b1 = _mm_add_epi16(a3, a2); + const __m128i b2 = _mm_sub_epi16(a3, a2); + const __m128i b3 = _mm_sub_epi16(a0, a1); + + // Separate the transforms of inA and inB. + __m128i A_b0 = _mm_unpacklo_epi64(b0, b1); + __m128i A_b2 = _mm_unpacklo_epi64(b2, b3); + __m128i B_b0 = _mm_unpackhi_epi64(b0, b1); + __m128i B_b2 = _mm_unpackhi_epi64(b2, b3); + + A_b0 = _mm_abs_epi16(A_b0); + A_b2 = _mm_abs_epi16(A_b2); + B_b0 = _mm_abs_epi16(B_b0); + B_b2 = _mm_abs_epi16(B_b2); + + // weighted sums + A_b0 = _mm_madd_epi16(A_b0, w_0); + A_b2 = _mm_madd_epi16(A_b2, w_8); + B_b0 = _mm_madd_epi16(B_b0, w_0); + B_b2 = _mm_madd_epi16(B_b2, w_8); + A_b0 = _mm_add_epi32(A_b0, A_b2); + B_b0 = _mm_add_epi32(B_b0, B_b2); + + // difference of weighted sums + A_b2 = _mm_sub_epi32(A_b0, B_b0); + _mm_storeu_si128((__m128i*)&sum[0], A_b2); + } + return sum[0] + sum[1] + sum[2] + sum[3]; +} + +static int Disto4x4(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + const int diff_sum = TTransform(a, b, w); + return abs(diff_sum) >> 5; +} + +static int Disto16x16(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4(a + x + y, b + x + y, w); + } + } + return D; +} + +//------------------------------------------------------------------------------ +// Quantization +// + +// Generates a pshufb constant for shuffling 16b words. +#define PSHUFB_CST(A,B,C,D,E,F,G,H) \ + _mm_set_epi8(2 * (H) + 1, 2 * (H) + 0, 2 * (G) + 1, 2 * (G) + 0, \ + 2 * (F) + 1, 2 * (F) + 0, 2 * (E) + 1, 2 * (E) + 0, \ + 2 * (D) + 1, 2 * (D) + 0, 2 * (C) + 1, 2 * (C) + 0, \ + 2 * (B) + 1, 2 * (B) + 0, 2 * (A) + 1, 2 * (A) + 0) + +static WEBP_INLINE int DoQuantizeBlock(int16_t in[16], int16_t out[16], + const uint16_t* const sharpen, + const VP8Matrix* const mtx) { + const __m128i max_coeff_2047 = _mm_set1_epi16(MAX_LEVEL); + const __m128i zero = _mm_setzero_si128(); + __m128i out0, out8; + __m128i packed_out; + + // Load all inputs. + __m128i in0 = _mm_loadu_si128((__m128i*)&in[0]); + __m128i in8 = _mm_loadu_si128((__m128i*)&in[8]); + const __m128i iq0 = _mm_loadu_si128((const __m128i*)&mtx->iq_[0]); + const __m128i iq8 = _mm_loadu_si128((const __m128i*)&mtx->iq_[8]); + const __m128i q0 = _mm_loadu_si128((const __m128i*)&mtx->q_[0]); + const __m128i q8 = _mm_loadu_si128((const __m128i*)&mtx->q_[8]); + + // coeff = abs(in) + __m128i coeff0 = _mm_abs_epi16(in0); + __m128i coeff8 = _mm_abs_epi16(in8); + + // coeff = abs(in) + sharpen + if (sharpen != NULL) { + const __m128i sharpen0 = _mm_loadu_si128((const __m128i*)&sharpen[0]); + const __m128i sharpen8 = _mm_loadu_si128((const __m128i*)&sharpen[8]); + coeff0 = _mm_add_epi16(coeff0, sharpen0); + coeff8 = _mm_add_epi16(coeff8, sharpen8); + } + + // out = (coeff * iQ + B) >> QFIX + { + // doing calculations with 32b precision (QFIX=17) + // out = (coeff * iQ) + const __m128i coeff_iQ0H = _mm_mulhi_epu16(coeff0, iq0); + const __m128i coeff_iQ0L = _mm_mullo_epi16(coeff0, iq0); + const __m128i coeff_iQ8H = _mm_mulhi_epu16(coeff8, iq8); + const __m128i coeff_iQ8L = _mm_mullo_epi16(coeff8, iq8); + __m128i out_00 = _mm_unpacklo_epi16(coeff_iQ0L, coeff_iQ0H); + __m128i out_04 = _mm_unpackhi_epi16(coeff_iQ0L, coeff_iQ0H); + __m128i out_08 = _mm_unpacklo_epi16(coeff_iQ8L, coeff_iQ8H); + __m128i out_12 = _mm_unpackhi_epi16(coeff_iQ8L, coeff_iQ8H); + // out = (coeff * iQ + B) + const __m128i bias_00 = _mm_loadu_si128((const __m128i*)&mtx->bias_[0]); + const __m128i bias_04 = _mm_loadu_si128((const __m128i*)&mtx->bias_[4]); + const __m128i bias_08 = _mm_loadu_si128((const __m128i*)&mtx->bias_[8]); + const __m128i bias_12 = _mm_loadu_si128((const __m128i*)&mtx->bias_[12]); + out_00 = _mm_add_epi32(out_00, bias_00); + out_04 = _mm_add_epi32(out_04, bias_04); + out_08 = _mm_add_epi32(out_08, bias_08); + out_12 = _mm_add_epi32(out_12, bias_12); + // out = QUANTDIV(coeff, iQ, B, QFIX) + out_00 = _mm_srai_epi32(out_00, QFIX); + out_04 = _mm_srai_epi32(out_04, QFIX); + out_08 = _mm_srai_epi32(out_08, QFIX); + out_12 = _mm_srai_epi32(out_12, QFIX); + + // pack result as 16b + out0 = _mm_packs_epi32(out_00, out_04); + out8 = _mm_packs_epi32(out_08, out_12); + + // if (coeff > 2047) coeff = 2047 + out0 = _mm_min_epi16(out0, max_coeff_2047); + out8 = _mm_min_epi16(out8, max_coeff_2047); + } + + // put sign back + out0 = _mm_sign_epi16(out0, in0); + out8 = _mm_sign_epi16(out8, in8); + + // in = out * Q + in0 = _mm_mullo_epi16(out0, q0); + in8 = _mm_mullo_epi16(out8, q8); + + _mm_storeu_si128((__m128i*)&in[0], in0); + _mm_storeu_si128((__m128i*)&in[8], in8); + + // zigzag the output before storing it. The re-ordering is: + // 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 + // -> 0 1 4[8]5 2 3 6 | 9 12 13 10 [7]11 14 15 + // There's only two misplaced entries ([8] and [7]) that are crossing the + // reg's boundaries. + // We use pshufb instead of pshuflo/pshufhi. + { + const __m128i kCst_lo = PSHUFB_CST(0, 1, 4, -1, 5, 2, 3, 6); + const __m128i kCst_7 = PSHUFB_CST(-1, -1, -1, -1, 7, -1, -1, -1); + const __m128i tmp_lo = _mm_shuffle_epi8(out0, kCst_lo); + const __m128i tmp_7 = _mm_shuffle_epi8(out0, kCst_7); // extract #7 + const __m128i kCst_hi = PSHUFB_CST(1, 4, 5, 2, -1, 3, 6, 7); + const __m128i kCst_8 = PSHUFB_CST(-1, -1, -1, 0, -1, -1, -1, -1); + const __m128i tmp_hi = _mm_shuffle_epi8(out8, kCst_hi); + const __m128i tmp_8 = _mm_shuffle_epi8(out8, kCst_8); // extract #8 + const __m128i out_z0 = _mm_or_si128(tmp_lo, tmp_8); + const __m128i out_z8 = _mm_or_si128(tmp_hi, tmp_7); + _mm_storeu_si128((__m128i*)&out[0], out_z0); + _mm_storeu_si128((__m128i*)&out[8], out_z8); + packed_out = _mm_packs_epi16(out_z0, out_z8); + } + + // detect if all 'out' values are zeroes or not + return (_mm_movemask_epi8(_mm_cmpeq_epi8(packed_out, zero)) != 0xffff); +} + +#undef PSHUFB_CST + +static int QuantizeBlock(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + return DoQuantizeBlock(in, out, &mtx->sharpen_[0], mtx); +} + +static int QuantizeBlockWHT(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + return DoQuantizeBlock(in, out, NULL, mtx); +} + +static int Quantize2Blocks(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + const uint16_t* const sharpen = &mtx->sharpen_[0]; + nz = DoQuantizeBlock(in + 0 * 16, out + 0 * 16, sharpen, mtx) << 0; + nz |= DoQuantizeBlock(in + 1 * 16, out + 1 * 16, sharpen, mtx) << 1; + return nz; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitSSE41(void); +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitSSE41(void) { + VP8CollectHistogram = CollectHistogram; + VP8EncQuantizeBlock = QuantizeBlock; + VP8EncQuantize2Blocks = Quantize2Blocks; + VP8EncQuantizeBlockWHT = QuantizeBlockWHT; + VP8TDisto4x4 = Disto4x4; + VP8TDisto16x16 = Disto16x16; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(VP8EncDspInitSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/3rdparty/libwebp/dsp/filters.c b/3rdparty/libwebp/dsp/filters.c new file mode 100644 index 0000000000..65f34aad1f --- /dev/null +++ b/3rdparty/libwebp/dsp/filters.c @@ -0,0 +1,273 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Spatial prediction using various filters +// +// Author: Urvang (urvang@google.com) + +#include "./dsp.h" +#include +#include +#include + +//------------------------------------------------------------------------------ +// Helpful macro. + +# define SANITY_CHECK(in, out) \ + assert(in != NULL); \ + assert(out != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + assert(row >= 0 && num_rows > 0 && row + num_rows <= height); \ + (void)height; // Silence unused warning. + +static WEBP_INLINE void PredictLine(const uint8_t* src, const uint8_t* pred, + uint8_t* dst, int length, int inverse) { + int i; + if (inverse) { + for (i = 0; i < length; ++i) dst[i] = src[i] + pred[i]; + } else { + for (i = 0; i < length; ++i) dst[i] = src[i] - pred[i]; + } +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + int inverse, uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + preds = inverse ? out : in; + + if (row == 0) { + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLine(in + 1, preds, out + 1, width - 1, inverse); + row = 1; + preds += stride; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + // Leftmost pixel is predicted from above. + PredictLine(in, preds - stride, out, 1, inverse); + PredictLine(in + 1, preds, out + 1, width - 1, inverse); + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + int inverse, uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + preds = inverse ? out : in; + + if (row == 0) { + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLine(in + 1, preds, out + 1, width - 1, inverse); + row = 1; + in += stride; + out += stride; + } else { + // We are starting from in-between. Make sure 'preds' points to prev row. + preds -= stride; + } + + // Filter line-by-line. + while (row < last_row) { + PredictLine(in, preds, out, width, inverse); + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +static WEBP_INLINE void DoGradientFilter(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + int inverse, uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + preds = inverse ? out : in; + + // left prediction for top scan-line + if (row == 0) { + out[0] = in[0]; + PredictLine(in + 1, preds, out + 1, width - 1, inverse); + row = 1; + preds += stride; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + int w; + // leftmost pixel: predict from above. + PredictLine(in, preds - stride, out, 1, inverse); + for (w = 1; w < width; ++w) { + const int pred = GradientPredictor(preds[w - 1], + preds[w - stride], + preds[w - stride - 1]); + out[w] = in[w] + (inverse ? pred : -pred); + } + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +#undef SANITY_CHECK + +//------------------------------------------------------------------------------ + +static void HorizontalFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoHorizontalFilter(data, width, height, stride, 0, height, 0, filtered_data); +} + +static void VerticalFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoVerticalFilter(data, width, height, stride, 0, height, 0, filtered_data); +} + + +static void GradientFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoGradientFilter(data, width, height, stride, 0, height, 0, filtered_data); +} + + +//------------------------------------------------------------------------------ + +static void HorizontalUnfilter(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + uint8_t pred = (prev == NULL) ? 0 : prev[0]; + int i; + for (i = 0; i < width; ++i) { + out[i] = pred + in[i]; + pred = out[i]; + } +} + +static void VerticalUnfilter(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter(NULL, in, out, width); + } else { + int i; + for (i = 0; i < width; ++i) out[i] = prev[i] + in[i]; + } +} + +static void GradientUnfilter(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter(NULL, in, out, width); + } else { + uint8_t top = prev[0], top_left = top, left = top; + int i; + for (i = 0; i < width; ++i) { + top = prev[i]; // need to read this first, in case prev==out + left = in[i] + GradientPredictor(left, top, top_left); + top_left = top; + out[i] = left; + } + } +} + +//------------------------------------------------------------------------------ +// Init function + +WebPFilterFunc WebPFilters[WEBP_FILTER_LAST]; +WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST]; + +extern void VP8FiltersInitMIPSdspR2(void); +extern void VP8FiltersInitMSA(void); +extern void VP8FiltersInitNEON(void); +extern void VP8FiltersInitSSE2(void); + +static volatile VP8CPUInfo filters_last_cpuinfo_used = + (VP8CPUInfo)&filters_last_cpuinfo_used; + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInit(void) { + if (filters_last_cpuinfo_used == VP8GetCPUInfo) return; + + WebPUnfilters[WEBP_FILTER_NONE] = NULL; + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter; + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter; + + WebPFilters[WEBP_FILTER_NONE] = NULL; + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8FiltersInitSSE2(); + } +#endif +#if defined(WEBP_USE_NEON) + if (VP8GetCPUInfo(kNEON)) { + VP8FiltersInitNEON(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8FiltersInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8FiltersInitMSA(); + } +#endif + } + filters_last_cpuinfo_used = VP8GetCPUInfo; +} diff --git a/3rdparty/libwebp/dsp/filters_mips_dsp_r2.c b/3rdparty/libwebp/dsp/filters_mips_dsp_r2.c new file mode 100644 index 0000000000..1d82e3c2e1 --- /dev/null +++ b/3rdparty/libwebp/dsp/filters_mips_dsp_r2.c @@ -0,0 +1,395 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Spatial prediction using various filters +// +// Author(s): Branimir Vasic (branimir.vasic@imgtec.com) +// Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "../dsp/dsp.h" +#include +#include +#include + +//------------------------------------------------------------------------------ +// Helpful macro. + +# define SANITY_CHECK(in, out) \ + assert(in != NULL); \ + assert(out != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + assert(row >= 0 && num_rows > 0 && row + num_rows <= height); \ + (void)height; // Silence unused warning. + +#define DO_PREDICT_LINE(SRC, DST, LENGTH, INVERSE) do { \ + const uint8_t* psrc = (uint8_t*)(SRC); \ + uint8_t* pdst = (uint8_t*)(DST); \ + const int ilength = (int)(LENGTH); \ + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; \ + __asm__ volatile ( \ + ".set push \n\t" \ + ".set noreorder \n\t" \ + "srl %[temp0], %[length], 2 \n\t" \ + "beqz %[temp0], 4f \n\t" \ + " andi %[temp6], %[length], 3 \n\t" \ + ".if " #INVERSE " \n\t" \ + "1: \n\t" \ + "lbu %[temp1], -1(%[dst]) \n\t" \ + "lbu %[temp2], 0(%[src]) \n\t" \ + "lbu %[temp3], 1(%[src]) \n\t" \ + "lbu %[temp4], 2(%[src]) \n\t" \ + "lbu %[temp5], 3(%[src]) \n\t" \ + "addu %[temp1], %[temp1], %[temp2] \n\t" \ + "addu %[temp2], %[temp1], %[temp3] \n\t" \ + "addu %[temp3], %[temp2], %[temp4] \n\t" \ + "addu %[temp4], %[temp3], %[temp5] \n\t" \ + "sb %[temp1], 0(%[dst]) \n\t" \ + "sb %[temp2], 1(%[dst]) \n\t" \ + "sb %[temp3], 2(%[dst]) \n\t" \ + "sb %[temp4], 3(%[dst]) \n\t" \ + "addiu %[src], %[src], 4 \n\t" \ + "addiu %[temp0], %[temp0], -1 \n\t" \ + "bnez %[temp0], 1b \n\t" \ + " addiu %[dst], %[dst], 4 \n\t" \ + ".else \n\t" \ + "1: \n\t" \ + "ulw %[temp1], -1(%[src]) \n\t" \ + "ulw %[temp2], 0(%[src]) \n\t" \ + "addiu %[src], %[src], 4 \n\t" \ + "addiu %[temp0], %[temp0], -1 \n\t" \ + "subu.qb %[temp3], %[temp2], %[temp1] \n\t" \ + "usw %[temp3], 0(%[dst]) \n\t" \ + "bnez %[temp0], 1b \n\t" \ + " addiu %[dst], %[dst], 4 \n\t" \ + ".endif \n\t" \ + "4: \n\t" \ + "beqz %[temp6], 3f \n\t" \ + " nop \n\t" \ + "2: \n\t" \ + "lbu %[temp2], 0(%[src]) \n\t" \ + ".if " #INVERSE " \n\t" \ + "lbu %[temp1], -1(%[dst]) \n\t" \ + "addu %[temp3], %[temp1], %[temp2] \n\t" \ + ".else \n\t" \ + "lbu %[temp1], -1(%[src]) \n\t" \ + "subu %[temp3], %[temp1], %[temp2] \n\t" \ + ".endif \n\t" \ + "addiu %[src], %[src], 1 \n\t" \ + "sb %[temp3], 0(%[dst]) \n\t" \ + "addiu %[temp6], %[temp6], -1 \n\t" \ + "bnez %[temp6], 2b \n\t" \ + " addiu %[dst], %[dst], 1 \n\t" \ + "3: \n\t" \ + ".set pop \n\t" \ + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), \ + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), \ + [temp6]"=&r"(temp6), [dst]"+&r"(pdst), [src]"+&r"(psrc) \ + : [length]"r"(ilength) \ + : "memory" \ + ); \ + } while (0) + +static WEBP_INLINE void PredictLine(const uint8_t* src, uint8_t* dst, + int length) { + DO_PREDICT_LINE(src, dst, length, 0); +} + +#define DO_PREDICT_LINE_VERTICAL(SRC, PRED, DST, LENGTH, INVERSE) do { \ + const uint8_t* psrc = (uint8_t*)(SRC); \ + const uint8_t* ppred = (uint8_t*)(PRED); \ + uint8_t* pdst = (uint8_t*)(DST); \ + const int ilength = (int)(LENGTH); \ + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; \ + __asm__ volatile ( \ + ".set push \n\t" \ + ".set noreorder \n\t" \ + "srl %[temp0], %[length], 0x3 \n\t" \ + "beqz %[temp0], 4f \n\t" \ + " andi %[temp7], %[length], 0x7 \n\t" \ + "1: \n\t" \ + "ulw %[temp1], 0(%[src]) \n\t" \ + "ulw %[temp2], 0(%[pred]) \n\t" \ + "ulw %[temp3], 4(%[src]) \n\t" \ + "ulw %[temp4], 4(%[pred]) \n\t" \ + "addiu %[src], %[src], 8 \n\t" \ + ".if " #INVERSE " \n\t" \ + "addu.qb %[temp5], %[temp1], %[temp2] \n\t" \ + "addu.qb %[temp6], %[temp3], %[temp4] \n\t" \ + ".else \n\t" \ + "subu.qb %[temp5], %[temp1], %[temp2] \n\t" \ + "subu.qb %[temp6], %[temp3], %[temp4] \n\t" \ + ".endif \n\t" \ + "addiu %[pred], %[pred], 8 \n\t" \ + "usw %[temp5], 0(%[dst]) \n\t" \ + "usw %[temp6], 4(%[dst]) \n\t" \ + "addiu %[temp0], %[temp0], -1 \n\t" \ + "bnez %[temp0], 1b \n\t" \ + " addiu %[dst], %[dst], 8 \n\t" \ + "4: \n\t" \ + "beqz %[temp7], 3f \n\t" \ + " nop \n\t" \ + "2: \n\t" \ + "lbu %[temp1], 0(%[src]) \n\t" \ + "lbu %[temp2], 0(%[pred]) \n\t" \ + "addiu %[src], %[src], 1 \n\t" \ + "addiu %[pred], %[pred], 1 \n\t" \ + ".if " #INVERSE " \n\t" \ + "addu %[temp3], %[temp1], %[temp2] \n\t" \ + ".else \n\t" \ + "subu %[temp3], %[temp1], %[temp2] \n\t" \ + ".endif \n\t" \ + "sb %[temp3], 0(%[dst]) \n\t" \ + "addiu %[temp7], %[temp7], -1 \n\t" \ + "bnez %[temp7], 2b \n\t" \ + " addiu %[dst], %[dst], 1 \n\t" \ + "3: \n\t" \ + ".set pop \n\t" \ + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), \ + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), \ + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [pred]"+&r"(ppred), \ + [dst]"+&r"(pdst), [src]"+&r"(psrc) \ + : [length]"r"(ilength) \ + : "memory" \ + ); \ + } while (0) + +#define PREDICT_LINE_ONE_PASS(SRC, PRED, DST) do { \ + int temp1, temp2, temp3; \ + __asm__ volatile ( \ + "lbu %[temp1], 0(%[src]) \n\t" \ + "lbu %[temp2], 0(%[pred]) \n\t" \ + "subu %[temp3], %[temp1], %[temp2] \n\t" \ + "sb %[temp3], 0(%[dst]) \n\t" \ + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3) \ + : [pred]"r"((PRED)), [dst]"r"((DST)), [src]"r"((SRC)) \ + : "memory" \ + ); \ + } while (0) + +//------------------------------------------------------------------------------ +// Horizontal filter. + +#define FILTER_LINE_BY_LINE do { \ + while (row < last_row) { \ + PREDICT_LINE_ONE_PASS(in, preds - stride, out); \ + DO_PREDICT_LINE(in + 1, out + 1, width - 1, 0); \ + ++row; \ + preds += stride; \ + in += stride; \ + out += stride; \ + } \ + } while (0) + +static WEBP_INLINE void DoHorizontalFilter(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + preds = in; + + if (row == 0) { + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLine(in + 1, out + 1, width - 1); + row = 1; + preds += stride; + in += stride; + out += stride; + } + + // Filter line-by-line. + FILTER_LINE_BY_LINE; +} +#undef FILTER_LINE_BY_LINE + +static void HorizontalFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoHorizontalFilter(data, width, height, stride, 0, height, filtered_data); +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +#define FILTER_LINE_BY_LINE do { \ + while (row < last_row) { \ + DO_PREDICT_LINE_VERTICAL(in, preds, out, width, 0); \ + ++row; \ + preds += stride; \ + in += stride; \ + out += stride; \ + } \ + } while (0) + +static WEBP_INLINE void DoVerticalFilter(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + preds = in; + + if (row == 0) { + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLine(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } else { + // We are starting from in-between. Make sure 'preds' points to prev row. + preds -= stride; + } + + // Filter line-by-line. + FILTER_LINE_BY_LINE; +} +#undef FILTER_LINE_BY_LINE + +static void VerticalFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoVerticalFilter(data, width, height, stride, 0, height, filtered_data); +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor(uint8_t a, uint8_t b, uint8_t c) { + int temp0; + __asm__ volatile ( + "addu %[temp0], %[a], %[b] \n\t" + "subu %[temp0], %[temp0], %[c] \n\t" + "shll_s.w %[temp0], %[temp0], 23 \n\t" + "precrqu_s.qb.ph %[temp0], %[temp0], $zero \n\t" + "srl %[temp0], %[temp0], 24 \n\t" + : [temp0]"=&r"(temp0) + : [a]"r"(a),[b]"r"(b),[c]"r"(c) + ); + return temp0; +} + +#define FILTER_LINE_BY_LINE(PREDS, OPERATION) do { \ + while (row < last_row) { \ + int w; \ + PREDICT_LINE_ONE_PASS(in, PREDS - stride, out); \ + for (w = 1; w < width; ++w) { \ + const int pred = GradientPredictor(PREDS[w - 1], \ + PREDS[w - stride], \ + PREDS[w - stride - 1]); \ + out[w] = in[w] OPERATION pred; \ + } \ + ++row; \ + in += stride; \ + out += stride; \ + } \ + } while (0) + +static WEBP_INLINE void DoGradientFilter(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + preds = in; + + // left prediction for top scan-line + if (row == 0) { + out[0] = in[0]; + PredictLine(in + 1, out + 1, width - 1); + row = 1; + preds += stride; + in += stride; + out += stride; + } + + // Filter line-by-line. + FILTER_LINE_BY_LINE(in, -); +} +#undef FILTER_LINE_BY_LINE + +static void GradientFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoGradientFilter(data, width, height, stride, 0, height, filtered_data); +} + +//------------------------------------------------------------------------------ + +static void HorizontalUnfilter(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + out[0] = in[0] + (prev == NULL ? 0 : prev[0]); + DO_PREDICT_LINE(in + 1, out + 1, width - 1, 1); +} + +static void VerticalUnfilter(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter(NULL, in, out, width); + } else { + DO_PREDICT_LINE_VERTICAL(in, prev, out, width, 1); + } +} + +static void GradientUnfilter(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter(NULL, in, out, width); + } else { + uint8_t top = prev[0], top_left = top, left = top; + int i; + for (i = 0; i < width; ++i) { + top = prev[i]; // need to read this first, in case prev==dst + left = in[i] + GradientPredictor(left, top, top_left); + top_left = top; + out[i] = left; + } + } +} + +#undef DO_PREDICT_LINE_VERTICAL +#undef PREDICT_LINE_ONE_PASS +#undef DO_PREDICT_LINE +#undef SANITY_CHECK + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitMIPSdspR2(void) { + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter; + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter; + + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8FiltersInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/filters_msa.c b/3rdparty/libwebp/dsp/filters_msa.c new file mode 100644 index 0000000000..4b8922d0bc --- /dev/null +++ b/3rdparty/libwebp/dsp/filters_msa.c @@ -0,0 +1,202 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA variant of alpha filters +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "./msa_macro.h" + +#include + +static WEBP_INLINE void PredictLineInverse0(const uint8_t* src, + const uint8_t* pred, + uint8_t* dst, int length) { + v16u8 src0, pred0, dst0; + assert(length >= 0); + while (length >= 32) { + v16u8 src1, pred1, dst1; + LD_UB2(src, 16, src0, src1); + LD_UB2(pred, 16, pred0, pred1); + SUB2(src0, pred0, src1, pred1, dst0, dst1); + ST_UB2(dst0, dst1, dst, 16); + src += 32; + pred += 32; + dst += 32; + length -= 32; + } + if (length > 0) { + int i; + if (length >= 16) { + src0 = LD_UB(src); + pred0 = LD_UB(pred); + dst0 = src0 - pred0; + ST_UB(dst0, dst); + src += 16; + pred += 16; + dst += 16; + length -= 16; + } + for (i = 0; i < length; i++) { + dst[i] = src[i] - pred[i]; + } + } +} + +//------------------------------------------------------------------------------ +// Helpful macro. + +#define SANITY_CHECK(in, out) \ + assert(in != NULL); \ + assert(out != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); + +//------------------------------------------------------------------------------ +// Horrizontal filter + +static void HorizontalFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + const uint8_t* preds = data; + const uint8_t* in = data; + uint8_t* out = filtered_data; + int row = 1; + SANITY_CHECK(in, out); + + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLineInverse0(in + 1, preds, out + 1, width - 1); + preds += stride; + in += stride; + out += stride; + // Filter line-by-line. + while (row < height) { + // Leftmost pixel is predicted from above. + PredictLineInverse0(in, preds - stride, out, 1); + PredictLineInverse0(in + 1, preds, out + 1, width - 1); + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Gradient filter + +static WEBP_INLINE void PredictLineGradient(const uint8_t* pinput, + const uint8_t* ppred, + uint8_t* poutput, int stride, + int size) { + int w; + const v16i8 zero = { 0 }; + while (size >= 16) { + v16u8 pred0, dst0; + v8i16 a0, a1, b0, b1, c0, c1; + const v16u8 tmp0 = LD_UB(ppred - 1); + const v16u8 tmp1 = LD_UB(ppred - stride); + const v16u8 tmp2 = LD_UB(ppred - stride - 1); + const v16u8 src0 = LD_UB(pinput); + ILVRL_B2_SH(zero, tmp0, a0, a1); + ILVRL_B2_SH(zero, tmp1, b0, b1); + ILVRL_B2_SH(zero, tmp2, c0, c1); + ADD2(a0, b0, a1, b1, a0, a1); + SUB2(a0, c0, a1, c1, a0, a1); + CLIP_SH2_0_255(a0, a1); + pred0 = (v16u8)__msa_pckev_b((v16i8)a1, (v16i8)a0); + dst0 = src0 - pred0; + ST_UB(dst0, poutput); + ppred += 16; + pinput += 16; + poutput += 16; + size -= 16; + } + for (w = 0; w < size; ++w) { + const int pred = ppred[w - 1] + ppred[w - stride] - ppred[w - stride - 1]; + poutput[w] = pinput[w] - (pred < 0 ? 0 : pred > 255 ? 255 : pred); + } +} + + +static void GradientFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + const uint8_t* in = data; + const uint8_t* preds = data; + uint8_t* out = filtered_data; + int row = 1; + SANITY_CHECK(in, out); + + // left prediction for top scan-line + out[0] = in[0]; + PredictLineInverse0(in + 1, preds, out + 1, width - 1); + preds += stride; + in += stride; + out += stride; + // Filter line-by-line. + while (row < height) { + out[0] = in[0] - preds[- stride]; + PredictLineGradient(preds + 1, in + 1, out + 1, stride, width - 1); + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Vertical filter + +static void VerticalFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + const uint8_t* in = data; + const uint8_t* preds = data; + uint8_t* out = filtered_data; + int row = 1; + SANITY_CHECK(in, out); + + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLineInverse0(in + 1, preds, out + 1, width - 1); + in += stride; + out += stride; + + // Filter line-by-line. + while (row < height) { + PredictLineInverse0(in, preds, out, width); + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +#undef SANITY_CHECK + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitMSA(void) { + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8FiltersInitMSA) + +#endif // WEBP_USE_MSA diff --git a/3rdparty/libwebp/dsp/filters_neon.c b/3rdparty/libwebp/dsp/filters_neon.c new file mode 100644 index 0000000000..4d6e50cc76 --- /dev/null +++ b/3rdparty/libwebp/dsp/filters_neon.c @@ -0,0 +1,327 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON variant of alpha filters +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_NEON) + +#include +#include "./neon.h" + +//------------------------------------------------------------------------------ +// Helpful macros. + +# define SANITY_CHECK(in, out) \ + assert(in != NULL); \ + assert(out != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + assert(row >= 0 && num_rows > 0 && row + num_rows <= height); \ + (void)height; // Silence unused warning. + +// load eight u8 and widen to s16 +#define U8_TO_S16(A) vreinterpretq_s16_u16(vmovl_u8(A)) +#define LOAD_U8_TO_S16(A) U8_TO_S16(vld1_u8(A)) + +// shift left or right by N byte, inserting zeros +#define SHIFT_RIGHT_N_Q(A, N) vextq_u8((A), zero, (N)) +#define SHIFT_LEFT_N_Q(A, N) vextq_u8(zero, (A), (16 - (N)) % 16) + +// rotate left by N bytes +#define ROTATE_LEFT_N(A, N) vext_u8((A), (A), (N)) +// rotate right by N bytes +#define ROTATE_RIGHT_N(A, N) vext_u8((A), (A), (8 - (N)) % 8) + +static void PredictLine_NEON(const uint8_t* src, const uint8_t* pred, + uint8_t* dst, int length) { + int i; + assert(length >= 0); + for (i = 0; i + 16 <= length; i += 16) { + const uint8x16_t A = vld1q_u8(&src[i]); + const uint8x16_t B = vld1q_u8(&pred[i]); + const uint8x16_t C = vsubq_u8(A, B); + vst1q_u8(&dst[i], C); + } + for (; i < length; ++i) dst[i] = src[i] - pred[i]; +} + +// Special case for left-based prediction (when preds==dst-1 or preds==src-1). +static void PredictLineLeft_NEON(const uint8_t* src, uint8_t* dst, int length) { + PredictLine_NEON(src, src - 1, dst, length); +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter_NEON(const uint8_t* in, + int width, int height, + int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + // Leftmost pixel is predicted from above. + out[0] = in[0] - in[-stride]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +static void HorizontalFilter_NEON(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoHorizontalFilter_NEON(data, width, height, stride, 0, height, + filtered_data); +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter_NEON(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + PredictLine_NEON(in, in - stride, out, width); + ++row; + in += stride; + out += stride; + } +} + +static void VerticalFilter_NEON(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoVerticalFilter_NEON(data, width, height, stride, 0, height, + filtered_data); +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor_C(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +static void GradientPredictDirect_NEON(const uint8_t* const row, + const uint8_t* const top, + uint8_t* const out, int length) { + int i; + for (i = 0; i + 8 <= length; i += 8) { + const uint8x8_t A = vld1_u8(&row[i - 1]); + const uint8x8_t B = vld1_u8(&top[i + 0]); + const int16x8_t C = vreinterpretq_s16_u16(vaddl_u8(A, B)); + const int16x8_t D = LOAD_U8_TO_S16(&top[i - 1]); + const uint8x8_t E = vqmovun_s16(vsubq_s16(C, D)); + const uint8x8_t F = vld1_u8(&row[i + 0]); + vst1_u8(&out[i], vsub_u8(F, E)); + } + for (; i < length; ++i) { + out[i] = row[i] - GradientPredictor_C(row[i - 1], top[i], top[i - 1]); + } +} + +static WEBP_INLINE void DoGradientFilter_NEON(const uint8_t* in, + int width, int height, + int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + + // left prediction for top scan-line + if (row == 0) { + out[0] = in[0]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + out[0] = in[0] - in[-stride]; + GradientPredictDirect_NEON(in + 1, in + 1 - stride, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +static void GradientFilter_NEON(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoGradientFilter_NEON(data, width, height, stride, 0, height, + filtered_data); +} + +#undef SANITY_CHECK + +//------------------------------------------------------------------------------ +// Inverse transforms + +static void HorizontalUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + int i; + const uint8x16_t zero = vdupq_n_u8(0); + uint8x16_t last; + out[0] = in[0] + (prev == NULL ? 0 : prev[0]); + if (width <= 1) return; + last = vsetq_lane_u8(out[0], zero, 0); + for (i = 1; i + 16 <= width; i += 16) { + const uint8x16_t A0 = vld1q_u8(&in[i]); + const uint8x16_t A1 = vaddq_u8(A0, last); + const uint8x16_t A2 = SHIFT_LEFT_N_Q(A1, 1); + const uint8x16_t A3 = vaddq_u8(A1, A2); + const uint8x16_t A4 = SHIFT_LEFT_N_Q(A3, 2); + const uint8x16_t A5 = vaddq_u8(A3, A4); + const uint8x16_t A6 = SHIFT_LEFT_N_Q(A5, 4); + const uint8x16_t A7 = vaddq_u8(A5, A6); + const uint8x16_t A8 = SHIFT_LEFT_N_Q(A7, 8); + const uint8x16_t A9 = vaddq_u8(A7, A8); + vst1q_u8(&out[i], A9); + last = SHIFT_RIGHT_N_Q(A9, 15); + } + for (; i < width; ++i) out[i] = in[i] + out[i - 1]; +} + +static void VerticalUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_NEON(NULL, in, out, width); + } else { + int i; + assert(width >= 0); + for (i = 0; i + 16 <= width; i += 16) { + const uint8x16_t A = vld1q_u8(&in[i]); + const uint8x16_t B = vld1q_u8(&prev[i]); + const uint8x16_t C = vaddq_u8(A, B); + vst1q_u8(&out[i], C); + } + for (; i < width; ++i) out[i] = in[i] + prev[i]; + } +} + +// GradientUnfilter_NEON is correct but slower than the C-version, +// at least on ARM64. For armv7, it's a wash. +// So best is to disable it for now, but keep the idea around... +// #define USE_GRADIENT_UNFILTER + +#if defined(USE_GRADIENT_UNFILTER) +#define GRAD_PROCESS_LANE(L) do { \ + const uint8x8_t tmp1 = ROTATE_RIGHT_N(pred, 1); /* rotate predictor in */ \ + const int16x8_t tmp2 = vaddq_s16(BC, U8_TO_S16(tmp1)); \ + const uint8x8_t delta = vqmovun_s16(tmp2); \ + pred = vadd_u8(D, delta); \ + out = vext_u8(out, ROTATE_LEFT_N(pred, (L)), 1); \ +} while (0) + +static void GradientPredictInverse_NEON(const uint8_t* const in, + const uint8_t* const top, + uint8_t* const row, int length) { + if (length > 0) { + int i; + uint8x8_t pred = vdup_n_u8(row[-1]); // left sample + uint8x8_t out = vdup_n_u8(0); + for (i = 0; i + 8 <= length; i += 8) { + const int16x8_t B = LOAD_U8_TO_S16(&top[i + 0]); + const int16x8_t C = LOAD_U8_TO_S16(&top[i - 1]); + const int16x8_t BC = vsubq_s16(B, C); // unclipped gradient basis B - C + const uint8x8_t D = vld1_u8(&in[i]); // base input + GRAD_PROCESS_LANE(0); + GRAD_PROCESS_LANE(1); + GRAD_PROCESS_LANE(2); + GRAD_PROCESS_LANE(3); + GRAD_PROCESS_LANE(4); + GRAD_PROCESS_LANE(5); + GRAD_PROCESS_LANE(6); + GRAD_PROCESS_LANE(7); + vst1_u8(&row[i], out); + } + for (; i < length; ++i) { + row[i] = in[i] + GradientPredictor_C(row[i - 1], top[i], top[i - 1]); + } + } +} +#undef GRAD_PROCESS_LANE + +static void GradientUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_NEON(NULL, in, out, width); + } else { + out[0] = in[0] + prev[0]; // predict from above + GradientPredictInverse_NEON(in + 1, prev + 1, out + 1, width - 1); + } +} + +#endif // USE_GRADIENT_UNFILTER + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitNEON(void) { + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_NEON; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_NEON; +#if defined(USE_GRADIENT_UNFILTER) + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_NEON; +#endif + + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_NEON; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_NEON; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8FiltersInitNEON) + +#endif // WEBP_USE_NEON diff --git a/3rdparty/libwebp/dsp/filters_sse2.c b/3rdparty/libwebp/dsp/filters_sse2.c new file mode 100644 index 0000000000..67f77999e6 --- /dev/null +++ b/3rdparty/libwebp/dsp/filters_sse2.c @@ -0,0 +1,330 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 variant of alpha filters +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE2) + +#include +#include +#include +#include + +//------------------------------------------------------------------------------ +// Helpful macro. + +# define SANITY_CHECK(in, out) \ + assert(in != NULL); \ + assert(out != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + assert(row >= 0 && num_rows > 0 && row + num_rows <= height); \ + (void)height; // Silence unused warning. + +static void PredictLineTop(const uint8_t* src, const uint8_t* pred, + uint8_t* dst, int length) { + int i; + const int max_pos = length & ~31; + assert(length >= 0); + for (i = 0; i < max_pos; i += 32) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)&src[i + 0]); + const __m128i A1 = _mm_loadu_si128((const __m128i*)&src[i + 16]); + const __m128i B0 = _mm_loadu_si128((const __m128i*)&pred[i + 0]); + const __m128i B1 = _mm_loadu_si128((const __m128i*)&pred[i + 16]); + const __m128i C0 = _mm_sub_epi8(A0, B0); + const __m128i C1 = _mm_sub_epi8(A1, B1); + _mm_storeu_si128((__m128i*)&dst[i + 0], C0); + _mm_storeu_si128((__m128i*)&dst[i + 16], C1); + } + for (; i < length; ++i) dst[i] = src[i] - pred[i]; +} + +// Special case for left-based prediction (when preds==dst-1 or preds==src-1). +static void PredictLineLeft(const uint8_t* src, uint8_t* dst, int length) { + int i; + const int max_pos = length & ~31; + assert(length >= 0); + for (i = 0; i < max_pos; i += 32) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(src + i + 0 )); + const __m128i B0 = _mm_loadu_si128((const __m128i*)(src + i + 0 - 1)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(src + i + 16 )); + const __m128i B1 = _mm_loadu_si128((const __m128i*)(src + i + 16 - 1)); + const __m128i C0 = _mm_sub_epi8(A0, B0); + const __m128i C1 = _mm_sub_epi8(A1, B1); + _mm_storeu_si128((__m128i*)(dst + i + 0), C0); + _mm_storeu_si128((__m128i*)(dst + i + 16), C1); + } + for (; i < length; ++i) dst[i] = src[i] - src[i - 1]; +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLineLeft(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + // Leftmost pixel is predicted from above. + out[0] = in[0] - in[-stride]; + PredictLineLeft(in + 1, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLineLeft(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + PredictLineTop(in, in - stride, out, width); + ++row; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictorC(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +static void GradientPredictDirect(const uint8_t* const row, + const uint8_t* const top, + uint8_t* const out, int length) { + const int max_pos = length & ~7; + int i; + const __m128i zero = _mm_setzero_si128(); + for (i = 0; i < max_pos; i += 8) { + const __m128i A0 = _mm_loadl_epi64((const __m128i*)&row[i - 1]); + const __m128i B0 = _mm_loadl_epi64((const __m128i*)&top[i]); + const __m128i C0 = _mm_loadl_epi64((const __m128i*)&top[i - 1]); + const __m128i D = _mm_loadl_epi64((const __m128i*)&row[i]); + const __m128i A1 = _mm_unpacklo_epi8(A0, zero); + const __m128i B1 = _mm_unpacklo_epi8(B0, zero); + const __m128i C1 = _mm_unpacklo_epi8(C0, zero); + const __m128i E = _mm_add_epi16(A1, B1); + const __m128i F = _mm_sub_epi16(E, C1); + const __m128i G = _mm_packus_epi16(F, zero); + const __m128i H = _mm_sub_epi8(D, G); + _mm_storel_epi64((__m128i*)(out + i), H); + } + for (; i < length; ++i) { + out[i] = row[i] - GradientPredictorC(row[i - 1], top[i], top[i - 1]); + } +} + +static WEBP_INLINE void DoGradientFilter(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + + // left prediction for top scan-line + if (row == 0) { + out[0] = in[0]; + PredictLineLeft(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + out[0] = in[0] - in[-stride]; + GradientPredictDirect(in + 1, in + 1 - stride, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +#undef SANITY_CHECK + +//------------------------------------------------------------------------------ + +static void HorizontalFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoHorizontalFilter(data, width, height, stride, 0, height, filtered_data); +} + +static void VerticalFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoVerticalFilter(data, width, height, stride, 0, height, filtered_data); +} + +static void GradientFilter(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoGradientFilter(data, width, height, stride, 0, height, filtered_data); +} + +//------------------------------------------------------------------------------ +// Inverse transforms + +static void HorizontalUnfilter(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + int i; + __m128i last; + out[0] = in[0] + (prev == NULL ? 0 : prev[0]); + if (width <= 1) return; + last = _mm_set_epi32(0, 0, 0, out[0]); + for (i = 1; i + 8 <= width; i += 8) { + const __m128i A0 = _mm_loadl_epi64((const __m128i*)(in + i)); + const __m128i A1 = _mm_add_epi8(A0, last); + const __m128i A2 = _mm_slli_si128(A1, 1); + const __m128i A3 = _mm_add_epi8(A1, A2); + const __m128i A4 = _mm_slli_si128(A3, 2); + const __m128i A5 = _mm_add_epi8(A3, A4); + const __m128i A6 = _mm_slli_si128(A5, 4); + const __m128i A7 = _mm_add_epi8(A5, A6); + _mm_storel_epi64((__m128i*)(out + i), A7); + last = _mm_srli_epi64(A7, 56); + } + for (; i < width; ++i) out[i] = in[i] + out[i - 1]; +} + +static void VerticalUnfilter(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter(NULL, in, out, width); + } else { + int i; + const int max_pos = width & ~31; + assert(width >= 0); + for (i = 0; i < max_pos; i += 32) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)&in[i + 0]); + const __m128i A1 = _mm_loadu_si128((const __m128i*)&in[i + 16]); + const __m128i B0 = _mm_loadu_si128((const __m128i*)&prev[i + 0]); + const __m128i B1 = _mm_loadu_si128((const __m128i*)&prev[i + 16]); + const __m128i C0 = _mm_add_epi8(A0, B0); + const __m128i C1 = _mm_add_epi8(A1, B1); + _mm_storeu_si128((__m128i*)&out[i + 0], C0); + _mm_storeu_si128((__m128i*)&out[i + 16], C1); + } + for (; i < width; ++i) out[i] = in[i] + prev[i]; + } +} + +static void GradientPredictInverse(const uint8_t* const in, + const uint8_t* const top, + uint8_t* const row, int length) { + if (length > 0) { + int i; + const int max_pos = length & ~7; + const __m128i zero = _mm_setzero_si128(); + __m128i A = _mm_set_epi32(0, 0, 0, row[-1]); // left sample + for (i = 0; i < max_pos; i += 8) { + const __m128i tmp0 = _mm_loadl_epi64((const __m128i*)&top[i]); + const __m128i tmp1 = _mm_loadl_epi64((const __m128i*)&top[i - 1]); + const __m128i B = _mm_unpacklo_epi8(tmp0, zero); + const __m128i C = _mm_unpacklo_epi8(tmp1, zero); + const __m128i D = _mm_loadl_epi64((const __m128i*)&in[i]); // base input + const __m128i E = _mm_sub_epi16(B, C); // unclipped gradient basis B - C + __m128i out = zero; // accumulator for output + __m128i mask_hi = _mm_set_epi32(0, 0, 0, 0xff); + int k = 8; + while (1) { + const __m128i tmp3 = _mm_add_epi16(A, E); // delta = A + B - C + const __m128i tmp4 = _mm_packus_epi16(tmp3, zero); // saturate delta + const __m128i tmp5 = _mm_add_epi8(tmp4, D); // add to in[] + A = _mm_and_si128(tmp5, mask_hi); // 1-complement clip + out = _mm_or_si128(out, A); // accumulate output + if (--k == 0) break; + A = _mm_slli_si128(A, 1); // rotate left sample + mask_hi = _mm_slli_si128(mask_hi, 1); // rotate mask + A = _mm_unpacklo_epi8(A, zero); // convert 8b->16b + } + A = _mm_srli_si128(A, 7); // prepare left sample for next iteration + _mm_storel_epi64((__m128i*)&row[i], out); + } + for (; i < length; ++i) { + row[i] = in[i] + GradientPredictorC(row[i - 1], top[i], top[i - 1]); + } + } +} + +static void GradientUnfilter(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter(NULL, in, out, width); + } else { + out[0] = in[0] + prev[0]; // predict from above + GradientPredictInverse(in + 1, prev + 1, out + 1, width - 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitSSE2(void) { + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter; + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter; + + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8FiltersInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/dsp/lossless.c b/3rdparty/libwebp/dsp/lossless.c index e445924ed3..20d18f6ecd 100644 --- a/3rdparty/libwebp/dsp/lossless.c +++ b/3rdparty/libwebp/dsp/lossless.c @@ -15,267 +15,20 @@ #include "./dsp.h" -// Define the following if target arch is sure to have SSE2 -// #define WEBP_TARGET_HAS_SSE2 - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#if defined(WEBP_TARGET_HAS_SSE2) -#include -#endif - #include #include +#include "../dec/vp8li_dec.h" +#include "../utils/endian_inl_utils.h" #include "./lossless.h" -#include "../dec/vp8li.h" -#include "./yuv.h" +#include "./lossless_common.h" #define MAX_DIFF_COST (1e30f) -// lookup table for small values of log2(int) -#define APPROX_LOG_MAX 4096 -#define LOG_2_RECIPROCAL 1.44269504088896338700465094007086 -const float kLog2Table[LOG_LOOKUP_IDX_MAX] = { - 0.0000000000000000f, 0.0000000000000000f, - 1.0000000000000000f, 1.5849625007211560f, - 2.0000000000000000f, 2.3219280948873621f, - 2.5849625007211560f, 2.8073549220576041f, - 3.0000000000000000f, 3.1699250014423121f, - 3.3219280948873621f, 3.4594316186372973f, - 3.5849625007211560f, 3.7004397181410921f, - 3.8073549220576041f, 3.9068905956085187f, - 4.0000000000000000f, 4.0874628412503390f, - 4.1699250014423121f, 4.2479275134435852f, - 4.3219280948873626f, 4.3923174227787606f, - 4.4594316186372973f, 4.5235619560570130f, - 4.5849625007211560f, 4.6438561897747243f, - 4.7004397181410917f, 4.7548875021634682f, - 4.8073549220576037f, 4.8579809951275718f, - 4.9068905956085187f, 4.9541963103868749f, - 5.0000000000000000f, 5.0443941193584533f, - 5.0874628412503390f, 5.1292830169449663f, - 5.1699250014423121f, 5.2094533656289501f, - 5.2479275134435852f, 5.2854022188622487f, - 5.3219280948873626f, 5.3575520046180837f, - 5.3923174227787606f, 5.4262647547020979f, - 5.4594316186372973f, 5.4918530963296747f, - 5.5235619560570130f, 5.5545888516776376f, - 5.5849625007211560f, 5.6147098441152083f, - 5.6438561897747243f, 5.6724253419714951f, - 5.7004397181410917f, 5.7279204545631987f, - 5.7548875021634682f, 5.7813597135246599f, - 5.8073549220576037f, 5.8328900141647412f, - 5.8579809951275718f, 5.8826430493618415f, - 5.9068905956085187f, 5.9307373375628866f, - 5.9541963103868749f, 5.9772799234999167f, - 6.0000000000000000f, 6.0223678130284543f, - 6.0443941193584533f, 6.0660891904577720f, - 6.0874628412503390f, 6.1085244567781691f, - 6.1292830169449663f, 6.1497471195046822f, - 6.1699250014423121f, 6.1898245588800175f, - 6.2094533656289501f, 6.2288186904958804f, - 6.2479275134435852f, 6.2667865406949010f, - 6.2854022188622487f, 6.3037807481771030f, - 6.3219280948873626f, 6.3398500028846243f, - 6.3575520046180837f, 6.3750394313469245f, - 6.3923174227787606f, 6.4093909361377017f, - 6.4262647547020979f, 6.4429434958487279f, - 6.4594316186372973f, 6.4757334309663976f, - 6.4918530963296747f, 6.5077946401986963f, - 6.5235619560570130f, 6.5391588111080309f, - 6.5545888516776376f, 6.5698556083309478f, - 6.5849625007211560f, 6.5999128421871278f, - 6.6147098441152083f, 6.6293566200796094f, - 6.6438561897747243f, 6.6582114827517946f, - 6.6724253419714951f, 6.6865005271832185f, - 6.7004397181410917f, 6.7142455176661224f, - 6.7279204545631987f, 6.7414669864011464f, - 6.7548875021634682f, 6.7681843247769259f, - 6.7813597135246599f, 6.7944158663501061f, - 6.8073549220576037f, 6.8201789624151878f, - 6.8328900141647412f, 6.8454900509443747f, - 6.8579809951275718f, 6.8703647195834047f, - 6.8826430493618415f, 6.8948177633079437f, - 6.9068905956085187f, 6.9188632372745946f, - 6.9307373375628866f, 6.9425145053392398f, - 6.9541963103868749f, 6.9657842846620869f, - 6.9772799234999167f, 6.9886846867721654f, - 7.0000000000000000f, 7.0112272554232539f, - 7.0223678130284543f, 7.0334230015374501f, - 7.0443941193584533f, 7.0552824355011898f, - 7.0660891904577720f, 7.0768155970508308f, - 7.0874628412503390f, 7.0980320829605263f, - 7.1085244567781691f, 7.1189410727235076f, - 7.1292830169449663f, 7.1395513523987936f, - 7.1497471195046822f, 7.1598713367783890f, - 7.1699250014423121f, 7.1799090900149344f, - 7.1898245588800175f, 7.1996723448363644f, - 7.2094533656289501f, 7.2191685204621611f, - 7.2288186904958804f, 7.2384047393250785f, - 7.2479275134435852f, 7.2573878426926521f, - 7.2667865406949010f, 7.2761244052742375f, - 7.2854022188622487f, 7.2946207488916270f, - 7.3037807481771030f, 7.3128829552843557f, - 7.3219280948873626f, 7.3309168781146167f, - 7.3398500028846243f, 7.3487281542310771f, - 7.3575520046180837f, 7.3663222142458160f, - 7.3750394313469245f, 7.3837042924740519f, - 7.3923174227787606f, 7.4008794362821843f, - 7.4093909361377017f, 7.4178525148858982f, - 7.4262647547020979f, 7.4346282276367245f, - 7.4429434958487279f, 7.4512111118323289f, - 7.4594316186372973f, 7.4676055500829976f, - 7.4757334309663976f, 7.4838157772642563f, - 7.4918530963296747f, 7.4998458870832056f, - 7.5077946401986963f, 7.5156998382840427f, - 7.5235619560570130f, 7.5313814605163118f, - 7.5391588111080309f, 7.5468944598876364f, - 7.5545888516776376f, 7.5622424242210728f, - 7.5698556083309478f, 7.5774288280357486f, - 7.5849625007211560f, 7.5924570372680806f, - 7.5999128421871278f, 7.6073303137496104f, - 7.6147098441152083f, 7.6220518194563764f, - 7.6293566200796094f, 7.6366246205436487f, - 7.6438561897747243f, 7.6510516911789281f, - 7.6582114827517946f, 7.6653359171851764f, - 7.6724253419714951f, 7.6794800995054464f, - 7.6865005271832185f, 7.6934869574993252f, - 7.7004397181410917f, 7.7073591320808825f, - 7.7142455176661224f, 7.7210991887071855f, - 7.7279204545631987f, 7.7347096202258383f, - 7.7414669864011464f, 7.7481928495894605f, - 7.7548875021634682f, 7.7615512324444795f, - 7.7681843247769259f, 7.7747870596011736f, - 7.7813597135246599f, 7.7879025593914317f, - 7.7944158663501061f, 7.8008998999203047f, - 7.8073549220576037f, 7.8137811912170374f, - 7.8201789624151878f, 7.8265484872909150f, - 7.8328900141647412f, 7.8392037880969436f, - 7.8454900509443747f, 7.8517490414160571f, - 7.8579809951275718f, 7.8641861446542797f, - 7.8703647195834047f, 7.8765169465649993f, - 7.8826430493618415f, 7.8887432488982591f, - 7.8948177633079437f, 7.9008668079807486f, - 7.9068905956085187f, 7.9128893362299619f, - 7.9188632372745946f, 7.9248125036057812f, - 7.9307373375628866f, 7.9366379390025709f, - 7.9425145053392398f, 7.9483672315846778f, - 7.9541963103868749f, 7.9600019320680805f, - 7.9657842846620869f, 7.9715435539507719f, - 7.9772799234999167f, 7.9829935746943103f, - 7.9886846867721654f, 7.9943534368588577f -}; - -const float kSLog2Table[LOG_LOOKUP_IDX_MAX] = { - 0.00000000f, 0.00000000f, 2.00000000f, 4.75488750f, - 8.00000000f, 11.60964047f, 15.50977500f, 19.65148445f, - 24.00000000f, 28.52932501f, 33.21928095f, 38.05374781f, - 43.01955001f, 48.10571634f, 53.30296891f, 58.60335893f, - 64.00000000f, 69.48686830f, 75.05865003f, 80.71062276f, - 86.43856190f, 92.23866588f, 98.10749561f, 104.04192499f, - 110.03910002f, 116.09640474f, 122.21143267f, 128.38196256f, - 134.60593782f, 140.88144886f, 147.20671787f, 153.58008562f, - 160.00000000f, 166.46500594f, 172.97373660f, 179.52490559f, - 186.11730005f, 192.74977453f, 199.42124551f, 206.13068654f, - 212.87712380f, 219.65963219f, 226.47733176f, 233.32938445f, - 240.21499122f, 247.13338933f, 254.08384998f, 261.06567603f, - 268.07820003f, 275.12078236f, 282.19280949f, 289.29369244f, - 296.42286534f, 303.57978409f, 310.76392512f, 317.97478424f, - 325.21187564f, 332.47473081f, 339.76289772f, 347.07593991f, - 354.41343574f, 361.77497759f, 369.16017124f, 376.56863518f, - 384.00000000f, 391.45390785f, 398.93001188f, 406.42797576f, - 413.94747321f, 421.48818752f, 429.04981119f, 436.63204548f, - 444.23460010f, 451.85719280f, 459.49954906f, 467.16140179f, - 474.84249102f, 482.54256363f, 490.26137307f, 497.99867911f, - 505.75424759f, 513.52785023f, 521.31926438f, 529.12827280f, - 536.95466351f, 544.79822957f, 552.65876890f, 560.53608414f, - 568.42998244f, 576.34027536f, 584.26677867f, 592.20931226f, - 600.16769996f, 608.14176943f, 616.13135206f, 624.13628279f, - 632.15640007f, 640.19154569f, 648.24156472f, 656.30630539f, - 664.38561898f, 672.47935976f, 680.58738488f, 688.70955430f, - 696.84573069f, 704.99577935f, 713.15956818f, 721.33696754f, - 729.52785023f, 737.73209140f, 745.94956849f, 754.18016116f, - 762.42375127f, 770.68022275f, 778.94946161f, 787.23135586f, - 795.52579543f, 803.83267219f, 812.15187982f, 820.48331383f, - 828.82687147f, 837.18245171f, 845.54995518f, 853.92928416f, - 862.32034249f, 870.72303558f, 879.13727036f, 887.56295522f, - 896.00000000f, 904.44831595f, 912.90781569f, 921.37841320f, - 929.86002376f, 938.35256392f, 946.85595152f, 955.37010560f, - 963.89494641f, 972.43039537f, 980.97637504f, 989.53280911f, - 998.09962237f, 1006.67674069f, 1015.26409097f, 1023.86160116f, - 1032.46920021f, 1041.08681805f, 1049.71438560f, 1058.35183469f, - 1066.99909811f, 1075.65610955f, 1084.32280357f, 1092.99911564f, - 1101.68498204f, 1110.38033993f, 1119.08512727f, 1127.79928282f, - 1136.52274614f, 1145.25545758f, 1153.99735821f, 1162.74838989f, - 1171.50849518f, 1180.27761738f, 1189.05570047f, 1197.84268914f, - 1206.63852876f, 1215.44316535f, 1224.25654560f, 1233.07861684f, - 1241.90932703f, 1250.74862473f, 1259.59645914f, 1268.45278005f, - 1277.31753781f, 1286.19068338f, 1295.07216828f, 1303.96194457f, - 1312.85996488f, 1321.76618236f, 1330.68055071f, 1339.60302413f, - 1348.53355734f, 1357.47210556f, 1366.41862452f, 1375.37307041f, - 1384.33539991f, 1393.30557020f, 1402.28353887f, 1411.26926400f, - 1420.26270412f, 1429.26381818f, 1438.27256558f, 1447.28890615f, - 1456.31280014f, 1465.34420819f, 1474.38309138f, 1483.42941118f, - 1492.48312945f, 1501.54420843f, 1510.61261078f, 1519.68829949f, - 1528.77123795f, 1537.86138993f, 1546.95871952f, 1556.06319119f, - 1565.17476976f, 1574.29342040f, 1583.41910860f, 1592.55180020f, - 1601.69146137f, 1610.83805860f, 1619.99155871f, 1629.15192882f, - 1638.31913637f, 1647.49314911f, 1656.67393509f, 1665.86146266f, - 1675.05570047f, 1684.25661744f, 1693.46418280f, 1702.67836605f, - 1711.89913698f, 1721.12646563f, 1730.36032233f, 1739.60067768f, - 1748.84750254f, 1758.10076802f, 1767.36044551f, 1776.62650662f, - 1785.89892323f, 1795.17766747f, 1804.46271172f, 1813.75402857f, - 1823.05159087f, 1832.35537170f, 1841.66534438f, 1850.98148244f, - 1860.30375965f, 1869.63214999f, 1878.96662767f, 1888.30716711f, - 1897.65374295f, 1907.00633003f, 1916.36490342f, 1925.72943838f, - 1935.09991037f, 1944.47629506f, 1953.85856831f, 1963.24670620f, - 1972.64068498f, 1982.04048108f, 1991.44607117f, 2000.85743204f, - 2010.27454072f, 2019.69737440f, 2029.12591044f, 2038.56012640f -}; - -float VP8LFastSLog2Slow(int v) { - assert(v >= LOG_LOOKUP_IDX_MAX); - if (v < APPROX_LOG_MAX) { - int log_cnt = 0; - const float v_f = (float)v; - while (v >= LOG_LOOKUP_IDX_MAX) { - ++log_cnt; - v = v >> 1; - } - return v_f * (kLog2Table[v] + log_cnt); - } else { - return (float)(LOG_2_RECIPROCAL * v * log((double)v)); - } -} - -float VP8LFastLog2Slow(int v) { - assert(v >= LOG_LOOKUP_IDX_MAX); - if (v < APPROX_LOG_MAX) { - int log_cnt = 0; - while (v >= LOG_LOOKUP_IDX_MAX) { - ++log_cnt; - v = v >> 1; - } - return kLog2Table[v] + log_cnt; - } else { - return (float)(LOG_2_RECIPROCAL * log((double)v)); - } -} - //------------------------------------------------------------------------------ // Image transforms. -// In-place sum of each component with mod 256. -static WEBP_INLINE void AddPixelsEq(uint32_t* a, uint32_t b) { - const uint32_t alpha_and_green = (*a & 0xff00ff00u) + (b & 0xff00ff00u); - const uint32_t red_and_blue = (*a & 0x00ff00ffu) + (b & 0x00ff00ffu); - *a = (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu); -} - static WEBP_INLINE uint32_t Average2(uint32_t a0, uint32_t a1) { - return (((a0 ^ a1) & 0xfefefefeL) >> 1) + (a0 & a1); + return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); } static WEBP_INLINE uint32_t Average3(uint32_t a0, uint32_t a1, uint32_t a2) { @@ -287,61 +40,6 @@ static WEBP_INLINE uint32_t Average4(uint32_t a0, uint32_t a1, return Average2(Average2(a0, a1), Average2(a2, a3)); } -#if defined(WEBP_TARGET_HAS_SSE2) -static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, - uint32_t c2) { - const __m128i zero = _mm_setzero_si128(); - const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c0), zero); - const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c1), zero); - const __m128i C2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c2), zero); - const __m128i V1 = _mm_add_epi16(C0, C1); - const __m128i V2 = _mm_sub_epi16(V1, C2); - const __m128i b = _mm_packus_epi16(V2, V2); - const uint32_t output = _mm_cvtsi128_si32(b); - return output; -} - -static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1, - uint32_t c2) { - const uint32_t ave = Average2(c0, c1); - const __m128i zero = _mm_setzero_si128(); - const __m128i A0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(ave), zero); - const __m128i B0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c2), zero); - const __m128i A1 = _mm_sub_epi16(A0, B0); - const __m128i BgtA = _mm_cmpgt_epi16(B0, A0); - const __m128i A2 = _mm_sub_epi16(A1, BgtA); - const __m128i A3 = _mm_srai_epi16(A2, 1); - const __m128i A4 = _mm_add_epi16(A0, A3); - const __m128i A5 = _mm_packus_epi16(A4, A4); - const uint32_t output = _mm_cvtsi128_si32(A5); - return output; -} - -static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) { - int pa_minus_pb; - const __m128i zero = _mm_setzero_si128(); - const __m128i A0 = _mm_cvtsi32_si128(a); - const __m128i B0 = _mm_cvtsi32_si128(b); - const __m128i C0 = _mm_cvtsi32_si128(c); - const __m128i AC0 = _mm_subs_epu8(A0, C0); - const __m128i CA0 = _mm_subs_epu8(C0, A0); - const __m128i BC0 = _mm_subs_epu8(B0, C0); - const __m128i CB0 = _mm_subs_epu8(C0, B0); - const __m128i AC = _mm_or_si128(AC0, CA0); - const __m128i BC = _mm_or_si128(BC0, CB0); - const __m128i pa = _mm_unpacklo_epi8(AC, zero); // |a - c| - const __m128i pb = _mm_unpacklo_epi8(BC, zero); // |b - c| - const __m128i diff = _mm_sub_epi16(pb, pa); - { - int16_t out[8]; - _mm_storeu_si128((__m128i*)out, diff); - pa_minus_pb = out[0] + out[1] + out[2] + out[3]; - } - return (pa_minus_pb <= 0) ? a : b; -} - -#else - static WEBP_INLINE uint32_t Clip255(uint32_t a) { if (a < 256) { return a; @@ -365,7 +63,7 @@ static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, (c1 >> 8) & 0xff, (c2 >> 8) & 0xff); const int b = AddSubtractComponentFull(c0 & 0xff, c1 & 0xff, c2 & 0xff); - return (a << 24) | (r << 16) | (g << 8) | b; + return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; } static WEBP_INLINE int AddSubtractComponentHalf(int a, int b) { @@ -379,15 +77,24 @@ static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1, const int r = AddSubtractComponentHalf((ave >> 16) & 0xff, (c2 >> 16) & 0xff); const int g = AddSubtractComponentHalf((ave >> 8) & 0xff, (c2 >> 8) & 0xff); const int b = AddSubtractComponentHalf((ave >> 0) & 0xff, (c2 >> 0) & 0xff); - return (a << 24) | (r << 16) | (g << 8) | b; + return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; } -static WEBP_INLINE int Sub3(int a, int b, int c) { +// gcc-4.9 on ARM generates incorrect code in Select() when Sub3() is inlined. +#if defined(__arm__) && LOCAL_GCC_VERSION == 0x409 +# define LOCAL_INLINE __attribute__ ((noinline)) +#else +# define LOCAL_INLINE WEBP_INLINE +#endif + +static LOCAL_INLINE int Sub3(int a, int b, int c) { const int pb = b - c; const int pa = a - c; return abs(pb) - abs(pa); } +#undef LOCAL_INLINE + static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) { const int pa_minus_pb = Sub3((a >> 24) , (b >> 24) , (c >> 24) ) + @@ -396,7 +103,6 @@ static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) { Sub3((a ) & 0xff, (b ) & 0xff, (c ) & 0xff); return (pa_minus_pb <= 0) ? a : b; } -#endif //------------------------------------------------------------------------------ // Predictors @@ -461,253 +167,68 @@ static uint32_t Predictor13(uint32_t left, const uint32_t* const top) { return pred; } -typedef uint32_t (*PredictorFunc)(uint32_t left, const uint32_t* const top); -static const PredictorFunc kPredictors[16] = { - Predictor0, Predictor1, Predictor2, Predictor3, - Predictor4, Predictor5, Predictor6, Predictor7, - Predictor8, Predictor9, Predictor10, Predictor11, - Predictor12, Predictor13, - Predictor0, Predictor0 // <- padding security sentinels -}; - -// TODO(vikasa): Replace 256 etc with defines. -static float PredictionCostSpatial(const int* counts, - int weight_0, double exp_val) { - const int significant_symbols = 16; - const double exp_decay_factor = 0.6; - double bits = weight_0 * counts[0]; +GENERATE_PREDICTOR_ADD(Predictor0, PredictorAdd0) +static void PredictorAdd1(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { int i; - for (i = 1; i < significant_symbols; ++i) { - bits += exp_val * (counts[i] + counts[256 - i]); - exp_val *= exp_decay_factor; + uint32_t left = out[-1]; + for (i = 0; i < num_pixels; ++i) { + out[i] = left = VP8LAddPixels(in[i], left); } - return (float)(-0.1 * bits); + (void)upper; } +GENERATE_PREDICTOR_ADD(Predictor2, PredictorAdd2) +GENERATE_PREDICTOR_ADD(Predictor3, PredictorAdd3) +GENERATE_PREDICTOR_ADD(Predictor4, PredictorAdd4) +GENERATE_PREDICTOR_ADD(Predictor5, PredictorAdd5) +GENERATE_PREDICTOR_ADD(Predictor6, PredictorAdd6) +GENERATE_PREDICTOR_ADD(Predictor7, PredictorAdd7) +GENERATE_PREDICTOR_ADD(Predictor8, PredictorAdd8) +GENERATE_PREDICTOR_ADD(Predictor9, PredictorAdd9) +GENERATE_PREDICTOR_ADD(Predictor10, PredictorAdd10) +GENERATE_PREDICTOR_ADD(Predictor11, PredictorAdd11) +GENERATE_PREDICTOR_ADD(Predictor12, PredictorAdd12) +GENERATE_PREDICTOR_ADD(Predictor13, PredictorAdd13) -// Compute the combined Shanon's entropy for distribution {X} and {X+Y} -static float CombinedShannonEntropy(const int* const X, - const int* const Y, int n) { - int i; - double retval = 0.; - int sumX = 0, sumXY = 0; - for (i = 0; i < n; ++i) { - const int x = X[i]; - const int xy = X[i] + Y[i]; - if (x != 0) { - sumX += x; - retval -= VP8LFastSLog2(x); - } - if (xy != 0) { - sumXY += xy; - retval -= VP8LFastSLog2(xy); - } - } - retval += VP8LFastSLog2(sumX) + VP8LFastSLog2(sumXY); - return (float)retval; -} - -static float PredictionCostSpatialHistogram(int accumulated[4][256], - int tile[4][256]) { - int i; - double retval = 0; - for (i = 0; i < 4; ++i) { - const double kExpValue = 0.94; - retval += PredictionCostSpatial(tile[i], 1, kExpValue); - retval += CombinedShannonEntropy(tile[i], accumulated[i], 256); - } - return (float)retval; -} - -static int GetBestPredictorForTile(int width, int height, - int tile_x, int tile_y, int bits, - int accumulated[4][256], - const uint32_t* const argb_scratch) { - const int kNumPredModes = 14; - const int col_start = tile_x << bits; - const int row_start = tile_y << bits; - const int tile_size = 1 << bits; - const int ymax = (tile_size <= height - row_start) ? - tile_size : height - row_start; - const int xmax = (tile_size <= width - col_start) ? - tile_size : width - col_start; - int histo[4][256]; - float best_diff = MAX_DIFF_COST; - int best_mode = 0; - - int mode; - for (mode = 0; mode < kNumPredModes; ++mode) { - const uint32_t* current_row = argb_scratch; - const PredictorFunc pred_func = kPredictors[mode]; - float cur_diff; - int y; - memset(&histo[0][0], 0, sizeof(histo)); - for (y = 0; y < ymax; ++y) { - int x; - const int row = row_start + y; - const uint32_t* const upper_row = current_row; - current_row = upper_row + width; - for (x = 0; x < xmax; ++x) { - const int col = col_start + x; - uint32_t predict; - uint32_t predict_diff; - if (row == 0) { - predict = (col == 0) ? ARGB_BLACK : current_row[col - 1]; // Left. - } else if (col == 0) { - predict = upper_row[col]; // Top. - } else { - predict = pred_func(current_row[col - 1], upper_row + col); - } - predict_diff = VP8LSubPixels(current_row[col], predict); - ++histo[0][predict_diff >> 24]; - ++histo[1][((predict_diff >> 16) & 0xff)]; - ++histo[2][((predict_diff >> 8) & 0xff)]; - ++histo[3][(predict_diff & 0xff)]; - } - } - cur_diff = PredictionCostSpatialHistogram(accumulated, histo); - if (cur_diff < best_diff) { - best_diff = cur_diff; - best_mode = mode; - } - } - - return best_mode; -} - -static void CopyTileWithPrediction(int width, int height, - int tile_x, int tile_y, int bits, int mode, - const uint32_t* const argb_scratch, - uint32_t* const argb) { - const int col_start = tile_x << bits; - const int row_start = tile_y << bits; - const int tile_size = 1 << bits; - const int ymax = (tile_size <= height - row_start) ? - tile_size : height - row_start; - const int xmax = (tile_size <= width - col_start) ? - tile_size : width - col_start; - const PredictorFunc pred_func = kPredictors[mode]; - const uint32_t* current_row = argb_scratch; - - int y; - for (y = 0; y < ymax; ++y) { - int x; - const int row = row_start + y; - const uint32_t* const upper_row = current_row; - current_row = upper_row + width; - for (x = 0; x < xmax; ++x) { - const int col = col_start + x; - const int pix = row * width + col; - uint32_t predict; - if (row == 0) { - predict = (col == 0) ? ARGB_BLACK : current_row[col - 1]; // Left. - } else if (col == 0) { - predict = upper_row[col]; // Top. - } else { - predict = pred_func(current_row[col - 1], upper_row + col); - } - argb[pix] = VP8LSubPixels(current_row[col], predict); - } - } -} - -void VP8LResidualImage(int width, int height, int bits, - uint32_t* const argb, uint32_t* const argb_scratch, - uint32_t* const image) { - const int max_tile_size = 1 << bits; - const int tiles_per_row = VP8LSubSampleSize(width, bits); - const int tiles_per_col = VP8LSubSampleSize(height, bits); - uint32_t* const upper_row = argb_scratch; - uint32_t* const current_tile_rows = argb_scratch + width; - int tile_y; - int histo[4][256]; - memset(histo, 0, sizeof(histo)); - for (tile_y = 0; tile_y < tiles_per_col; ++tile_y) { - const int tile_y_offset = tile_y * max_tile_size; - const int this_tile_height = - (tile_y < tiles_per_col - 1) ? max_tile_size : height - tile_y_offset; - int tile_x; - if (tile_y > 0) { - memcpy(upper_row, current_tile_rows + (max_tile_size - 1) * width, - width * sizeof(*upper_row)); - } - memcpy(current_tile_rows, &argb[tile_y_offset * width], - this_tile_height * width * sizeof(*current_tile_rows)); - for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) { - int pred; - int y; - const int tile_x_offset = tile_x * max_tile_size; - int all_x_max = tile_x_offset + max_tile_size; - if (all_x_max > width) { - all_x_max = width; - } - pred = GetBestPredictorForTile(width, height, tile_x, tile_y, bits, histo, - argb_scratch); - image[tile_y * tiles_per_row + tile_x] = 0xff000000u | (pred << 8); - CopyTileWithPrediction(width, height, tile_x, tile_y, bits, pred, - argb_scratch, argb); - for (y = 0; y < max_tile_size; ++y) { - int ix; - int all_x; - int all_y = tile_y_offset + y; - if (all_y >= height) { - break; - } - ix = all_y * width + tile_x_offset; - for (all_x = tile_x_offset; all_x < all_x_max; ++all_x, ++ix) { - const uint32_t a = argb[ix]; - ++histo[0][a >> 24]; - ++histo[1][((a >> 16) & 0xff)]; - ++histo[2][((a >> 8) & 0xff)]; - ++histo[3][(a & 0xff)]; - } - } - } - } -} +//------------------------------------------------------------------------------ // Inverse prediction. static void PredictorInverseTransform(const VP8LTransform* const transform, - int y_start, int y_end, uint32_t* data) { + int y_start, int y_end, + const uint32_t* in, uint32_t* out) { const int width = transform->xsize_; if (y_start == 0) { // First Row follows the L (mode=1) mode. - int x; - const uint32_t pred0 = Predictor0(data[-1], NULL); - AddPixelsEq(data, pred0); - for (x = 1; x < width; ++x) { - const uint32_t pred1 = Predictor1(data[x - 1], NULL); - AddPixelsEq(data + x, pred1); - } - data += width; + PredictorAdd0(in, NULL, 1, out); + PredictorAdd1(in + 1, NULL, width - 1, out + 1); + in += width; + out += width; ++y_start; } { int y = y_start; - const int mask = (1 << transform->bits_) - 1; + const int tile_width = 1 << transform->bits_; + const int mask = tile_width - 1; const int tiles_per_row = VP8LSubSampleSize(width, transform->bits_); const uint32_t* pred_mode_base = transform->data_ + (y >> transform->bits_) * tiles_per_row; while (y < y_end) { - int x; - const uint32_t pred2 = Predictor2(data[-1], data - width); const uint32_t* pred_mode_src = pred_mode_base; - PredictorFunc pred_func; - + int x = 1; // First pixel follows the T (mode=2) mode. - AddPixelsEq(data, pred2); - + PredictorAdd2(in, out - width, 1, out); // .. the rest: - pred_func = kPredictors[((*pred_mode_src++) >> 8) & 0xf]; - for (x = 1; x < width; ++x) { - uint32_t pred; - if ((x & mask) == 0) { // start of tile. Read predictor function. - pred_func = kPredictors[((*pred_mode_src++) >> 8) & 0xf]; - } - pred = pred_func(data[x - 1], data + x - width); - AddPixelsEq(data + x, pred); + while (x < width) { + const VP8LPredictorAddSubFunc pred_func = + VP8LPredictorsAdd[((*pred_mode_src++) >> 8) & 0xf]; + int x_end = (x & ~mask) + tile_width; + if (x_end > width) x_end = width; + pred_func(in + x, out + x - width, x_end - x, out + x); + x = x_end; } - data += width; + in += width; + out += width; ++y; if ((y & mask) == 0) { // Use the same mask, since tiles are squares. pred_mode_base += tiles_per_row; @@ -716,364 +237,61 @@ static void PredictorInverseTransform(const VP8LTransform* const transform, } } -void VP8LSubtractGreenFromBlueAndRed(uint32_t* argb_data, int num_pixs) { - int i = 0; -#if defined(WEBP_TARGET_HAS_SSE2) - const __m128i mask = _mm_set1_epi32(0x0000ff00); - for (; i + 4 < num_pixs; i += 4) { - const __m128i in = _mm_loadu_si128((__m128i*)&argb_data[i]); - const __m128i in_00g0 = _mm_and_si128(in, mask); // 00g0|00g0|... - const __m128i in_0g00 = _mm_slli_epi32(in_00g0, 8); // 0g00|0g00|... - const __m128i in_000g = _mm_srli_epi32(in_00g0, 8); // 000g|000g|... - const __m128i in_0g0g = _mm_or_si128(in_0g00, in_000g); - const __m128i out = _mm_sub_epi8(in, in_0g0g); - _mm_storeu_si128((__m128i*)&argb_data[i], out); - } - // fallthrough and finish off with plain-C -#endif - for (; i < num_pixs; ++i) { - const uint32_t argb = argb_data[i]; - const uint32_t green = (argb >> 8) & 0xff; - const uint32_t new_r = (((argb >> 16) & 0xff) - green) & 0xff; - const uint32_t new_b = ((argb & 0xff) - green) & 0xff; - argb_data[i] = (argb & 0xff00ff00) | (new_r << 16) | new_b; - } -} - // Add green to blue and red channels (i.e. perform the inverse transform of // 'subtract green'). -static void AddGreenToBlueAndRed(const VP8LTransform* const transform, - int y_start, int y_end, uint32_t* data) { - const int width = transform->xsize_; - const uint32_t* const data_end = data + (y_end - y_start) * width; -#if defined(WEBP_TARGET_HAS_SSE2) - const __m128i mask = _mm_set1_epi32(0x0000ff00); - for (; data + 4 < data_end; data += 4) { - const __m128i in = _mm_loadu_si128((__m128i*)data); - const __m128i in_00g0 = _mm_and_si128(in, mask); // 00g0|00g0|... - const __m128i in_0g00 = _mm_slli_epi32(in_00g0, 8); // 0g00|0g00|... - const __m128i in_000g = _mm_srli_epi32(in_00g0, 8); // 000g|000g|... - const __m128i in_0g0g = _mm_or_si128(in_0g00, in_000g); - const __m128i out = _mm_add_epi8(in, in_0g0g); - _mm_storeu_si128((__m128i*)data, out); - } - // fallthrough and finish off with plain-C -#endif - while (data < data_end) { - const uint32_t argb = *data; +void VP8LAddGreenToBlueAndRed_C(const uint32_t* src, int num_pixels, + uint32_t* dst) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint32_t argb = src[i]; const uint32_t green = ((argb >> 8) & 0xff); uint32_t red_blue = (argb & 0x00ff00ffu); red_blue += (green << 16) | green; red_blue &= 0x00ff00ffu; - *data++ = (argb & 0xff00ff00u) | red_blue; + dst[i] = (argb & 0xff00ff00u) | red_blue; } } -typedef struct { - // Note: the members are uint8_t, so that any negative values are - // automatically converted to "mod 256" values. - uint8_t green_to_red_; - uint8_t green_to_blue_; - uint8_t red_to_blue_; -} Multipliers; - -static WEBP_INLINE void MultipliersClear(Multipliers* m) { - m->green_to_red_ = 0; - m->green_to_blue_ = 0; - m->red_to_blue_ = 0; -} - -static WEBP_INLINE uint32_t ColorTransformDelta(int8_t color_pred, - int8_t color) { - return (uint32_t)((int)(color_pred) * color) >> 5; +static WEBP_INLINE int ColorTransformDelta(int8_t color_pred, + int8_t color) { + return ((int)color_pred * color) >> 5; } static WEBP_INLINE void ColorCodeToMultipliers(uint32_t color_code, - Multipliers* const m) { + VP8LMultipliers* const m) { m->green_to_red_ = (color_code >> 0) & 0xff; m->green_to_blue_ = (color_code >> 8) & 0xff; m->red_to_blue_ = (color_code >> 16) & 0xff; } -static WEBP_INLINE uint32_t MultipliersToColorCode(Multipliers* const m) { - return 0xff000000u | - ((uint32_t)(m->red_to_blue_) << 16) | - ((uint32_t)(m->green_to_blue_) << 8) | - m->green_to_red_; -} - -static WEBP_INLINE uint32_t TransformColor(const Multipliers* const m, - uint32_t argb, int inverse) { - const uint32_t green = argb >> 8; - const uint32_t red = argb >> 16; - uint32_t new_red = red; - uint32_t new_blue = argb; - - if (inverse) { +void VP8LTransformColorInverse_C(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint32_t argb = src[i]; + const uint32_t green = argb >> 8; + const uint32_t red = argb >> 16; + int new_red = red; + int new_blue = argb; new_red += ColorTransformDelta(m->green_to_red_, green); new_red &= 0xff; new_blue += ColorTransformDelta(m->green_to_blue_, green); new_blue += ColorTransformDelta(m->red_to_blue_, new_red); new_blue &= 0xff; - } else { - new_red -= ColorTransformDelta(m->green_to_red_, green); - new_red &= 0xff; - new_blue -= ColorTransformDelta(m->green_to_blue_, green); - new_blue -= ColorTransformDelta(m->red_to_blue_, red); - new_blue &= 0xff; - } - return (argb & 0xff00ff00u) | (new_red << 16) | (new_blue); -} - -static WEBP_INLINE uint8_t TransformColorRed(uint8_t green_to_red, - uint32_t argb) { - const uint32_t green = argb >> 8; - uint32_t new_red = argb >> 16; - new_red -= ColorTransformDelta(green_to_red, green); - return (new_red & 0xff); -} - -static WEBP_INLINE uint8_t TransformColorBlue(uint8_t green_to_blue, - uint8_t red_to_blue, - uint32_t argb) { - const uint32_t green = argb >> 8; - const uint32_t red = argb >> 16; - uint8_t new_blue = argb; - new_blue -= ColorTransformDelta(green_to_blue, green); - new_blue -= ColorTransformDelta(red_to_blue, red); - return (new_blue & 0xff); -} - -static WEBP_INLINE int SkipRepeatedPixels(const uint32_t* const argb, - int ix, int xsize) { - const uint32_t v = argb[ix]; - if (ix >= xsize + 3) { - if (v == argb[ix - xsize] && - argb[ix - 1] == argb[ix - xsize - 1] && - argb[ix - 2] == argb[ix - xsize - 2] && - argb[ix - 3] == argb[ix - xsize - 3]) { - return 1; - } - return v == argb[ix - 3] && v == argb[ix - 2] && v == argb[ix - 1]; - } else if (ix >= 3) { - return v == argb[ix - 3] && v == argb[ix - 2] && v == argb[ix - 1]; - } - return 0; -} - -static float PredictionCostCrossColor(const int accumulated[256], - const int counts[256]) { - // Favor low entropy, locally and globally. - // Favor small absolute values for PredictionCostSpatial - static const double kExpValue = 2.4; - return CombinedShannonEntropy(counts, accumulated, 256) + - PredictionCostSpatial(counts, 3, kExpValue); -} - -static Multipliers GetBestColorTransformForTile( - int tile_x, int tile_y, int bits, - Multipliers prevX, - Multipliers prevY, - int step, int xsize, int ysize, - int* accumulated_red_histo, - int* accumulated_blue_histo, - const uint32_t* const argb) { - float best_diff = MAX_DIFF_COST; - float cur_diff; - const int halfstep = step / 2; - const int max_tile_size = 1 << bits; - const int tile_y_offset = tile_y * max_tile_size; - const int tile_x_offset = tile_x * max_tile_size; - int green_to_red; - int green_to_blue; - int red_to_blue; - int all_x_max = tile_x_offset + max_tile_size; - int all_y_max = tile_y_offset + max_tile_size; - Multipliers best_tx; - MultipliersClear(&best_tx); - if (all_x_max > xsize) { - all_x_max = xsize; - } - if (all_y_max > ysize) { - all_y_max = ysize; - } - - for (green_to_red = -64; green_to_red <= 64; green_to_red += halfstep) { - int histo[256] = { 0 }; - int all_y; - - for (all_y = tile_y_offset; all_y < all_y_max; ++all_y) { - int ix = all_y * xsize + tile_x_offset; - int all_x; - for (all_x = tile_x_offset; all_x < all_x_max; ++all_x, ++ix) { - if (SkipRepeatedPixels(argb, ix, xsize)) { - continue; - } - ++histo[TransformColorRed(green_to_red, argb[ix])]; // red. - } - } - cur_diff = PredictionCostCrossColor(&accumulated_red_histo[0], &histo[0]); - if ((uint8_t)green_to_red == prevX.green_to_red_) { - cur_diff -= 3; // favor keeping the areas locally similar - } - if ((uint8_t)green_to_red == prevY.green_to_red_) { - cur_diff -= 3; // favor keeping the areas locally similar - } - if (green_to_red == 0) { - cur_diff -= 3; - } - if (cur_diff < best_diff) { - best_diff = cur_diff; - best_tx.green_to_red_ = green_to_red; - } - } - best_diff = MAX_DIFF_COST; - for (green_to_blue = -32; green_to_blue <= 32; green_to_blue += step) { - for (red_to_blue = -32; red_to_blue <= 32; red_to_blue += step) { - int all_y; - int histo[256] = { 0 }; - for (all_y = tile_y_offset; all_y < all_y_max; ++all_y) { - int all_x; - int ix = all_y * xsize + tile_x_offset; - for (all_x = tile_x_offset; all_x < all_x_max; ++all_x, ++ix) { - if (SkipRepeatedPixels(argb, ix, xsize)) { - continue; - } - ++histo[TransformColorBlue(green_to_blue, red_to_blue, argb[ix])]; - } - } - cur_diff = - PredictionCostCrossColor(&accumulated_blue_histo[0], &histo[0]); - if ((uint8_t)green_to_blue == prevX.green_to_blue_) { - cur_diff -= 3; // favor keeping the areas locally similar - } - if ((uint8_t)green_to_blue == prevY.green_to_blue_) { - cur_diff -= 3; // favor keeping the areas locally similar - } - if ((uint8_t)red_to_blue == prevX.red_to_blue_) { - cur_diff -= 3; // favor keeping the areas locally similar - } - if ((uint8_t)red_to_blue == prevY.red_to_blue_) { - cur_diff -= 3; // favor keeping the areas locally similar - } - if (green_to_blue == 0) { - cur_diff -= 3; - } - if (red_to_blue == 0) { - cur_diff -= 3; - } - if (cur_diff < best_diff) { - best_diff = cur_diff; - best_tx.green_to_blue_ = green_to_blue; - best_tx.red_to_blue_ = red_to_blue; - } - } - } - return best_tx; -} - -static void CopyTileWithColorTransform(int xsize, int ysize, - int tile_x, int tile_y, int bits, - Multipliers color_transform, - uint32_t* const argb) { - int y; - int xscan = 1 << bits; - int yscan = 1 << bits; - tile_x <<= bits; - tile_y <<= bits; - if (xscan > xsize - tile_x) { - xscan = xsize - tile_x; - } - if (yscan > ysize - tile_y) { - yscan = ysize - tile_y; - } - yscan += tile_y; - for (y = tile_y; y < yscan; ++y) { - int ix = y * xsize + tile_x; - const int end_ix = ix + xscan; - for (; ix < end_ix; ++ix) { - argb[ix] = TransformColor(&color_transform, argb[ix], 0); - } - } -} - -void VP8LColorSpaceTransform(int width, int height, int bits, int step, - uint32_t* const argb, uint32_t* image) { - const int max_tile_size = 1 << bits; - int tile_xsize = VP8LSubSampleSize(width, bits); - int tile_ysize = VP8LSubSampleSize(height, bits); - int accumulated_red_histo[256] = { 0 }; - int accumulated_blue_histo[256] = { 0 }; - int tile_y; - int tile_x; - Multipliers prevX; - Multipliers prevY; - MultipliersClear(&prevY); - MultipliersClear(&prevX); - for (tile_y = 0; tile_y < tile_ysize; ++tile_y) { - for (tile_x = 0; tile_x < tile_xsize; ++tile_x) { - Multipliers color_transform; - int all_x_max; - int y; - const int tile_y_offset = tile_y * max_tile_size; - const int tile_x_offset = tile_x * max_tile_size; - if (tile_y != 0) { - ColorCodeToMultipliers(image[tile_y * tile_xsize + tile_x - 1], &prevX); - ColorCodeToMultipliers(image[(tile_y - 1) * tile_xsize + tile_x], - &prevY); - } else if (tile_x != 0) { - ColorCodeToMultipliers(image[tile_y * tile_xsize + tile_x - 1], &prevX); - } - color_transform = - GetBestColorTransformForTile(tile_x, tile_y, bits, - prevX, prevY, - step, width, height, - &accumulated_red_histo[0], - &accumulated_blue_histo[0], - argb); - image[tile_y * tile_xsize + tile_x] = - MultipliersToColorCode(&color_transform); - CopyTileWithColorTransform(width, height, tile_x, tile_y, bits, - color_transform, argb); - - // Gather accumulated histogram data. - all_x_max = tile_x_offset + max_tile_size; - if (all_x_max > width) { - all_x_max = width; - } - for (y = 0; y < max_tile_size; ++y) { - int ix; - int all_x; - int all_y = tile_y_offset + y; - if (all_y >= height) { - break; - } - ix = all_y * width + tile_x_offset; - for (all_x = tile_x_offset; all_x < all_x_max; ++all_x, ++ix) { - if (ix >= 2 && - argb[ix] == argb[ix - 2] && - argb[ix] == argb[ix - 1]) { - continue; // repeated pixels are handled by backward references - } - if (ix >= width + 2 && - argb[ix - 2] == argb[ix - width - 2] && - argb[ix - 1] == argb[ix - width - 1] && - argb[ix] == argb[ix - width]) { - continue; // repeated pixels are handled by backward references - } - ++accumulated_red_histo[(argb[ix] >> 16) & 0xff]; - ++accumulated_blue_histo[argb[ix] & 0xff]; - } - } - } + dst[i] = (argb & 0xff00ff00u) | (new_red << 16) | (new_blue); } } // Color space inverse transform. static void ColorSpaceInverseTransform(const VP8LTransform* const transform, - int y_start, int y_end, uint32_t* data) { + int y_start, int y_end, + const uint32_t* src, uint32_t* dst) { const int width = transform->xsize_; - const int mask = (1 << transform->bits_) - 1; + const int tile_width = 1 << transform->bits_; + const int mask = tile_width - 1; + const int safe_width = width & ~mask; + const int remaining_width = width - safe_width; const int tiles_per_row = VP8LSubSampleSize(width, transform->bits_); int y = y_start; const uint32_t* pred_row = @@ -1081,24 +299,43 @@ static void ColorSpaceInverseTransform(const VP8LTransform* const transform, while (y < y_end) { const uint32_t* pred = pred_row; - Multipliers m = { 0, 0, 0 }; - int x; - - for (x = 0; x < width; ++x) { - if ((x & mask) == 0) ColorCodeToMultipliers(*pred++, &m); - data[x] = TransformColor(&m, data[x], 1); + VP8LMultipliers m = { 0, 0, 0 }; + const uint32_t* const src_safe_end = src + safe_width; + const uint32_t* const src_end = src + width; + while (src < src_safe_end) { + ColorCodeToMultipliers(*pred++, &m); + VP8LTransformColorInverse(&m, src, tile_width, dst); + src += tile_width; + dst += tile_width; + } + if (src < src_end) { // Left-overs using C-version. + ColorCodeToMultipliers(*pred++, &m); + VP8LTransformColorInverse(&m, src, remaining_width, dst); + src += remaining_width; + dst += remaining_width; } - data += width; ++y; - if ((y & mask) == 0) pred_row += tiles_per_row;; + if ((y & mask) == 0) pred_row += tiles_per_row; } } // Separate out pixels packed together using pixel-bundling. // We define two methods for ARGB data (uint32_t) and alpha-only data (uint8_t). -#define COLOR_INDEX_INVERSE(FUNC_NAME, TYPE, GET_INDEX, GET_VALUE) \ -void FUNC_NAME(const VP8LTransform* const transform, \ - int y_start, int y_end, const TYPE* src, TYPE* dst) { \ +#define COLOR_INDEX_INVERSE(FUNC_NAME, F_NAME, STATIC_DECL, TYPE, BIT_SUFFIX, \ + GET_INDEX, GET_VALUE) \ +static void F_NAME(const TYPE* src, const uint32_t* const color_map, \ + TYPE* dst, int y_start, int y_end, int width) { \ + int y; \ + for (y = y_start; y < y_end; ++y) { \ + int x; \ + for (x = 0; x < width; ++x) { \ + *dst++ = GET_VALUE(color_map[GET_INDEX(*src++)]); \ + } \ + } \ +} \ +STATIC_DECL void FUNC_NAME(const VP8LTransform* const transform, \ + int y_start, int y_end, const TYPE* src, \ + TYPE* dst) { \ int y; \ const int bits_per_pixel = 8 >> transform->bits_; \ const int width = transform->xsize_; \ @@ -1121,59 +358,38 @@ void FUNC_NAME(const VP8LTransform* const transform, \ } \ } \ } else { \ - for (y = y_start; y < y_end; ++y) { \ - int x; \ - for (x = 0; x < width; ++x) { \ - *dst++ = GET_VALUE(color_map[GET_INDEX(*src++)]); \ - } \ - } \ + VP8LMapColor##BIT_SUFFIX(src, color_map, dst, y_start, y_end, width); \ } \ } -static WEBP_INLINE uint32_t GetARGBIndex(uint32_t idx) { - return (idx >> 8) & 0xff; -} - -static WEBP_INLINE uint8_t GetAlphaIndex(uint8_t idx) { - return idx; -} - -static WEBP_INLINE uint32_t GetARGBValue(uint32_t val) { - return val; -} - -static WEBP_INLINE uint8_t GetAlphaValue(uint32_t val) { - return (val >> 8) & 0xff; -} - -static COLOR_INDEX_INVERSE(ColorIndexInverseTransform, uint32_t, GetARGBIndex, - GetARGBValue) -COLOR_INDEX_INVERSE(VP8LColorIndexInverseTransformAlpha, uint8_t, GetAlphaIndex, - GetAlphaValue) +COLOR_INDEX_INVERSE(ColorIndexInverseTransform, MapARGB, static, uint32_t, 32b, + VP8GetARGBIndex, VP8GetARGBValue) +COLOR_INDEX_INVERSE(VP8LColorIndexInverseTransformAlpha, MapAlpha, , uint8_t, + 8b, VP8GetAlphaIndex, VP8GetAlphaValue) #undef COLOR_INDEX_INVERSE void VP8LInverseTransform(const VP8LTransform* const transform, int row_start, int row_end, const uint32_t* const in, uint32_t* const out) { + const int width = transform->xsize_; assert(row_start < row_end); assert(row_end <= transform->ysize_); switch (transform->type_) { case SUBTRACT_GREEN: - AddGreenToBlueAndRed(transform, row_start, row_end, out); + VP8LAddGreenToBlueAndRed(in, (row_end - row_start) * width, out); break; case PREDICTOR_TRANSFORM: - PredictorInverseTransform(transform, row_start, row_end, out); + PredictorInverseTransform(transform, row_start, row_end, in, out); if (row_end != transform->ysize_) { // The last predicted row in this iteration will be the top-pred row // for the first row in next iteration. - const int width = transform->xsize_; memcpy(out - width, out + (row_end - row_start - 1) * width, width * sizeof(*out)); } break; case CROSS_COLOR_TRANSFORM: - ColorSpaceInverseTransform(transform, row_start, row_end, out); + ColorSpaceInverseTransform(transform, row_start, row_end, in, out); break; case COLOR_INDEXING_TRANSFORM: if (in == out && transform->bits_ > 0) { @@ -1182,7 +398,7 @@ void VP8LInverseTransform(const VP8LTransform* const transform, // Also, note that this is the only transform that applies on // the effective width of VP8LSubSampleSize(xsize_, bits_). All other // transforms work on effective width of xsize_. - const int out_stride = (row_end - row_start) * transform->xsize_; + const int out_stride = (row_end - row_start) * width; const int in_stride = (row_end - row_start) * VP8LSubSampleSize(transform->xsize_, transform->bits_); uint32_t* const src = out + out_stride - in_stride; @@ -1206,8 +422,8 @@ static int is_big_endian(void) { return (tmp.b[0] != 1); } -static void ConvertBGRAToRGB(const uint32_t* src, - int num_pixels, uint8_t* dst) { +void VP8LConvertBGRAToRGB_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { const uint32_t* const src_end = src + num_pixels; while (src < src_end) { const uint32_t argb = *src++; @@ -1217,8 +433,8 @@ static void ConvertBGRAToRGB(const uint32_t* src, } } -static void ConvertBGRAToRGBA(const uint32_t* src, - int num_pixels, uint8_t* dst) { +void VP8LConvertBGRAToRGBA_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { const uint32_t* const src_end = src + num_pixels; while (src < src_end) { const uint32_t argb = *src++; @@ -1229,8 +445,8 @@ static void ConvertBGRAToRGBA(const uint32_t* src, } } -static void ConvertBGRAToRGBA4444(const uint32_t* src, - int num_pixels, uint8_t* dst) { +void VP8LConvertBGRAToRGBA4444_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { const uint32_t* const src_end = src + num_pixels; while (src < src_end) { const uint32_t argb = *src++; @@ -1246,8 +462,8 @@ static void ConvertBGRAToRGBA4444(const uint32_t* src, } } -static void ConvertBGRAToRGB565(const uint32_t* src, - int num_pixels, uint8_t* dst) { +void VP8LConvertBGRAToRGB565_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { const uint32_t* const src_end = src + num_pixels; while (src < src_end) { const uint32_t argb = *src++; @@ -1263,8 +479,8 @@ static void ConvertBGRAToRGB565(const uint32_t* src, } } -static void ConvertBGRAToBGR(const uint32_t* src, - int num_pixels, uint8_t* dst) { +void VP8LConvertBGRAToBGR_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { const uint32_t* const src_end = src + num_pixels; while (src < src_end) { const uint32_t argb = *src++; @@ -1279,29 +495,18 @@ static void CopyOrSwap(const uint32_t* src, int num_pixels, uint8_t* dst, if (is_big_endian() == swap_on_big_endian) { const uint32_t* const src_end = src + num_pixels; while (src < src_end) { - uint32_t argb = *src++; + const uint32_t argb = *src++; -#if !defined(__BIG_ENDIAN__) +#if !defined(WORDS_BIGENDIAN) #if !defined(WEBP_REFERENCE_IMPLEMENTATION) -#if defined(__i386__) || defined(__x86_64__) - __asm__ volatile("bswap %0" : "=r"(argb) : "0"(argb)); - *(uint32_t*)dst = argb; -#elif defined(_MSC_VER) - argb = _byteswap_ulong(argb); - *(uint32_t*)dst = argb; -#else - dst[0] = (argb >> 24) & 0xff; - dst[1] = (argb >> 16) & 0xff; - dst[2] = (argb >> 8) & 0xff; - dst[3] = (argb >> 0) & 0xff; -#endif + WebPUint32ToMem(dst, BSwap32(argb)); #else // WEBP_REFERENCE_IMPLEMENTATION dst[0] = (argb >> 24) & 0xff; dst[1] = (argb >> 16) & 0xff; dst[2] = (argb >> 8) & 0xff; dst[3] = (argb >> 0) & 0xff; #endif -#else // __BIG_ENDIAN__ +#else // WORDS_BIGENDIAN dst[0] = (argb >> 0) & 0xff; dst[1] = (argb >> 8) & 0xff; dst[2] = (argb >> 16) & 0xff; @@ -1318,17 +523,17 @@ void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels, WEBP_CSP_MODE out_colorspace, uint8_t* const rgba) { switch (out_colorspace) { case MODE_RGB: - ConvertBGRAToRGB(in_data, num_pixels, rgba); + VP8LConvertBGRAToRGB(in_data, num_pixels, rgba); break; case MODE_RGBA: - ConvertBGRAToRGBA(in_data, num_pixels, rgba); + VP8LConvertBGRAToRGBA(in_data, num_pixels, rgba); break; case MODE_rgbA: - ConvertBGRAToRGBA(in_data, num_pixels, rgba); + VP8LConvertBGRAToRGBA(in_data, num_pixels, rgba); WebPApplyAlphaMultiply(rgba, 0, num_pixels, 1, 0); break; case MODE_BGR: - ConvertBGRAToBGR(in_data, num_pixels, rgba); + VP8LConvertBGRAToBGR(in_data, num_pixels, rgba); break; case MODE_BGRA: CopyOrSwap(in_data, num_pixels, rgba, 1); @@ -1345,43 +550,114 @@ void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels, WebPApplyAlphaMultiply(rgba, 1, num_pixels, 1, 0); break; case MODE_RGBA_4444: - ConvertBGRAToRGBA4444(in_data, num_pixels, rgba); + VP8LConvertBGRAToRGBA4444(in_data, num_pixels, rgba); break; case MODE_rgbA_4444: - ConvertBGRAToRGBA4444(in_data, num_pixels, rgba); + VP8LConvertBGRAToRGBA4444(in_data, num_pixels, rgba); WebPApplyAlphaMultiply4444(rgba, num_pixels, 1, 0); break; case MODE_RGB_565: - ConvertBGRAToRGB565(in_data, num_pixels, rgba); + VP8LConvertBGRAToRGB565(in_data, num_pixels, rgba); break; default: assert(0); // Code flow should not reach here. } } -// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. -void VP8LBundleColorMap(const uint8_t* const row, int width, - int xbits, uint32_t* const dst) { - int x; - if (xbits > 0) { - const int bit_depth = 1 << (3 - xbits); - const int mask = (1 << xbits) - 1; - uint32_t code = 0xff000000; - for (x = 0; x < width; ++x) { - const int xsub = x & mask; - if (xsub == 0) { - code = 0xff000000; - } - code |= row[x] << (8 + bit_depth * xsub); - dst[x >> xbits] = code; - } - } else { - for (x = 0; x < width; ++x) dst[x] = 0xff000000 | (row[x] << 8); - } -} - //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" +VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed; +VP8LPredictorAddSubFunc VP8LPredictorsAdd[16]; +VP8LPredictorFunc VP8LPredictors[16]; + +// exposed plain-C implementations +VP8LPredictorAddSubFunc VP8LPredictorsAdd_C[16]; +VP8LPredictorFunc VP8LPredictors_C[16]; + +VP8LTransformColorInverseFunc VP8LTransformColorInverse; + +VP8LConvertFunc VP8LConvertBGRAToRGB; +VP8LConvertFunc VP8LConvertBGRAToRGBA; +VP8LConvertFunc VP8LConvertBGRAToRGBA4444; +VP8LConvertFunc VP8LConvertBGRAToRGB565; +VP8LConvertFunc VP8LConvertBGRAToBGR; + +VP8LMapARGBFunc VP8LMapColor32b; +VP8LMapAlphaFunc VP8LMapColor8b; + +extern void VP8LDspInitSSE2(void); +extern void VP8LDspInitNEON(void); +extern void VP8LDspInitMIPSdspR2(void); +extern void VP8LDspInitMSA(void); + +static volatile VP8CPUInfo lossless_last_cpuinfo_used = + (VP8CPUInfo)&lossless_last_cpuinfo_used; + +#define COPY_PREDICTOR_ARRAY(IN, OUT) do { \ + (OUT)[0] = IN##0; \ + (OUT)[1] = IN##1; \ + (OUT)[2] = IN##2; \ + (OUT)[3] = IN##3; \ + (OUT)[4] = IN##4; \ + (OUT)[5] = IN##5; \ + (OUT)[6] = IN##6; \ + (OUT)[7] = IN##7; \ + (OUT)[8] = IN##8; \ + (OUT)[9] = IN##9; \ + (OUT)[10] = IN##10; \ + (OUT)[11] = IN##11; \ + (OUT)[12] = IN##12; \ + (OUT)[13] = IN##13; \ + (OUT)[14] = IN##0; /* <- padding security sentinels*/ \ + (OUT)[15] = IN##0; \ +} while (0); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInit(void) { + if (lossless_last_cpuinfo_used == VP8GetCPUInfo) return; + + COPY_PREDICTOR_ARRAY(Predictor, VP8LPredictors) + COPY_PREDICTOR_ARRAY(Predictor, VP8LPredictors_C) + COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd) + COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd_C) + + VP8LAddGreenToBlueAndRed = VP8LAddGreenToBlueAndRed_C; + + VP8LTransformColorInverse = VP8LTransformColorInverse_C; + + VP8LConvertBGRAToRGB = VP8LConvertBGRAToRGB_C; + VP8LConvertBGRAToRGBA = VP8LConvertBGRAToRGBA_C; + VP8LConvertBGRAToRGBA4444 = VP8LConvertBGRAToRGBA4444_C; + VP8LConvertBGRAToRGB565 = VP8LConvertBGRAToRGB565_C; + VP8LConvertBGRAToBGR = VP8LConvertBGRAToBGR_C; + + VP8LMapColor32b = MapARGB; + VP8LMapColor8b = MapAlpha; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8LDspInitSSE2(); + } #endif +#if defined(WEBP_USE_NEON) + if (VP8GetCPUInfo(kNEON)) { + VP8LDspInitNEON(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8LDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8LDspInitMSA(); + } +#endif + } + lossless_last_cpuinfo_used = VP8GetCPUInfo; +} +#undef COPY_PREDICTOR_ARRAY + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/dsp/lossless.h b/3rdparty/libwebp/dsp/lossless.h index 7490ec8efe..352a54e509 100644 --- a/3rdparty/libwebp/dsp/lossless.h +++ b/3rdparty/libwebp/dsp/lossless.h @@ -18,12 +18,45 @@ #include "../webp/types.h" #include "../webp/decode.h" -#if defined(__cplusplus) || defined(c_plusplus) +#include "../enc/histogram_enc.h" +#include "../utils/utils.h" + +#ifdef __cplusplus extern "C" { #endif +#ifdef WEBP_EXPERIMENTAL_FEATURES +#include "../enc/delta_palettization_enc.h" +#endif // WEBP_EXPERIMENTAL_FEATURES + //------------------------------------------------------------------------------ -// Image transforms. +// Decoding + +typedef uint32_t (*VP8LPredictorFunc)(uint32_t left, const uint32_t* const top); +extern VP8LPredictorFunc VP8LPredictors[16]; +extern VP8LPredictorFunc VP8LPredictors_C[16]; +// These Add/Sub function expects upper[-1] and out[-1] to be readable. +typedef void (*VP8LPredictorAddSubFunc)(const uint32_t* in, + const uint32_t* upper, int num_pixels, + uint32_t* out); +extern VP8LPredictorAddSubFunc VP8LPredictorsAdd[16]; +extern VP8LPredictorAddSubFunc VP8LPredictorsAdd_C[16]; + +typedef void (*VP8LProcessDecBlueAndRedFunc)(const uint32_t* src, + int num_pixels, uint32_t* dst); +extern VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed; + +typedef struct { + // Note: the members are uint8_t, so that any negative values are + // automatically converted to "mod 256" values. + uint8_t green_to_red_; + uint8_t green_to_blue_; + uint8_t red_to_blue_; +} VP8LMultipliers; +typedef void (*VP8LTransformColorInverseFunc)(const VP8LMultipliers* const m, + const uint32_t* src, + int num_pixels, uint32_t* dst); +extern VP8LTransformColorInverseFunc VP8LTransformColorInverse; struct VP8LTransform; // Defined in dec/vp8li.h. @@ -35,6 +68,31 @@ void VP8LInverseTransform(const struct VP8LTransform* const transform, int row_start, int row_end, const uint32_t* const in, uint32_t* const out); +// Color space conversion. +typedef void (*VP8LConvertFunc)(const uint32_t* src, int num_pixels, + uint8_t* dst); +extern VP8LConvertFunc VP8LConvertBGRAToRGB; +extern VP8LConvertFunc VP8LConvertBGRAToRGBA; +extern VP8LConvertFunc VP8LConvertBGRAToRGBA4444; +extern VP8LConvertFunc VP8LConvertBGRAToRGB565; +extern VP8LConvertFunc VP8LConvertBGRAToBGR; + +// Converts from BGRA to other color spaces. +void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels, + WEBP_CSP_MODE out_colorspace, uint8_t* const rgba); + +typedef void (*VP8LMapARGBFunc)(const uint32_t* src, + const uint32_t* const color_map, + uint32_t* dst, int y_start, + int y_end, int width); +typedef void (*VP8LMapAlphaFunc)(const uint8_t* src, + const uint32_t* const color_map, + uint8_t* dst, int y_start, + int y_end, int width); + +extern VP8LMapARGBFunc VP8LMapColor32b; +extern VP8LMapAlphaFunc VP8LMapColor8b; + // Similar to the static method ColorIndexInverseTransform() that is part of // lossless.c, but used only for alpha decoding. It takes uint8_t (rather than // uint32_t) arguments for 'src' and 'dst'. @@ -42,62 +100,129 @@ void VP8LColorIndexInverseTransformAlpha( const struct VP8LTransform* const transform, int y_start, int y_end, const uint8_t* src, uint8_t* dst); -// Subtracts green from blue and red channels. -void VP8LSubtractGreenFromBlueAndRed(uint32_t* argb_data, int num_pixs); +// Expose some C-only fallback functions +void VP8LTransformColorInverse_C(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst); -void VP8LResidualImage(int width, int height, int bits, - uint32_t* const argb, uint32_t* const argb_scratch, - uint32_t* const image); +void VP8LConvertBGRAToRGB_C(const uint32_t* src, int num_pixels, uint8_t* dst); +void VP8LConvertBGRAToRGBA_C(const uint32_t* src, int num_pixels, uint8_t* dst); +void VP8LConvertBGRAToRGBA4444_C(const uint32_t* src, + int num_pixels, uint8_t* dst); +void VP8LConvertBGRAToRGB565_C(const uint32_t* src, + int num_pixels, uint8_t* dst); +void VP8LConvertBGRAToBGR_C(const uint32_t* src, int num_pixels, uint8_t* dst); +void VP8LAddGreenToBlueAndRed_C(const uint32_t* src, int num_pixels, + uint32_t* dst); -void VP8LColorSpaceTransform(int width, int height, int bits, int step, - uint32_t* const argb, uint32_t* image); +// Must be called before calling any of the above methods. +void VP8LDspInit(void); //------------------------------------------------------------------------------ -// Color space conversion. +// Encoding -// Converts from BGRA to other color spaces. -void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels, - WEBP_CSP_MODE out_colorspace, uint8_t* const rgba); +typedef void (*VP8LProcessEncBlueAndRedFunc)(uint32_t* dst, int num_pixels); +extern VP8LProcessEncBlueAndRedFunc VP8LSubtractGreenFromBlueAndRed; +typedef void (*VP8LTransformColorFunc)(const VP8LMultipliers* const m, + uint32_t* const dst, int num_pixels); +extern VP8LTransformColorFunc VP8LTransformColor; +typedef void (*VP8LCollectColorBlueTransformsFunc)( + const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, int histo[]); +extern VP8LCollectColorBlueTransformsFunc VP8LCollectColorBlueTransforms; -//------------------------------------------------------------------------------ -// Misc methods. +typedef void (*VP8LCollectColorRedTransformsFunc)( + const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]); +extern VP8LCollectColorRedTransformsFunc VP8LCollectColorRedTransforms; -// Computes sampled size of 'size' when sampling using 'sampling bits'. -static WEBP_INLINE uint32_t VP8LSubSampleSize(uint32_t size, - uint32_t sampling_bits) { - return (size + (1 << sampling_bits) - 1) >> sampling_bits; -} +// Expose some C-only fallback functions +void VP8LTransformColor_C(const VP8LMultipliers* const m, + uint32_t* data, int num_pixels); +void VP8LSubtractGreenFromBlueAndRed_C(uint32_t* argb_data, int num_pixels); +void VP8LCollectColorRedTransforms_C(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]); +void VP8LCollectColorBlueTransforms_C(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]); -// Faster logarithm for integers. Small values use a look-up table. -#define LOG_LOOKUP_IDX_MAX 256 -extern const float kLog2Table[LOG_LOOKUP_IDX_MAX]; -extern const float kSLog2Table[LOG_LOOKUP_IDX_MAX]; -extern float VP8LFastLog2Slow(int v); -extern float VP8LFastSLog2Slow(int v); -static WEBP_INLINE float VP8LFastLog2(int v) { - return (v < LOG_LOOKUP_IDX_MAX) ? kLog2Table[v] : VP8LFastLog2Slow(v); -} -// Fast calculation of v * log2(v) for integer input. -static WEBP_INLINE float VP8LFastSLog2(int v) { - return (v < LOG_LOOKUP_IDX_MAX) ? kSLog2Table[v] : VP8LFastSLog2Slow(v); -} +extern VP8LPredictorAddSubFunc VP8LPredictorsSub[16]; +extern VP8LPredictorAddSubFunc VP8LPredictorsSub_C[16]; +// ----------------------------------------------------------------------------- +// Huffman-cost related functions. -// In-place difference of each component with mod 256. -static WEBP_INLINE uint32_t VP8LSubPixels(uint32_t a, uint32_t b) { - const uint32_t alpha_and_green = - 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); - const uint32_t red_and_blue = - 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); - return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu); -} +typedef double (*VP8LCostFunc)(const uint32_t* population, int length); +typedef double (*VP8LCostCombinedFunc)(const uint32_t* X, const uint32_t* Y, + int length); +typedef float (*VP8LCombinedShannonEntropyFunc)(const int X[256], + const int Y[256]); -void VP8LBundleColorMap(const uint8_t* const row, int width, - int xbits, uint32_t* const dst); +extern VP8LCostFunc VP8LExtraCost; +extern VP8LCostCombinedFunc VP8LExtraCostCombined; +extern VP8LCombinedShannonEntropyFunc VP8LCombinedShannonEntropy; + +typedef struct { // small struct to hold counters + int counts[2]; // index: 0=zero steak, 1=non-zero streak + int streaks[2][2]; // [zero/non-zero][streak<3 / streak>=3] +} VP8LStreaks; + +typedef struct { // small struct to hold bit entropy results + double entropy; // entropy + uint32_t sum; // sum of the population + int nonzeros; // number of non-zero elements in the population + uint32_t max_val; // maximum value in the population + uint32_t nonzero_code; // index of the last non-zero in the population +} VP8LBitEntropy; + +void VP8LBitEntropyInit(VP8LBitEntropy* const entropy); + +// Get the combined symbol bit entropy and Huffman cost stats for the +// distributions 'X' and 'Y'. Those results can then be refined according to +// codec specific heuristics. +typedef void (*VP8LGetCombinedEntropyUnrefinedFunc)( + const uint32_t X[], const uint32_t Y[], int length, + VP8LBitEntropy* const bit_entropy, VP8LStreaks* const stats); +extern VP8LGetCombinedEntropyUnrefinedFunc VP8LGetCombinedEntropyUnrefined; + +// Get the entropy for the distribution 'X'. +typedef void (*VP8LGetEntropyUnrefinedFunc)(const uint32_t X[], int length, + VP8LBitEntropy* const bit_entropy, + VP8LStreaks* const stats); +extern VP8LGetEntropyUnrefinedFunc VP8LGetEntropyUnrefined; + +void VP8LBitsEntropyUnrefined(const uint32_t* const array, int n, + VP8LBitEntropy* const entropy); + +typedef void (*VP8LHistogramAddFunc)(const VP8LHistogram* const a, + const VP8LHistogram* const b, + VP8LHistogram* const out); +extern VP8LHistogramAddFunc VP8LHistogramAdd; + +// ----------------------------------------------------------------------------- +// PrefixEncode() + +typedef int (*VP8LVectorMismatchFunc)(const uint32_t* const array1, + const uint32_t* const array2, int length); +// Returns the first index where array1 and array2 are different. +extern VP8LVectorMismatchFunc VP8LVectorMismatch; + +typedef void (*VP8LBundleColorMapFunc)(const uint8_t* const row, int width, + int xbits, uint32_t* dst); +extern VP8LBundleColorMapFunc VP8LBundleColorMap; +void VP8LBundleColorMap_C(const uint8_t* const row, int width, int xbits, + uint32_t* dst); + +// Must be called before calling any of the above methods. +void VP8LEncDspInit(void); //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/dsp/lossless_common.h b/3rdparty/libwebp/dsp/lossless_common.h new file mode 100644 index 0000000000..c40f711208 --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_common.h @@ -0,0 +1,210 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transforms and color space conversion methods for lossless decoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) +// Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_LOSSLESS_COMMON_H_ +#define WEBP_DSP_LOSSLESS_COMMON_H_ + +#include "../webp/types.h" + +#include "../utils/utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Decoding + +// color mapping related functions. +static WEBP_INLINE uint32_t VP8GetARGBIndex(uint32_t idx) { + return (idx >> 8) & 0xff; +} + +static WEBP_INLINE uint8_t VP8GetAlphaIndex(uint8_t idx) { + return idx; +} + +static WEBP_INLINE uint32_t VP8GetARGBValue(uint32_t val) { + return val; +} + +static WEBP_INLINE uint8_t VP8GetAlphaValue(uint32_t val) { + return (val >> 8) & 0xff; +} + +//------------------------------------------------------------------------------ +// Misc methods. + +// Computes sampled size of 'size' when sampling using 'sampling bits'. +static WEBP_INLINE uint32_t VP8LSubSampleSize(uint32_t size, + uint32_t sampling_bits) { + return (size + (1 << sampling_bits) - 1) >> sampling_bits; +} + +// Converts near lossless quality into max number of bits shaved off. +static WEBP_INLINE int VP8LNearLosslessBits(int near_lossless_quality) { + // 100 -> 0 + // 80..99 -> 1 + // 60..79 -> 2 + // 40..59 -> 3 + // 20..39 -> 4 + // 0..19 -> 5 + return 5 - near_lossless_quality / 20; +} + +// ----------------------------------------------------------------------------- +// Faster logarithm for integers. Small values use a look-up table. + +// The threshold till approximate version of log_2 can be used. +// Practically, we can get rid of the call to log() as the two values match to +// very high degree (the ratio of these two is 0.99999x). +// Keeping a high threshold for now. +#define APPROX_LOG_WITH_CORRECTION_MAX 65536 +#define APPROX_LOG_MAX 4096 +#define LOG_2_RECIPROCAL 1.44269504088896338700465094007086 +#define LOG_LOOKUP_IDX_MAX 256 +extern const float kLog2Table[LOG_LOOKUP_IDX_MAX]; +extern const float kSLog2Table[LOG_LOOKUP_IDX_MAX]; +typedef float (*VP8LFastLog2SlowFunc)(uint32_t v); + +extern VP8LFastLog2SlowFunc VP8LFastLog2Slow; +extern VP8LFastLog2SlowFunc VP8LFastSLog2Slow; + +static WEBP_INLINE float VP8LFastLog2(uint32_t v) { + return (v < LOG_LOOKUP_IDX_MAX) ? kLog2Table[v] : VP8LFastLog2Slow(v); +} +// Fast calculation of v * log2(v) for integer input. +static WEBP_INLINE float VP8LFastSLog2(uint32_t v) { + return (v < LOG_LOOKUP_IDX_MAX) ? kSLog2Table[v] : VP8LFastSLog2Slow(v); +} + +// ----------------------------------------------------------------------------- +// PrefixEncode() + +static WEBP_INLINE int VP8LBitsLog2Ceiling(uint32_t n) { + const int log_floor = BitsLog2Floor(n); + if (n == (n & ~(n - 1))) { // zero or a power of two. + return log_floor; + } + return log_floor + 1; +} + +// Splitting of distance and length codes into prefixes and +// extra bits. The prefixes are encoded with an entropy code +// while the extra bits are stored just as normal bits. +static WEBP_INLINE void VP8LPrefixEncodeBitsNoLUT(int distance, int* const code, + int* const extra_bits) { + const int highest_bit = BitsLog2Floor(--distance); + const int second_highest_bit = (distance >> (highest_bit - 1)) & 1; + *extra_bits = highest_bit - 1; + *code = 2 * highest_bit + second_highest_bit; +} + +static WEBP_INLINE void VP8LPrefixEncodeNoLUT(int distance, int* const code, + int* const extra_bits, + int* const extra_bits_value) { + const int highest_bit = BitsLog2Floor(--distance); + const int second_highest_bit = (distance >> (highest_bit - 1)) & 1; + *extra_bits = highest_bit - 1; + *extra_bits_value = distance & ((1 << *extra_bits) - 1); + *code = 2 * highest_bit + second_highest_bit; +} + +#define PREFIX_LOOKUP_IDX_MAX 512 +typedef struct { + int8_t code_; + int8_t extra_bits_; +} VP8LPrefixCode; + +// These tables are derived using VP8LPrefixEncodeNoLUT. +extern const VP8LPrefixCode kPrefixEncodeCode[PREFIX_LOOKUP_IDX_MAX]; +extern const uint8_t kPrefixEncodeExtraBitsValue[PREFIX_LOOKUP_IDX_MAX]; +static WEBP_INLINE void VP8LPrefixEncodeBits(int distance, int* const code, + int* const extra_bits) { + if (distance < PREFIX_LOOKUP_IDX_MAX) { + const VP8LPrefixCode prefix_code = kPrefixEncodeCode[distance]; + *code = prefix_code.code_; + *extra_bits = prefix_code.extra_bits_; + } else { + VP8LPrefixEncodeBitsNoLUT(distance, code, extra_bits); + } +} + +static WEBP_INLINE void VP8LPrefixEncode(int distance, int* const code, + int* const extra_bits, + int* const extra_bits_value) { + if (distance < PREFIX_LOOKUP_IDX_MAX) { + const VP8LPrefixCode prefix_code = kPrefixEncodeCode[distance]; + *code = prefix_code.code_; + *extra_bits = prefix_code.extra_bits_; + *extra_bits_value = kPrefixEncodeExtraBitsValue[distance]; + } else { + VP8LPrefixEncodeNoLUT(distance, code, extra_bits, extra_bits_value); + } +} + +// Sum of each component, mod 256. +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +uint32_t VP8LAddPixels(uint32_t a, uint32_t b) { + const uint32_t alpha_and_green = (a & 0xff00ff00u) + (b & 0xff00ff00u); + const uint32_t red_and_blue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); + return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu); +} + +// Difference of each component, mod 256. +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +uint32_t VP8LSubPixels(uint32_t a, uint32_t b) { + const uint32_t alpha_and_green = + 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); + const uint32_t red_and_blue = + 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); + return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu); +} + +//------------------------------------------------------------------------------ +// Transform-related functions use din both encoding and decoding. + +// Macros used to create a batch predictor that iteratively uses a +// one-pixel predictor. + +// The predictor is added to the output pixel (which +// is therefore considered as a residual) to get the final prediction. +#define GENERATE_PREDICTOR_ADD(PREDICTOR, PREDICTOR_ADD) \ +static void PREDICTOR_ADD(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int x; \ + for (x = 0; x < num_pixels; ++x) { \ + const uint32_t pred = (PREDICTOR)(out[x - 1], upper + x); \ + out[x] = VP8LAddPixels(in[x], pred); \ + } \ +} + +// It subtracts the prediction from the input pixel and stores the residual +// in the output pixel. +#define GENERATE_PREDICTOR_SUB(PREDICTOR, PREDICTOR_SUB) \ +static void PREDICTOR_SUB(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int x; \ + for (x = 0; x < num_pixels; ++x) { \ + const uint32_t pred = (PREDICTOR)(in[x - 1], upper + x); \ + out[x] = VP8LSubPixels(in[x], pred); \ + } \ +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_LOSSLESS_COMMON_H_ diff --git a/3rdparty/libwebp/dsp/lossless_enc.c b/3rdparty/libwebp/dsp/lossless_enc.c new file mode 100644 index 0000000000..4e46fbab8b --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_enc.c @@ -0,0 +1,964 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transform methods for lossless encoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) +// Urvang Joshi (urvang@google.com) + +#include "./dsp.h" + +#include +#include +#include "../dec/vp8li_dec.h" +#include "../utils/endian_inl_utils.h" +#include "./lossless.h" +#include "./lossless_common.h" +#include "./yuv.h" + +// lookup table for small values of log2(int) +const float kLog2Table[LOG_LOOKUP_IDX_MAX] = { + 0.0000000000000000f, 0.0000000000000000f, + 1.0000000000000000f, 1.5849625007211560f, + 2.0000000000000000f, 2.3219280948873621f, + 2.5849625007211560f, 2.8073549220576041f, + 3.0000000000000000f, 3.1699250014423121f, + 3.3219280948873621f, 3.4594316186372973f, + 3.5849625007211560f, 3.7004397181410921f, + 3.8073549220576041f, 3.9068905956085187f, + 4.0000000000000000f, 4.0874628412503390f, + 4.1699250014423121f, 4.2479275134435852f, + 4.3219280948873626f, 4.3923174227787606f, + 4.4594316186372973f, 4.5235619560570130f, + 4.5849625007211560f, 4.6438561897747243f, + 4.7004397181410917f, 4.7548875021634682f, + 4.8073549220576037f, 4.8579809951275718f, + 4.9068905956085187f, 4.9541963103868749f, + 5.0000000000000000f, 5.0443941193584533f, + 5.0874628412503390f, 5.1292830169449663f, + 5.1699250014423121f, 5.2094533656289501f, + 5.2479275134435852f, 5.2854022188622487f, + 5.3219280948873626f, 5.3575520046180837f, + 5.3923174227787606f, 5.4262647547020979f, + 5.4594316186372973f, 5.4918530963296747f, + 5.5235619560570130f, 5.5545888516776376f, + 5.5849625007211560f, 5.6147098441152083f, + 5.6438561897747243f, 5.6724253419714951f, + 5.7004397181410917f, 5.7279204545631987f, + 5.7548875021634682f, 5.7813597135246599f, + 5.8073549220576037f, 5.8328900141647412f, + 5.8579809951275718f, 5.8826430493618415f, + 5.9068905956085187f, 5.9307373375628866f, + 5.9541963103868749f, 5.9772799234999167f, + 6.0000000000000000f, 6.0223678130284543f, + 6.0443941193584533f, 6.0660891904577720f, + 6.0874628412503390f, 6.1085244567781691f, + 6.1292830169449663f, 6.1497471195046822f, + 6.1699250014423121f, 6.1898245588800175f, + 6.2094533656289501f, 6.2288186904958804f, + 6.2479275134435852f, 6.2667865406949010f, + 6.2854022188622487f, 6.3037807481771030f, + 6.3219280948873626f, 6.3398500028846243f, + 6.3575520046180837f, 6.3750394313469245f, + 6.3923174227787606f, 6.4093909361377017f, + 6.4262647547020979f, 6.4429434958487279f, + 6.4594316186372973f, 6.4757334309663976f, + 6.4918530963296747f, 6.5077946401986963f, + 6.5235619560570130f, 6.5391588111080309f, + 6.5545888516776376f, 6.5698556083309478f, + 6.5849625007211560f, 6.5999128421871278f, + 6.6147098441152083f, 6.6293566200796094f, + 6.6438561897747243f, 6.6582114827517946f, + 6.6724253419714951f, 6.6865005271832185f, + 6.7004397181410917f, 6.7142455176661224f, + 6.7279204545631987f, 6.7414669864011464f, + 6.7548875021634682f, 6.7681843247769259f, + 6.7813597135246599f, 6.7944158663501061f, + 6.8073549220576037f, 6.8201789624151878f, + 6.8328900141647412f, 6.8454900509443747f, + 6.8579809951275718f, 6.8703647195834047f, + 6.8826430493618415f, 6.8948177633079437f, + 6.9068905956085187f, 6.9188632372745946f, + 6.9307373375628866f, 6.9425145053392398f, + 6.9541963103868749f, 6.9657842846620869f, + 6.9772799234999167f, 6.9886846867721654f, + 7.0000000000000000f, 7.0112272554232539f, + 7.0223678130284543f, 7.0334230015374501f, + 7.0443941193584533f, 7.0552824355011898f, + 7.0660891904577720f, 7.0768155970508308f, + 7.0874628412503390f, 7.0980320829605263f, + 7.1085244567781691f, 7.1189410727235076f, + 7.1292830169449663f, 7.1395513523987936f, + 7.1497471195046822f, 7.1598713367783890f, + 7.1699250014423121f, 7.1799090900149344f, + 7.1898245588800175f, 7.1996723448363644f, + 7.2094533656289501f, 7.2191685204621611f, + 7.2288186904958804f, 7.2384047393250785f, + 7.2479275134435852f, 7.2573878426926521f, + 7.2667865406949010f, 7.2761244052742375f, + 7.2854022188622487f, 7.2946207488916270f, + 7.3037807481771030f, 7.3128829552843557f, + 7.3219280948873626f, 7.3309168781146167f, + 7.3398500028846243f, 7.3487281542310771f, + 7.3575520046180837f, 7.3663222142458160f, + 7.3750394313469245f, 7.3837042924740519f, + 7.3923174227787606f, 7.4008794362821843f, + 7.4093909361377017f, 7.4178525148858982f, + 7.4262647547020979f, 7.4346282276367245f, + 7.4429434958487279f, 7.4512111118323289f, + 7.4594316186372973f, 7.4676055500829976f, + 7.4757334309663976f, 7.4838157772642563f, + 7.4918530963296747f, 7.4998458870832056f, + 7.5077946401986963f, 7.5156998382840427f, + 7.5235619560570130f, 7.5313814605163118f, + 7.5391588111080309f, 7.5468944598876364f, + 7.5545888516776376f, 7.5622424242210728f, + 7.5698556083309478f, 7.5774288280357486f, + 7.5849625007211560f, 7.5924570372680806f, + 7.5999128421871278f, 7.6073303137496104f, + 7.6147098441152083f, 7.6220518194563764f, + 7.6293566200796094f, 7.6366246205436487f, + 7.6438561897747243f, 7.6510516911789281f, + 7.6582114827517946f, 7.6653359171851764f, + 7.6724253419714951f, 7.6794800995054464f, + 7.6865005271832185f, 7.6934869574993252f, + 7.7004397181410917f, 7.7073591320808825f, + 7.7142455176661224f, 7.7210991887071855f, + 7.7279204545631987f, 7.7347096202258383f, + 7.7414669864011464f, 7.7481928495894605f, + 7.7548875021634682f, 7.7615512324444795f, + 7.7681843247769259f, 7.7747870596011736f, + 7.7813597135246599f, 7.7879025593914317f, + 7.7944158663501061f, 7.8008998999203047f, + 7.8073549220576037f, 7.8137811912170374f, + 7.8201789624151878f, 7.8265484872909150f, + 7.8328900141647412f, 7.8392037880969436f, + 7.8454900509443747f, 7.8517490414160571f, + 7.8579809951275718f, 7.8641861446542797f, + 7.8703647195834047f, 7.8765169465649993f, + 7.8826430493618415f, 7.8887432488982591f, + 7.8948177633079437f, 7.9008668079807486f, + 7.9068905956085187f, 7.9128893362299619f, + 7.9188632372745946f, 7.9248125036057812f, + 7.9307373375628866f, 7.9366379390025709f, + 7.9425145053392398f, 7.9483672315846778f, + 7.9541963103868749f, 7.9600019320680805f, + 7.9657842846620869f, 7.9715435539507719f, + 7.9772799234999167f, 7.9829935746943103f, + 7.9886846867721654f, 7.9943534368588577f +}; + +const float kSLog2Table[LOG_LOOKUP_IDX_MAX] = { + 0.00000000f, 0.00000000f, 2.00000000f, 4.75488750f, + 8.00000000f, 11.60964047f, 15.50977500f, 19.65148445f, + 24.00000000f, 28.52932501f, 33.21928095f, 38.05374781f, + 43.01955001f, 48.10571634f, 53.30296891f, 58.60335893f, + 64.00000000f, 69.48686830f, 75.05865003f, 80.71062276f, + 86.43856190f, 92.23866588f, 98.10749561f, 104.04192499f, + 110.03910002f, 116.09640474f, 122.21143267f, 128.38196256f, + 134.60593782f, 140.88144886f, 147.20671787f, 153.58008562f, + 160.00000000f, 166.46500594f, 172.97373660f, 179.52490559f, + 186.11730005f, 192.74977453f, 199.42124551f, 206.13068654f, + 212.87712380f, 219.65963219f, 226.47733176f, 233.32938445f, + 240.21499122f, 247.13338933f, 254.08384998f, 261.06567603f, + 268.07820003f, 275.12078236f, 282.19280949f, 289.29369244f, + 296.42286534f, 303.57978409f, 310.76392512f, 317.97478424f, + 325.21187564f, 332.47473081f, 339.76289772f, 347.07593991f, + 354.41343574f, 361.77497759f, 369.16017124f, 376.56863518f, + 384.00000000f, 391.45390785f, 398.93001188f, 406.42797576f, + 413.94747321f, 421.48818752f, 429.04981119f, 436.63204548f, + 444.23460010f, 451.85719280f, 459.49954906f, 467.16140179f, + 474.84249102f, 482.54256363f, 490.26137307f, 497.99867911f, + 505.75424759f, 513.52785023f, 521.31926438f, 529.12827280f, + 536.95466351f, 544.79822957f, 552.65876890f, 560.53608414f, + 568.42998244f, 576.34027536f, 584.26677867f, 592.20931226f, + 600.16769996f, 608.14176943f, 616.13135206f, 624.13628279f, + 632.15640007f, 640.19154569f, 648.24156472f, 656.30630539f, + 664.38561898f, 672.47935976f, 680.58738488f, 688.70955430f, + 696.84573069f, 704.99577935f, 713.15956818f, 721.33696754f, + 729.52785023f, 737.73209140f, 745.94956849f, 754.18016116f, + 762.42375127f, 770.68022275f, 778.94946161f, 787.23135586f, + 795.52579543f, 803.83267219f, 812.15187982f, 820.48331383f, + 828.82687147f, 837.18245171f, 845.54995518f, 853.92928416f, + 862.32034249f, 870.72303558f, 879.13727036f, 887.56295522f, + 896.00000000f, 904.44831595f, 912.90781569f, 921.37841320f, + 929.86002376f, 938.35256392f, 946.85595152f, 955.37010560f, + 963.89494641f, 972.43039537f, 980.97637504f, 989.53280911f, + 998.09962237f, 1006.67674069f, 1015.26409097f, 1023.86160116f, + 1032.46920021f, 1041.08681805f, 1049.71438560f, 1058.35183469f, + 1066.99909811f, 1075.65610955f, 1084.32280357f, 1092.99911564f, + 1101.68498204f, 1110.38033993f, 1119.08512727f, 1127.79928282f, + 1136.52274614f, 1145.25545758f, 1153.99735821f, 1162.74838989f, + 1171.50849518f, 1180.27761738f, 1189.05570047f, 1197.84268914f, + 1206.63852876f, 1215.44316535f, 1224.25654560f, 1233.07861684f, + 1241.90932703f, 1250.74862473f, 1259.59645914f, 1268.45278005f, + 1277.31753781f, 1286.19068338f, 1295.07216828f, 1303.96194457f, + 1312.85996488f, 1321.76618236f, 1330.68055071f, 1339.60302413f, + 1348.53355734f, 1357.47210556f, 1366.41862452f, 1375.37307041f, + 1384.33539991f, 1393.30557020f, 1402.28353887f, 1411.26926400f, + 1420.26270412f, 1429.26381818f, 1438.27256558f, 1447.28890615f, + 1456.31280014f, 1465.34420819f, 1474.38309138f, 1483.42941118f, + 1492.48312945f, 1501.54420843f, 1510.61261078f, 1519.68829949f, + 1528.77123795f, 1537.86138993f, 1546.95871952f, 1556.06319119f, + 1565.17476976f, 1574.29342040f, 1583.41910860f, 1592.55180020f, + 1601.69146137f, 1610.83805860f, 1619.99155871f, 1629.15192882f, + 1638.31913637f, 1647.49314911f, 1656.67393509f, 1665.86146266f, + 1675.05570047f, 1684.25661744f, 1693.46418280f, 1702.67836605f, + 1711.89913698f, 1721.12646563f, 1730.36032233f, 1739.60067768f, + 1748.84750254f, 1758.10076802f, 1767.36044551f, 1776.62650662f, + 1785.89892323f, 1795.17766747f, 1804.46271172f, 1813.75402857f, + 1823.05159087f, 1832.35537170f, 1841.66534438f, 1850.98148244f, + 1860.30375965f, 1869.63214999f, 1878.96662767f, 1888.30716711f, + 1897.65374295f, 1907.00633003f, 1916.36490342f, 1925.72943838f, + 1935.09991037f, 1944.47629506f, 1953.85856831f, 1963.24670620f, + 1972.64068498f, 1982.04048108f, 1991.44607117f, 2000.85743204f, + 2010.27454072f, 2019.69737440f, 2029.12591044f, 2038.56012640f +}; + +const VP8LPrefixCode kPrefixEncodeCode[PREFIX_LOOKUP_IDX_MAX] = { + { 0, 0}, { 0, 0}, { 1, 0}, { 2, 0}, { 3, 0}, { 4, 1}, { 4, 1}, { 5, 1}, + { 5, 1}, { 6, 2}, { 6, 2}, { 6, 2}, { 6, 2}, { 7, 2}, { 7, 2}, { 7, 2}, + { 7, 2}, { 8, 3}, { 8, 3}, { 8, 3}, { 8, 3}, { 8, 3}, { 8, 3}, { 8, 3}, + { 8, 3}, { 9, 3}, { 9, 3}, { 9, 3}, { 9, 3}, { 9, 3}, { 9, 3}, { 9, 3}, + { 9, 3}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, + {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, + {10, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, + {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, + {11, 4}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, + {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, + {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, + {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, + {12, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, + {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, + {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, + {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, + {13, 5}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, +}; + +const uint8_t kPrefixEncodeExtraBitsValue[PREFIX_LOOKUP_IDX_MAX] = { + 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, + 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, + 127, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126 +}; + +static float FastSLog2Slow(uint32_t v) { + assert(v >= LOG_LOOKUP_IDX_MAX); + if (v < APPROX_LOG_WITH_CORRECTION_MAX) { + int log_cnt = 0; + uint32_t y = 1; + int correction = 0; + const float v_f = (float)v; + const uint32_t orig_v = v; + do { + ++log_cnt; + v = v >> 1; + y = y << 1; + } while (v >= LOG_LOOKUP_IDX_MAX); + // vf = (2^log_cnt) * Xf; where y = 2^log_cnt and Xf < 256 + // Xf = floor(Xf) * (1 + (v % y) / v) + // log2(Xf) = log2(floor(Xf)) + log2(1 + (v % y) / v) + // The correction factor: log(1 + d) ~ d; for very small d values, so + // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v + // LOG_2_RECIPROCAL ~ 23/16 + correction = (23 * (orig_v & (y - 1))) >> 4; + return v_f * (kLog2Table[v] + log_cnt) + correction; + } else { + return (float)(LOG_2_RECIPROCAL * v * log((double)v)); + } +} + +static float FastLog2Slow(uint32_t v) { + assert(v >= LOG_LOOKUP_IDX_MAX); + if (v < APPROX_LOG_WITH_CORRECTION_MAX) { + int log_cnt = 0; + uint32_t y = 1; + const uint32_t orig_v = v; + double log_2; + do { + ++log_cnt; + v = v >> 1; + y = y << 1; + } while (v >= LOG_LOOKUP_IDX_MAX); + log_2 = kLog2Table[v] + log_cnt; + if (orig_v >= APPROX_LOG_MAX) { + // Since the division is still expensive, add this correction factor only + // for large values of 'v'. + const int correction = (23 * (orig_v & (y - 1))) >> 4; + log_2 += (double)correction / orig_v; + } + return (float)log_2; + } else { + return (float)(LOG_2_RECIPROCAL * log((double)v)); + } +} + +//------------------------------------------------------------------------------ +// Methods to calculate Entropy (Shannon). + +// Compute the combined Shanon's entropy for distribution {X} and {X+Y} +static float CombinedShannonEntropy(const int X[256], const int Y[256]) { + int i; + double retval = 0.; + int sumX = 0, sumXY = 0; + for (i = 0; i < 256; ++i) { + const int x = X[i]; + if (x != 0) { + const int xy = x + Y[i]; + sumX += x; + retval -= VP8LFastSLog2(x); + sumXY += xy; + retval -= VP8LFastSLog2(xy); + } else if (Y[i] != 0) { + sumXY += Y[i]; + retval -= VP8LFastSLog2(Y[i]); + } + } + retval += VP8LFastSLog2(sumX) + VP8LFastSLog2(sumXY); + return (float)retval; +} + +void VP8LBitEntropyInit(VP8LBitEntropy* const entropy) { + entropy->entropy = 0.; + entropy->sum = 0; + entropy->nonzeros = 0; + entropy->max_val = 0; + entropy->nonzero_code = VP8L_NON_TRIVIAL_SYM; +} + +void VP8LBitsEntropyUnrefined(const uint32_t* const array, int n, + VP8LBitEntropy* const entropy) { + int i; + + VP8LBitEntropyInit(entropy); + + for (i = 0; i < n; ++i) { + if (array[i] != 0) { + entropy->sum += array[i]; + entropy->nonzero_code = i; + ++entropy->nonzeros; + entropy->entropy -= VP8LFastSLog2(array[i]); + if (entropy->max_val < array[i]) { + entropy->max_val = array[i]; + } + } + } + entropy->entropy += VP8LFastSLog2(entropy->sum); +} + +static WEBP_INLINE void GetEntropyUnrefinedHelper( + uint32_t val, int i, uint32_t* const val_prev, int* const i_prev, + VP8LBitEntropy* const bit_entropy, VP8LStreaks* const stats) { + const int streak = i - *i_prev; + + // Gather info for the bit entropy. + if (*val_prev != 0) { + bit_entropy->sum += (*val_prev) * streak; + bit_entropy->nonzeros += streak; + bit_entropy->nonzero_code = *i_prev; + bit_entropy->entropy -= VP8LFastSLog2(*val_prev) * streak; + if (bit_entropy->max_val < *val_prev) { + bit_entropy->max_val = *val_prev; + } + } + + // Gather info for the Huffman cost. + stats->counts[*val_prev != 0] += (streak > 3); + stats->streaks[*val_prev != 0][(streak > 3)] += streak; + + *val_prev = val; + *i_prev = i; +} + +static void GetEntropyUnrefined(const uint32_t X[], int length, + VP8LBitEntropy* const bit_entropy, + VP8LStreaks* const stats) { + int i; + int i_prev = 0; + uint32_t x_prev = X[0]; + + memset(stats, 0, sizeof(*stats)); + VP8LBitEntropyInit(bit_entropy); + + for (i = 1; i < length; ++i) { + const uint32_t x = X[i]; + if (x != x_prev) { + GetEntropyUnrefinedHelper(x, i, &x_prev, &i_prev, bit_entropy, stats); + } + } + GetEntropyUnrefinedHelper(0, i, &x_prev, &i_prev, bit_entropy, stats); + + bit_entropy->entropy += VP8LFastSLog2(bit_entropy->sum); +} + +static void GetCombinedEntropyUnrefined(const uint32_t X[], const uint32_t Y[], + int length, + VP8LBitEntropy* const bit_entropy, + VP8LStreaks* const stats) { + int i = 1; + int i_prev = 0; + uint32_t xy_prev = X[0] + Y[0]; + + memset(stats, 0, sizeof(*stats)); + VP8LBitEntropyInit(bit_entropy); + + for (i = 1; i < length; ++i) { + const uint32_t xy = X[i] + Y[i]; + if (xy != xy_prev) { + GetEntropyUnrefinedHelper(xy, i, &xy_prev, &i_prev, bit_entropy, stats); + } + } + GetEntropyUnrefinedHelper(0, i, &xy_prev, &i_prev, bit_entropy, stats); + + bit_entropy->entropy += VP8LFastSLog2(bit_entropy->sum); +} + +//------------------------------------------------------------------------------ + +void VP8LSubtractGreenFromBlueAndRed_C(uint32_t* argb_data, int num_pixels) { + int i; + for (i = 0; i < num_pixels; ++i) { + const int argb = argb_data[i]; + const int green = (argb >> 8) & 0xff; + const uint32_t new_r = (((argb >> 16) & 0xff) - green) & 0xff; + const uint32_t new_b = (((argb >> 0) & 0xff) - green) & 0xff; + argb_data[i] = (argb & 0xff00ff00u) | (new_r << 16) | new_b; + } +} + +static WEBP_INLINE int ColorTransformDelta(int8_t color_pred, int8_t color) { + return ((int)color_pred * color) >> 5; +} + +void VP8LTransformColor_C(const VP8LMultipliers* const m, uint32_t* data, + int num_pixels) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint32_t argb = data[i]; + const uint32_t green = argb >> 8; + const uint32_t red = argb >> 16; + int new_red = red; + int new_blue = argb; + new_red -= ColorTransformDelta(m->green_to_red_, green); + new_red &= 0xff; + new_blue -= ColorTransformDelta(m->green_to_blue_, green); + new_blue -= ColorTransformDelta(m->red_to_blue_, red); + new_blue &= 0xff; + data[i] = (argb & 0xff00ff00u) | (new_red << 16) | (new_blue); + } +} + +static WEBP_INLINE uint8_t TransformColorRed(uint8_t green_to_red, + uint32_t argb) { + const uint32_t green = argb >> 8; + int new_red = argb >> 16; + new_red -= ColorTransformDelta(green_to_red, green); + return (new_red & 0xff); +} + +static WEBP_INLINE uint8_t TransformColorBlue(uint8_t green_to_blue, + uint8_t red_to_blue, + uint32_t argb) { + const uint32_t green = argb >> 8; + const uint32_t red = argb >> 16; + uint8_t new_blue = argb; + new_blue -= ColorTransformDelta(green_to_blue, green); + new_blue -= ColorTransformDelta(red_to_blue, red); + return (new_blue & 0xff); +} + +void VP8LCollectColorRedTransforms_C(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]) { + while (tile_height-- > 0) { + int x; + for (x = 0; x < tile_width; ++x) { + ++histo[TransformColorRed(green_to_red, argb[x])]; + } + argb += stride; + } +} + +void VP8LCollectColorBlueTransforms_C(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]) { + while (tile_height-- > 0) { + int x; + for (x = 0; x < tile_width; ++x) { + ++histo[TransformColorBlue(green_to_blue, red_to_blue, argb[x])]; + } + argb += stride; + } +} + +//------------------------------------------------------------------------------ + +static int VectorMismatch(const uint32_t* const array1, + const uint32_t* const array2, int length) { + int match_len = 0; + + while (match_len < length && array1[match_len] == array2[match_len]) { + ++match_len; + } + return match_len; +} + +// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. +void VP8LBundleColorMap_C(const uint8_t* const row, int width, int xbits, + uint32_t* dst) { + int x; + if (xbits > 0) { + const int bit_depth = 1 << (3 - xbits); + const int mask = (1 << xbits) - 1; + uint32_t code = 0xff000000; + for (x = 0; x < width; ++x) { + const int xsub = x & mask; + if (xsub == 0) { + code = 0xff000000; + } + code |= row[x] << (8 + bit_depth * xsub); + dst[x >> xbits] = code; + } + } else { + for (x = 0; x < width; ++x) dst[x] = 0xff000000 | (row[x] << 8); + } +} + +//------------------------------------------------------------------------------ + +static double ExtraCost(const uint32_t* population, int length) { + int i; + double cost = 0.; + for (i = 2; i < length - 2; ++i) cost += (i >> 1) * population[i + 2]; + return cost; +} + +static double ExtraCostCombined(const uint32_t* X, const uint32_t* Y, + int length) { + int i; + double cost = 0.; + for (i = 2; i < length - 2; ++i) { + const int xy = X[i + 2] + Y[i + 2]; + cost += (i >> 1) * xy; + } + return cost; +} + +//------------------------------------------------------------------------------ + +static void HistogramAdd(const VP8LHistogram* const a, + const VP8LHistogram* const b, + VP8LHistogram* const out) { + int i; + const int literal_size = VP8LHistogramNumCodes(a->palette_code_bits_); + assert(a->palette_code_bits_ == b->palette_code_bits_); + if (b != out) { + for (i = 0; i < literal_size; ++i) { + out->literal_[i] = a->literal_[i] + b->literal_[i]; + } + for (i = 0; i < NUM_DISTANCE_CODES; ++i) { + out->distance_[i] = a->distance_[i] + b->distance_[i]; + } + for (i = 0; i < NUM_LITERAL_CODES; ++i) { + out->red_[i] = a->red_[i] + b->red_[i]; + out->blue_[i] = a->blue_[i] + b->blue_[i]; + out->alpha_[i] = a->alpha_[i] + b->alpha_[i]; + } + } else { + for (i = 0; i < literal_size; ++i) { + out->literal_[i] += a->literal_[i]; + } + for (i = 0; i < NUM_DISTANCE_CODES; ++i) { + out->distance_[i] += a->distance_[i]; + } + for (i = 0; i < NUM_LITERAL_CODES; ++i) { + out->red_[i] += a->red_[i]; + out->blue_[i] += a->blue_[i]; + out->alpha_[i] += a->alpha_[i]; + } + } +} + +//------------------------------------------------------------------------------ +// Image transforms. + +static WEBP_INLINE uint32_t Average2(uint32_t a0, uint32_t a1) { + return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); +} + +static WEBP_INLINE uint32_t Average3(uint32_t a0, uint32_t a1, uint32_t a2) { + return Average2(Average2(a0, a2), a1); +} + +static WEBP_INLINE uint32_t Average4(uint32_t a0, uint32_t a1, + uint32_t a2, uint32_t a3) { + return Average2(Average2(a0, a1), Average2(a2, a3)); +} + +static WEBP_INLINE uint32_t Clip255(uint32_t a) { + if (a < 256) { + return a; + } + // return 0, when a is a negative integer. + // return 255, when a is positive. + return ~a >> 24; +} + +static WEBP_INLINE int AddSubtractComponentFull(int a, int b, int c) { + return Clip255(a + b - c); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, + uint32_t c2) { + const int a = AddSubtractComponentFull(c0 >> 24, c1 >> 24, c2 >> 24); + const int r = AddSubtractComponentFull((c0 >> 16) & 0xff, + (c1 >> 16) & 0xff, + (c2 >> 16) & 0xff); + const int g = AddSubtractComponentFull((c0 >> 8) & 0xff, + (c1 >> 8) & 0xff, + (c2 >> 8) & 0xff); + const int b = AddSubtractComponentFull(c0 & 0xff, c1 & 0xff, c2 & 0xff); + return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; +} + +static WEBP_INLINE int AddSubtractComponentHalf(int a, int b) { + return Clip255(a + (a - b) / 2); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1, + uint32_t c2) { + const uint32_t ave = Average2(c0, c1); + const int a = AddSubtractComponentHalf(ave >> 24, c2 >> 24); + const int r = AddSubtractComponentHalf((ave >> 16) & 0xff, (c2 >> 16) & 0xff); + const int g = AddSubtractComponentHalf((ave >> 8) & 0xff, (c2 >> 8) & 0xff); + const int b = AddSubtractComponentHalf((ave >> 0) & 0xff, (c2 >> 0) & 0xff); + return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; +} + +// gcc-4.9 on ARM generates incorrect code in Select() when Sub3() is inlined. +#if defined(__arm__) && \ + (LOCAL_GCC_VERSION == 0x409 || LOCAL_GCC_VERSION == 0x408) +# define LOCAL_INLINE __attribute__ ((noinline)) +#else +# define LOCAL_INLINE WEBP_INLINE +#endif + +static LOCAL_INLINE int Sub3(int a, int b, int c) { + const int pb = b - c; + const int pa = a - c; + return abs(pb) - abs(pa); +} + +#undef LOCAL_INLINE + +static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) { + const int pa_minus_pb = + Sub3((a >> 24) , (b >> 24) , (c >> 24) ) + + Sub3((a >> 16) & 0xff, (b >> 16) & 0xff, (c >> 16) & 0xff) + + Sub3((a >> 8) & 0xff, (b >> 8) & 0xff, (c >> 8) & 0xff) + + Sub3((a ) & 0xff, (b ) & 0xff, (c ) & 0xff); + return (pa_minus_pb <= 0) ? a : b; +} + +//------------------------------------------------------------------------------ +// Predictors + +static uint32_t Predictor2(uint32_t left, const uint32_t* const top) { + (void)left; + return top[0]; +} +static uint32_t Predictor3(uint32_t left, const uint32_t* const top) { + (void)left; + return top[1]; +} +static uint32_t Predictor4(uint32_t left, const uint32_t* const top) { + (void)left; + return top[-1]; +} +static uint32_t Predictor5(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average3(left, top[0], top[1]); + return pred; +} +static uint32_t Predictor6(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average2(left, top[-1]); + return pred; +} +static uint32_t Predictor7(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average2(left, top[0]); + return pred; +} +static uint32_t Predictor8(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average2(top[-1], top[0]); + (void)left; + return pred; +} +static uint32_t Predictor9(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average2(top[0], top[1]); + (void)left; + return pred; +} +static uint32_t Predictor10(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average4(left, top[-1], top[0], top[1]); + return pred; +} +static uint32_t Predictor11(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Select(top[0], left, top[-1]); + return pred; +} +static uint32_t Predictor12(uint32_t left, const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractFull(left, top[0], top[-1]); + return pred; +} +static uint32_t Predictor13(uint32_t left, const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractHalf(left, top[0], top[-1]); + return pred; +} + +//------------------------------------------------------------------------------ + +static void PredictorSub0_C(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i < num_pixels; ++i) out[i] = VP8LSubPixels(in[i], ARGB_BLACK); + (void)upper; +} + +static void PredictorSub1_C(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i < num_pixels; ++i) out[i] = VP8LSubPixels(in[i], in[i - 1]); + (void)upper; +} + +GENERATE_PREDICTOR_SUB(Predictor2, PredictorSub2_C) +GENERATE_PREDICTOR_SUB(Predictor3, PredictorSub3_C) +GENERATE_PREDICTOR_SUB(Predictor4, PredictorSub4_C) +GENERATE_PREDICTOR_SUB(Predictor5, PredictorSub5_C) +GENERATE_PREDICTOR_SUB(Predictor6, PredictorSub6_C) +GENERATE_PREDICTOR_SUB(Predictor7, PredictorSub7_C) +GENERATE_PREDICTOR_SUB(Predictor8, PredictorSub8_C) +GENERATE_PREDICTOR_SUB(Predictor9, PredictorSub9_C) +GENERATE_PREDICTOR_SUB(Predictor10, PredictorSub10_C) +GENERATE_PREDICTOR_SUB(Predictor11, PredictorSub11_C) +GENERATE_PREDICTOR_SUB(Predictor12, PredictorSub12_C) +GENERATE_PREDICTOR_SUB(Predictor13, PredictorSub13_C) + +//------------------------------------------------------------------------------ + +VP8LProcessEncBlueAndRedFunc VP8LSubtractGreenFromBlueAndRed; + +VP8LTransformColorFunc VP8LTransformColor; + +VP8LCollectColorBlueTransformsFunc VP8LCollectColorBlueTransforms; +VP8LCollectColorRedTransformsFunc VP8LCollectColorRedTransforms; + +VP8LFastLog2SlowFunc VP8LFastLog2Slow; +VP8LFastLog2SlowFunc VP8LFastSLog2Slow; + +VP8LCostFunc VP8LExtraCost; +VP8LCostCombinedFunc VP8LExtraCostCombined; +VP8LCombinedShannonEntropyFunc VP8LCombinedShannonEntropy; + +VP8LGetEntropyUnrefinedFunc VP8LGetEntropyUnrefined; +VP8LGetCombinedEntropyUnrefinedFunc VP8LGetCombinedEntropyUnrefined; + +VP8LHistogramAddFunc VP8LHistogramAdd; + +VP8LVectorMismatchFunc VP8LVectorMismatch; +VP8LBundleColorMapFunc VP8LBundleColorMap; + +VP8LPredictorAddSubFunc VP8LPredictorsSub[16]; +VP8LPredictorAddSubFunc VP8LPredictorsSub_C[16]; + +extern void VP8LEncDspInitSSE2(void); +extern void VP8LEncDspInitSSE41(void); +extern void VP8LEncDspInitNEON(void); +extern void VP8LEncDspInitMIPS32(void); +extern void VP8LEncDspInitMIPSdspR2(void); +extern void VP8LEncDspInitMSA(void); + +static volatile VP8CPUInfo lossless_enc_last_cpuinfo_used = + (VP8CPUInfo)&lossless_enc_last_cpuinfo_used; + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInit(void) { + if (lossless_enc_last_cpuinfo_used == VP8GetCPUInfo) return; + + VP8LDspInit(); + + VP8LSubtractGreenFromBlueAndRed = VP8LSubtractGreenFromBlueAndRed_C; + + VP8LTransformColor = VP8LTransformColor_C; + + VP8LCollectColorBlueTransforms = VP8LCollectColorBlueTransforms_C; + VP8LCollectColorRedTransforms = VP8LCollectColorRedTransforms_C; + + VP8LFastLog2Slow = FastLog2Slow; + VP8LFastSLog2Slow = FastSLog2Slow; + + VP8LExtraCost = ExtraCost; + VP8LExtraCostCombined = ExtraCostCombined; + VP8LCombinedShannonEntropy = CombinedShannonEntropy; + + VP8LGetEntropyUnrefined = GetEntropyUnrefined; + VP8LGetCombinedEntropyUnrefined = GetCombinedEntropyUnrefined; + + VP8LHistogramAdd = HistogramAdd; + + VP8LVectorMismatch = VectorMismatch; + VP8LBundleColorMap = VP8LBundleColorMap_C; + + VP8LPredictorsSub[0] = PredictorSub0_C; + VP8LPredictorsSub[1] = PredictorSub1_C; + VP8LPredictorsSub[2] = PredictorSub2_C; + VP8LPredictorsSub[3] = PredictorSub3_C; + VP8LPredictorsSub[4] = PredictorSub4_C; + VP8LPredictorsSub[5] = PredictorSub5_C; + VP8LPredictorsSub[6] = PredictorSub6_C; + VP8LPredictorsSub[7] = PredictorSub7_C; + VP8LPredictorsSub[8] = PredictorSub8_C; + VP8LPredictorsSub[9] = PredictorSub9_C; + VP8LPredictorsSub[10] = PredictorSub10_C; + VP8LPredictorsSub[11] = PredictorSub11_C; + VP8LPredictorsSub[12] = PredictorSub12_C; + VP8LPredictorsSub[13] = PredictorSub13_C; + VP8LPredictorsSub[14] = PredictorSub0_C; // <- padding security sentinels + VP8LPredictorsSub[15] = PredictorSub0_C; + + VP8LPredictorsSub_C[0] = PredictorSub0_C; + VP8LPredictorsSub_C[1] = PredictorSub1_C; + VP8LPredictorsSub_C[2] = PredictorSub2_C; + VP8LPredictorsSub_C[3] = PredictorSub3_C; + VP8LPredictorsSub_C[4] = PredictorSub4_C; + VP8LPredictorsSub_C[5] = PredictorSub5_C; + VP8LPredictorsSub_C[6] = PredictorSub6_C; + VP8LPredictorsSub_C[7] = PredictorSub7_C; + VP8LPredictorsSub_C[8] = PredictorSub8_C; + VP8LPredictorsSub_C[9] = PredictorSub9_C; + VP8LPredictorsSub_C[10] = PredictorSub10_C; + VP8LPredictorsSub_C[11] = PredictorSub11_C; + VP8LPredictorsSub_C[12] = PredictorSub12_C; + VP8LPredictorsSub_C[13] = PredictorSub13_C; + VP8LPredictorsSub_C[14] = PredictorSub0_C; // <- padding security sentinels + VP8LPredictorsSub_C[15] = PredictorSub0_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8LEncDspInitSSE2(); +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + VP8LEncDspInitSSE41(); + } +#endif + } +#endif +#if defined(WEBP_USE_NEON) + if (VP8GetCPUInfo(kNEON)) { + VP8LEncDspInitNEON(); + } +#endif +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + VP8LEncDspInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8LEncDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8LEncDspInitMSA(); + } +#endif + } + lossless_enc_last_cpuinfo_used = VP8GetCPUInfo; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/dsp/lossless_enc_mips32.c b/3rdparty/libwebp/dsp/lossless_enc_mips32.c new file mode 100644 index 0000000000..4186b9f50d --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_enc_mips32.c @@ -0,0 +1,431 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of lossless functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "./dsp.h" +#include "./lossless.h" +#include "./lossless_common.h" + +#if defined(WEBP_USE_MIPS32) + +#include +#include +#include +#include + +static float FastSLog2Slow(uint32_t v) { + assert(v >= LOG_LOOKUP_IDX_MAX); + if (v < APPROX_LOG_WITH_CORRECTION_MAX) { + uint32_t log_cnt, y, correction; + const int c24 = 24; + const float v_f = (float)v; + uint32_t temp; + + // Xf = 256 = 2^8 + // log_cnt is index of leading one in upper 24 bits + __asm__ volatile( + "clz %[log_cnt], %[v] \n\t" + "addiu %[y], $zero, 1 \n\t" + "subu %[log_cnt], %[c24], %[log_cnt] \n\t" + "sllv %[y], %[y], %[log_cnt] \n\t" + "srlv %[temp], %[v], %[log_cnt] \n\t" + : [log_cnt]"=&r"(log_cnt), [y]"=&r"(y), + [temp]"=r"(temp) + : [c24]"r"(c24), [v]"r"(v) + ); + + // vf = (2^log_cnt) * Xf; where y = 2^log_cnt and Xf < 256 + // Xf = floor(Xf) * (1 + (v % y) / v) + // log2(Xf) = log2(floor(Xf)) + log2(1 + (v % y) / v) + // The correction factor: log(1 + d) ~ d; for very small d values, so + // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v + // LOG_2_RECIPROCAL ~ 23/16 + + // (v % y) = (v % 2^log_cnt) = v & (2^log_cnt - 1) + correction = (23 * (v & (y - 1))) >> 4; + return v_f * (kLog2Table[temp] + log_cnt) + correction; + } else { + return (float)(LOG_2_RECIPROCAL * v * log((double)v)); + } +} + +static float FastLog2Slow(uint32_t v) { + assert(v >= LOG_LOOKUP_IDX_MAX); + if (v < APPROX_LOG_WITH_CORRECTION_MAX) { + uint32_t log_cnt, y; + const int c24 = 24; + double log_2; + uint32_t temp; + + __asm__ volatile( + "clz %[log_cnt], %[v] \n\t" + "addiu %[y], $zero, 1 \n\t" + "subu %[log_cnt], %[c24], %[log_cnt] \n\t" + "sllv %[y], %[y], %[log_cnt] \n\t" + "srlv %[temp], %[v], %[log_cnt] \n\t" + : [log_cnt]"=&r"(log_cnt), [y]"=&r"(y), + [temp]"=r"(temp) + : [c24]"r"(c24), [v]"r"(v) + ); + + log_2 = kLog2Table[temp] + log_cnt; + if (v >= APPROX_LOG_MAX) { + // Since the division is still expensive, add this correction factor only + // for large values of 'v'. + + const uint32_t correction = (23 * (v & (y - 1))) >> 4; + log_2 += (double)correction / v; + } + return (float)log_2; + } else { + return (float)(LOG_2_RECIPROCAL * log((double)v)); + } +} + +// C version of this function: +// int i = 0; +// int64_t cost = 0; +// const uint32_t* pop = &population[4]; +// const uint32_t* LoopEnd = &population[length]; +// while (pop != LoopEnd) { +// ++i; +// cost += i * *pop; +// cost += i * *(pop + 1); +// pop += 2; +// } +// return (double)cost; +static double ExtraCost(const uint32_t* const population, int length) { + int i, temp0, temp1; + const uint32_t* pop = &population[4]; + const uint32_t* const LoopEnd = &population[length]; + + __asm__ volatile( + "mult $zero, $zero \n\t" + "xor %[i], %[i], %[i] \n\t" + "beq %[pop], %[LoopEnd], 2f \n\t" + "1: \n\t" + "lw %[temp0], 0(%[pop]) \n\t" + "lw %[temp1], 4(%[pop]) \n\t" + "addiu %[i], %[i], 1 \n\t" + "addiu %[pop], %[pop], 8 \n\t" + "madd %[i], %[temp0] \n\t" + "madd %[i], %[temp1] \n\t" + "bne %[pop], %[LoopEnd], 1b \n\t" + "2: \n\t" + "mfhi %[temp0] \n\t" + "mflo %[temp1] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [i]"=&r"(i), [pop]"+r"(pop) + : [LoopEnd]"r"(LoopEnd) + : "memory", "hi", "lo" + ); + + return (double)((int64_t)temp0 << 32 | temp1); +} + +// C version of this function: +// int i = 0; +// int64_t cost = 0; +// const uint32_t* pX = &X[4]; +// const uint32_t* pY = &Y[4]; +// const uint32_t* LoopEnd = &X[length]; +// while (pX != LoopEnd) { +// const uint32_t xy0 = *pX + *pY; +// const uint32_t xy1 = *(pX + 1) + *(pY + 1); +// ++i; +// cost += i * xy0; +// cost += i * xy1; +// pX += 2; +// pY += 2; +// } +// return (double)cost; +static double ExtraCostCombined(const uint32_t* const X, + const uint32_t* const Y, int length) { + int i, temp0, temp1, temp2, temp3; + const uint32_t* pX = &X[4]; + const uint32_t* pY = &Y[4]; + const uint32_t* const LoopEnd = &X[length]; + + __asm__ volatile( + "mult $zero, $zero \n\t" + "xor %[i], %[i], %[i] \n\t" + "beq %[pX], %[LoopEnd], 2f \n\t" + "1: \n\t" + "lw %[temp0], 0(%[pX]) \n\t" + "lw %[temp1], 0(%[pY]) \n\t" + "lw %[temp2], 4(%[pX]) \n\t" + "lw %[temp3], 4(%[pY]) \n\t" + "addiu %[i], %[i], 1 \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addiu %[pX], %[pX], 8 \n\t" + "addiu %[pY], %[pY], 8 \n\t" + "madd %[i], %[temp0] \n\t" + "madd %[i], %[temp2] \n\t" + "bne %[pX], %[LoopEnd], 1b \n\t" + "2: \n\t" + "mfhi %[temp0] \n\t" + "mflo %[temp1] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [i]"=&r"(i), [pX]"+r"(pX), [pY]"+r"(pY) + : [LoopEnd]"r"(LoopEnd) + : "memory", "hi", "lo" + ); + + return (double)((int64_t)temp0 << 32 | temp1); +} + +#define HUFFMAN_COST_PASS \ + __asm__ volatile( \ + "sll %[temp1], %[temp0], 3 \n\t" \ + "addiu %[temp3], %[streak], -3 \n\t" \ + "addu %[temp2], %[pstreaks], %[temp1] \n\t" \ + "blez %[temp3], 1f \n\t" \ + "srl %[temp1], %[temp1], 1 \n\t" \ + "addu %[temp3], %[pcnts], %[temp1] \n\t" \ + "lw %[temp0], 4(%[temp2]) \n\t" \ + "lw %[temp1], 0(%[temp3]) \n\t" \ + "addu %[temp0], %[temp0], %[streak] \n\t" \ + "addiu %[temp1], %[temp1], 1 \n\t" \ + "sw %[temp0], 4(%[temp2]) \n\t" \ + "sw %[temp1], 0(%[temp3]) \n\t" \ + "b 2f \n\t" \ + "1: \n\t" \ + "lw %[temp0], 0(%[temp2]) \n\t" \ + "addu %[temp0], %[temp0], %[streak] \n\t" \ + "sw %[temp0], 0(%[temp2]) \n\t" \ + "2: \n\t" \ + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), \ + [temp3]"=&r"(temp3), [temp0]"+r"(temp0) \ + : [pstreaks]"r"(pstreaks), [pcnts]"r"(pcnts), \ + [streak]"r"(streak) \ + : "memory" \ + ); + +// Returns the various RLE counts +static WEBP_INLINE void GetEntropyUnrefinedHelper( + uint32_t val, int i, uint32_t* const val_prev, int* const i_prev, + VP8LBitEntropy* const bit_entropy, VP8LStreaks* const stats) { + int* const pstreaks = &stats->streaks[0][0]; + int* const pcnts = &stats->counts[0]; + int temp0, temp1, temp2, temp3; + const int streak = i - *i_prev; + + // Gather info for the bit entropy. + if (*val_prev != 0) { + bit_entropy->sum += (*val_prev) * streak; + bit_entropy->nonzeros += streak; + bit_entropy->nonzero_code = *i_prev; + bit_entropy->entropy -= VP8LFastSLog2(*val_prev) * streak; + if (bit_entropy->max_val < *val_prev) { + bit_entropy->max_val = *val_prev; + } + } + + // Gather info for the Huffman cost. + temp0 = (*val_prev != 0); + HUFFMAN_COST_PASS + + *val_prev = val; + *i_prev = i; +} + +static void GetEntropyUnrefined(const uint32_t X[], int length, + VP8LBitEntropy* const bit_entropy, + VP8LStreaks* const stats) { + int i; + int i_prev = 0; + uint32_t x_prev = X[0]; + + memset(stats, 0, sizeof(*stats)); + VP8LBitEntropyInit(bit_entropy); + + for (i = 1; i < length; ++i) { + const uint32_t x = X[i]; + if (x != x_prev) { + GetEntropyUnrefinedHelper(x, i, &x_prev, &i_prev, bit_entropy, stats); + } + } + GetEntropyUnrefinedHelper(0, i, &x_prev, &i_prev, bit_entropy, stats); + + bit_entropy->entropy += VP8LFastSLog2(bit_entropy->sum); +} + +static void GetCombinedEntropyUnrefined(const uint32_t X[], const uint32_t Y[], + int length, + VP8LBitEntropy* const bit_entropy, + VP8LStreaks* const stats) { + int i = 1; + int i_prev = 0; + uint32_t xy_prev = X[0] + Y[0]; + + memset(stats, 0, sizeof(*stats)); + VP8LBitEntropyInit(bit_entropy); + + for (i = 1; i < length; ++i) { + const uint32_t xy = X[i] + Y[i]; + if (xy != xy_prev) { + GetEntropyUnrefinedHelper(xy, i, &xy_prev, &i_prev, bit_entropy, stats); + } + } + GetEntropyUnrefinedHelper(0, i, &xy_prev, &i_prev, bit_entropy, stats); + + bit_entropy->entropy += VP8LFastSLog2(bit_entropy->sum); +} + +#define ASM_START \ + __asm__ volatile( \ + ".set push \n\t" \ + ".set at \n\t" \ + ".set macro \n\t" \ + "1: \n\t" + +// P2 = P0 + P1 +// A..D - offsets +// E - temp variable to tell macro +// if pointer should be incremented +// literal_ and successive histograms could be unaligned +// so we must use ulw and usw +#define ADD_TO_OUT(A, B, C, D, E, P0, P1, P2) \ + "ulw %[temp0], " #A "(%[" #P0 "]) \n\t" \ + "ulw %[temp1], " #B "(%[" #P0 "]) \n\t" \ + "ulw %[temp2], " #C "(%[" #P0 "]) \n\t" \ + "ulw %[temp3], " #D "(%[" #P0 "]) \n\t" \ + "ulw %[temp4], " #A "(%[" #P1 "]) \n\t" \ + "ulw %[temp5], " #B "(%[" #P1 "]) \n\t" \ + "ulw %[temp6], " #C "(%[" #P1 "]) \n\t" \ + "ulw %[temp7], " #D "(%[" #P1 "]) \n\t" \ + "addu %[temp4], %[temp4], %[temp0] \n\t" \ + "addu %[temp5], %[temp5], %[temp1] \n\t" \ + "addu %[temp6], %[temp6], %[temp2] \n\t" \ + "addu %[temp7], %[temp7], %[temp3] \n\t" \ + "addiu %[" #P0 "], %[" #P0 "], 16 \n\t" \ + ".if " #E " == 1 \n\t" \ + "addiu %[" #P1 "], %[" #P1 "], 16 \n\t" \ + ".endif \n\t" \ + "usw %[temp4], " #A "(%[" #P2 "]) \n\t" \ + "usw %[temp5], " #B "(%[" #P2 "]) \n\t" \ + "usw %[temp6], " #C "(%[" #P2 "]) \n\t" \ + "usw %[temp7], " #D "(%[" #P2 "]) \n\t" \ + "addiu %[" #P2 "], %[" #P2 "], 16 \n\t" \ + "bne %[" #P0 "], %[LoopEnd], 1b \n\t" \ + ".set pop \n\t" \ + +#define ASM_END_COMMON_0 \ + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), \ + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), \ + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), \ + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), \ + [pa]"+r"(pa), [pout]"+r"(pout) + +#define ASM_END_COMMON_1 \ + : [LoopEnd]"r"(LoopEnd) \ + : "memory", "at" \ + ); + +#define ASM_END_0 \ + ASM_END_COMMON_0 \ + , [pb]"+r"(pb) \ + ASM_END_COMMON_1 + +#define ASM_END_1 \ + ASM_END_COMMON_0 \ + ASM_END_COMMON_1 + +#define ADD_VECTOR(A, B, OUT, SIZE, EXTRA_SIZE) do { \ + const uint32_t* pa = (const uint32_t*)(A); \ + const uint32_t* pb = (const uint32_t*)(B); \ + uint32_t* pout = (uint32_t*)(OUT); \ + const uint32_t* const LoopEnd = pa + (SIZE); \ + assert((SIZE) % 4 == 0); \ + ASM_START \ + ADD_TO_OUT(0, 4, 8, 12, 1, pa, pb, pout) \ + ASM_END_0 \ + if ((EXTRA_SIZE) > 0) { \ + const int last = (EXTRA_SIZE); \ + int i; \ + for (i = 0; i < last; ++i) pout[i] = pa[i] + pb[i]; \ + } \ +} while (0) + +#define ADD_VECTOR_EQ(A, OUT, SIZE, EXTRA_SIZE) do { \ + const uint32_t* pa = (const uint32_t*)(A); \ + uint32_t* pout = (uint32_t*)(OUT); \ + const uint32_t* const LoopEnd = pa + (SIZE); \ + assert((SIZE) % 4 == 0); \ + ASM_START \ + ADD_TO_OUT(0, 4, 8, 12, 0, pa, pout, pout) \ + ASM_END_1 \ + if ((EXTRA_SIZE) > 0) { \ + const int last = (EXTRA_SIZE); \ + int i; \ + for (i = 0; i < last; ++i) pout[i] += pa[i]; \ + } \ +} while (0) + +static void HistogramAdd(const VP8LHistogram* const a, + const VP8LHistogram* const b, + VP8LHistogram* const out) { + uint32_t temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + const int extra_cache_size = VP8LHistogramNumCodes(a->palette_code_bits_) + - (NUM_LITERAL_CODES + NUM_LENGTH_CODES); + assert(a->palette_code_bits_ == b->palette_code_bits_); + + if (b != out) { + ADD_VECTOR(a->literal_, b->literal_, out->literal_, + NUM_LITERAL_CODES + NUM_LENGTH_CODES, extra_cache_size); + ADD_VECTOR(a->distance_, b->distance_, out->distance_, + NUM_DISTANCE_CODES, 0); + ADD_VECTOR(a->red_, b->red_, out->red_, NUM_LITERAL_CODES, 0); + ADD_VECTOR(a->blue_, b->blue_, out->blue_, NUM_LITERAL_CODES, 0); + ADD_VECTOR(a->alpha_, b->alpha_, out->alpha_, NUM_LITERAL_CODES, 0); + } else { + ADD_VECTOR_EQ(a->literal_, out->literal_, + NUM_LITERAL_CODES + NUM_LENGTH_CODES, extra_cache_size); + ADD_VECTOR_EQ(a->distance_, out->distance_, NUM_DISTANCE_CODES, 0); + ADD_VECTOR_EQ(a->red_, out->red_, NUM_LITERAL_CODES, 0); + ADD_VECTOR_EQ(a->blue_, out->blue_, NUM_LITERAL_CODES, 0); + ADD_VECTOR_EQ(a->alpha_, out->alpha_, NUM_LITERAL_CODES, 0); + } +} + +#undef ADD_VECTOR_EQ +#undef ADD_VECTOR +#undef ASM_END_1 +#undef ASM_END_0 +#undef ASM_END_COMMON_1 +#undef ASM_END_COMMON_0 +#undef ADD_TO_OUT +#undef ASM_START + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitMIPS32(void) { + VP8LFastSLog2Slow = FastSLog2Slow; + VP8LFastLog2Slow = FastLog2Slow; + VP8LExtraCost = ExtraCost; + VP8LExtraCostCombined = ExtraCostCombined; + VP8LGetEntropyUnrefined = GetEntropyUnrefined; + VP8LGetCombinedEntropyUnrefined = GetCombinedEntropyUnrefined; + VP8LHistogramAdd = HistogramAdd; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(VP8LEncDspInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/3rdparty/libwebp/dsp/lossless_enc_mips_dsp_r2.c b/3rdparty/libwebp/dsp/lossless_enc_mips_dsp_r2.c new file mode 100644 index 0000000000..0abf3c4f36 --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_enc_mips_dsp_r2.c @@ -0,0 +1,275 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transform methods for lossless encoder. +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "./lossless.h" + +static void SubtractGreenFromBlueAndRed(uint32_t* argb_data, + int num_pixels) { + uint32_t temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + uint32_t* const p_loop1_end = argb_data + (num_pixels & ~3); + uint32_t* const p_loop2_end = p_loop1_end + (num_pixels & 3); + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[argb_data], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[argb_data]) \n\t" + "lw %[temp1], 4(%[argb_data]) \n\t" + "lw %[temp2], 8(%[argb_data]) \n\t" + "lw %[temp3], 12(%[argb_data]) \n\t" + "ext %[temp4], %[temp0], 8, 8 \n\t" + "ext %[temp5], %[temp1], 8, 8 \n\t" + "ext %[temp6], %[temp2], 8, 8 \n\t" + "ext %[temp7], %[temp3], 8, 8 \n\t" + "addiu %[argb_data], %[argb_data], 16 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "replv.ph %[temp5], %[temp5] \n\t" + "replv.ph %[temp6], %[temp6] \n\t" + "replv.ph %[temp7], %[temp7] \n\t" + "subu.qb %[temp0], %[temp0], %[temp4] \n\t" + "subu.qb %[temp1], %[temp1], %[temp5] \n\t" + "subu.qb %[temp2], %[temp2], %[temp6] \n\t" + "subu.qb %[temp3], %[temp3], %[temp7] \n\t" + "sw %[temp0], -16(%[argb_data]) \n\t" + "sw %[temp1], -12(%[argb_data]) \n\t" + "sw %[temp2], -8(%[argb_data]) \n\t" + "bne %[argb_data], %[p_loop1_end], 0b \n\t" + " sw %[temp3], -4(%[argb_data]) \n\t" + "3: \n\t" + "beq %[argb_data], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[argb_data]) \n\t" + "addiu %[argb_data], %[argb_data], 4 \n\t" + "ext %[temp4], %[temp0], 8, 8 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "subu.qb %[temp0], %[temp0], %[temp4] \n\t" + "bne %[argb_data], %[p_loop2_end], 1b \n\t" + " sw %[temp0], -4(%[argb_data]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [argb_data]"+&r"(argb_data), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp6]"=&r"(temp6), + [temp7]"=&r"(temp7) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static WEBP_INLINE uint32_t ColorTransformDelta(int8_t color_pred, + int8_t color) { + return (uint32_t)((int)(color_pred) * color) >> 5; +} + +static void TransformColor(const VP8LMultipliers* const m, uint32_t* data, + int num_pixels) { + int temp0, temp1, temp2, temp3, temp4, temp5; + uint32_t argb, argb1, new_red, new_red1; + const uint32_t G_to_R = m->green_to_red_; + const uint32_t G_to_B = m->green_to_blue_; + const uint32_t R_to_B = m->red_to_blue_; + uint32_t* const p_loop_end = data + (num_pixels & ~1); + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[data], %[p_loop_end], 1f \n\t" + " nop \n\t" + "replv.ph %[temp0], %[G_to_R] \n\t" + "replv.ph %[temp1], %[G_to_B] \n\t" + "replv.ph %[temp2], %[R_to_B] \n\t" + "shll.ph %[temp0], %[temp0], 8 \n\t" + "shll.ph %[temp1], %[temp1], 8 \n\t" + "shll.ph %[temp2], %[temp2], 8 \n\t" + "shra.ph %[temp0], %[temp0], 8 \n\t" + "shra.ph %[temp1], %[temp1], 8 \n\t" + "shra.ph %[temp2], %[temp2], 8 \n\t" + "0: \n\t" + "lw %[argb], 0(%[data]) \n\t" + "lw %[argb1], 4(%[data]) \n\t" + "lhu %[new_red], 2(%[data]) \n\t" + "lhu %[new_red1], 6(%[data]) \n\t" + "precrq.qb.ph %[temp3], %[argb], %[argb1] \n\t" + "precr.qb.ph %[temp4], %[argb], %[argb1] \n\t" + "preceu.ph.qbra %[temp3], %[temp3] \n\t" + "preceu.ph.qbla %[temp4], %[temp4] \n\t" + "shll.ph %[temp3], %[temp3], 8 \n\t" + "shll.ph %[temp4], %[temp4], 8 \n\t" + "shra.ph %[temp3], %[temp3], 8 \n\t" + "shra.ph %[temp4], %[temp4], 8 \n\t" + "mul.ph %[temp5], %[temp3], %[temp0] \n\t" + "mul.ph %[temp3], %[temp3], %[temp1] \n\t" + "mul.ph %[temp4], %[temp4], %[temp2] \n\t" + "addiu %[data], %[data], 8 \n\t" + "ins %[new_red1], %[new_red], 16, 16 \n\t" + "ins %[argb1], %[argb], 16, 16 \n\t" + "shra.ph %[temp5], %[temp5], 5 \n\t" + "shra.ph %[temp3], %[temp3], 5 \n\t" + "shra.ph %[temp4], %[temp4], 5 \n\t" + "subu.ph %[new_red1], %[new_red1], %[temp5] \n\t" + "subu.ph %[argb1], %[argb1], %[temp3] \n\t" + "preceu.ph.qbra %[temp5], %[new_red1] \n\t" + "subu.ph %[argb1], %[argb1], %[temp4] \n\t" + "preceu.ph.qbra %[temp3], %[argb1] \n\t" + "sb %[temp5], -2(%[data]) \n\t" + "sb %[temp3], -4(%[data]) \n\t" + "sra %[temp5], %[temp5], 16 \n\t" + "sra %[temp3], %[temp3], 16 \n\t" + "sb %[temp5], -6(%[data]) \n\t" + "bne %[data], %[p_loop_end], 0b \n\t" + " sb %[temp3], -8(%[data]) \n\t" + "1: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [new_red1]"=&r"(new_red1), [new_red]"=&r"(new_red), + [argb]"=&r"(argb), [argb1]"=&r"(argb1), [data]"+&r"(data) + : [G_to_R]"r"(G_to_R), [R_to_B]"r"(R_to_B), + [G_to_B]"r"(G_to_B), [p_loop_end]"r"(p_loop_end) + : "memory", "hi", "lo" + ); + + if (num_pixels & 1) { + const uint32_t argb_ = data[0]; + const uint32_t green = argb_ >> 8; + const uint32_t red = argb_ >> 16; + uint32_t new_blue = argb_; + new_red = red; + new_red -= ColorTransformDelta(m->green_to_red_, green); + new_red &= 0xff; + new_blue -= ColorTransformDelta(m->green_to_blue_, green); + new_blue -= ColorTransformDelta(m->red_to_blue_, red); + new_blue &= 0xff; + data[0] = (argb_ & 0xff00ff00u) | (new_red << 16) | (new_blue); + } +} + +static WEBP_INLINE uint8_t TransformColorBlue(uint8_t green_to_blue, + uint8_t red_to_blue, + uint32_t argb) { + const uint32_t green = argb >> 8; + const uint32_t red = argb >> 16; + uint8_t new_blue = argb; + new_blue -= ColorTransformDelta(green_to_blue, green); + new_blue -= ColorTransformDelta(red_to_blue, red); + return (new_blue & 0xff); +} + +static void CollectColorBlueTransforms(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]) { + const int rtb = (red_to_blue << 16) | (red_to_blue & 0xffff); + const int gtb = (green_to_blue << 16) | (green_to_blue & 0xffff); + const uint32_t mask = 0xff00ffu; + while (tile_height-- > 0) { + int x; + const uint32_t* p_argb = argb; + argb += stride; + for (x = 0; x < (tile_width >> 1); ++x) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + __asm__ volatile ( + "lw %[temp0], 0(%[p_argb]) \n\t" + "lw %[temp1], 4(%[p_argb]) \n\t" + "precr.qb.ph %[temp2], %[temp0], %[temp1] \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "shra.ph %[temp2], %[temp2], 8 \n\t" + "shra.ph %[temp3], %[temp1], 8 \n\t" + "mul.ph %[temp5], %[temp2], %[rtb] \n\t" + "mul.ph %[temp6], %[temp3], %[gtb] \n\t" + "and %[temp4], %[temp1], %[mask] \n\t" + "addiu %[p_argb], %[p_argb], 8 \n\t" + "shra.ph %[temp5], %[temp5], 5 \n\t" + "shra.ph %[temp6], %[temp6], 5 \n\t" + "subu.qb %[temp2], %[temp4], %[temp5] \n\t" + "subu.qb %[temp2], %[temp2], %[temp6] \n\t" + : [p_argb]"+&r"(p_argb), [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), + [temp5]"=&r"(temp5), [temp6]"=&r"(temp6) + : [rtb]"r"(rtb), [gtb]"r"(gtb), [mask]"r"(mask) + : "memory", "hi", "lo" + ); + ++histo[(uint8_t)(temp2 >> 16)]; + ++histo[(uint8_t)temp2]; + } + if (tile_width & 1) { + ++histo[TransformColorBlue(green_to_blue, red_to_blue, *p_argb)]; + } + } +} + +static WEBP_INLINE uint8_t TransformColorRed(uint8_t green_to_red, + uint32_t argb) { + const uint32_t green = argb >> 8; + uint32_t new_red = argb >> 16; + new_red -= ColorTransformDelta(green_to_red, green); + return (new_red & 0xff); +} + +static void CollectColorRedTransforms(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]) { + const int gtr = (green_to_red << 16) | (green_to_red & 0xffff); + while (tile_height-- > 0) { + int x; + const uint32_t* p_argb = argb; + argb += stride; + for (x = 0; x < (tile_width >> 1); ++x) { + int temp0, temp1, temp2, temp3, temp4; + __asm__ volatile ( + "lw %[temp0], 0(%[p_argb]) \n\t" + "lw %[temp1], 4(%[p_argb]) \n\t" + "precrq.ph.w %[temp4], %[temp0], %[temp1] \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "shra.ph %[temp3], %[temp1], 8 \n\t" + "mul.ph %[temp2], %[temp3], %[gtr] \n\t" + "addiu %[p_argb], %[p_argb], 8 \n\t" + "shra.ph %[temp2], %[temp2], 5 \n\t" + "subu.qb %[temp2], %[temp4], %[temp2] \n\t" + : [p_argb]"+&r"(p_argb), [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [temp4]"=&r"(temp4) + : [gtr]"r"(gtr) + : "memory", "hi", "lo" + ); + ++histo[(uint8_t)(temp2 >> 16)]; + ++histo[(uint8_t)temp2]; + } + if (tile_width & 1) { + ++histo[TransformColorRed(green_to_red, *p_argb)]; + } + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitMIPSdspR2(void) { + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed; + VP8LTransformColor = TransformColor; + VP8LCollectColorBlueTransforms = CollectColorBlueTransforms; + VP8LCollectColorRedTransforms = CollectColorRedTransforms; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8LEncDspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/lossless_enc_msa.c b/3rdparty/libwebp/dsp/lossless_enc_msa.c new file mode 100644 index 0000000000..2f69ba3bca --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_enc_msa.c @@ -0,0 +1,147 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA variant of Image transform methods for lossless encoder. +// +// Authors: Prashant Patil (Prashant.Patil@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "./lossless.h" +#include "./msa_macro.h" + +#define TRANSFORM_COLOR_8(src0, src1, dst0, dst1, c0, c1, mask0, mask1) do { \ + v8i16 g0, g1, t0, t1, t2, t3; \ + v4i32 t4, t5; \ + VSHF_B2_SH(src0, src0, src1, src1, mask0, mask0, g0, g1); \ + DOTP_SB2_SH(g0, g1, c0, c0, t0, t1); \ + SRAI_H2_SH(t0, t1, 5); \ + t0 = __msa_subv_h((v8i16)src0, t0); \ + t1 = __msa_subv_h((v8i16)src1, t1); \ + t4 = __msa_srli_w((v4i32)src0, 16); \ + t5 = __msa_srli_w((v4i32)src1, 16); \ + DOTP_SB2_SH(t4, t5, c1, c1, t2, t3); \ + SRAI_H2_SH(t2, t3, 5); \ + SUB2(t0, t2, t1, t3, t0, t1); \ + VSHF_B2_UB(src0, t0, src1, t1, mask1, mask1, dst0, dst1); \ +} while (0) + +#define TRANSFORM_COLOR_4(src, dst, c0, c1, mask0, mask1) do { \ + const v16i8 g0 = VSHF_SB(src, src, mask0); \ + v8i16 t0 = __msa_dotp_s_h(c0, g0); \ + v8i16 t1; \ + v4i32 t2; \ + t0 = SRAI_H(t0, 5); \ + t0 = __msa_subv_h((v8i16)src, t0); \ + t2 = __msa_srli_w((v4i32)src, 16); \ + t1 = __msa_dotp_s_h(c1, (v16i8)t2); \ + t1 = SRAI_H(t1, 5); \ + t0 = t0 - t1; \ + dst = VSHF_UB(src, t0, mask1); \ +} while (0) + +static void TransformColor(const VP8LMultipliers* const m, uint32_t* data, + int num_pixels) { + v16u8 src0, dst0; + const v16i8 g2br = (v16i8)__msa_fill_w(m->green_to_blue_ | + (m->green_to_red_ << 16)); + const v16i8 r2b = (v16i8)__msa_fill_w(m->red_to_blue_); + const v16u8 mask0 = { 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, + 13, 255, 13, 255 }; + const v16u8 mask1 = { 16, 1, 18, 3, 20, 5, 22, 7, 24, 9, 26, 11, + 28, 13, 30, 15 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1; + LD_UB2(data, 4, src0, src1); + TRANSFORM_COLOR_8(src0, src1, dst0, dst1, g2br, r2b, mask0, mask1); + ST_UB2(dst0, dst1, data, 4); + data += 8; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(data); + TRANSFORM_COLOR_4(src0, dst0, g2br, r2b, mask0, mask1); + ST_UB(dst0, data); + data += 4; + num_pixels -= 4; + } + if (num_pixels > 0) { + src0 = LD_UB(data); + TRANSFORM_COLOR_4(src0, dst0, g2br, r2b, mask0, mask1); + if (num_pixels == 3) { + const uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); + const uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 2); + SD(pix_d, data + 0); + SW(pix_w, data + 2); + } else if (num_pixels == 2) { + const uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); + SD(pix_d, data); + } else { + const uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 0); + SW(pix_w, data); + } + } + } +} + +static void SubtractGreenFromBlueAndRed(uint32_t* argb_data, int num_pixels) { + int i; + uint8_t* ptemp_data = (uint8_t*)argb_data; + v16u8 src0, dst0, tmp0; + const v16u8 mask = { 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, + 13, 255, 13, 255 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1, tmp1; + LD_UB2(ptemp_data, 16, src0, src1); + VSHF_B2_UB(src0, src1, src1, src0, mask, mask, tmp0, tmp1); + SUB2(src0, tmp0, src1, tmp1, dst0, dst1); + ST_UB2(dst0, dst1, ptemp_data, 16); + ptemp_data += 8 * 4; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(ptemp_data); + tmp0 = VSHF_UB(src0, src0, mask); + dst0 = src0 - tmp0; + ST_UB(dst0, ptemp_data); + ptemp_data += 4 * 4; + num_pixels -= 4; + } + for (i = 0; i < num_pixels; i++) { + const uint8_t b = ptemp_data[0]; + const uint8_t g = ptemp_data[1]; + const uint8_t r = ptemp_data[2]; + ptemp_data[0] = (b - g) & 0xff; + ptemp_data[2] = (r - g) & 0xff; + ptemp_data += 4; + } + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitMSA(void) { + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed; + VP8LTransformColor = TransformColor; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8LEncDspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/3rdparty/libwebp/dsp/lossless_enc_neon.c b/3rdparty/libwebp/dsp/lossless_enc_neon.c new file mode 100644 index 0000000000..4c56f2594b --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_enc_neon.c @@ -0,0 +1,143 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON variant of methods for lossless encoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_NEON) + +#include + +#include "./lossless.h" +#include "./neon.h" + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +// vtbl?_u8 are marked unavailable for iOS arm64 with Xcode < 6.3, use +// non-standard versions there. +#if defined(__APPLE__) && defined(__aarch64__) && \ + defined(__apple_build_version__) && (__apple_build_version__< 6020037) +#define USE_VTBLQ +#endif + +#ifdef USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[16] = { + 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255 +}; + +static WEBP_INLINE uint8x16_t DoGreenShuffle(const uint8x16_t argb, + const uint8x16_t shuffle) { + return vcombine_u8(vtbl1q_u8(argb, vget_low_u8(shuffle)), + vtbl1q_u8(argb, vget_high_u8(shuffle))); +} +#else // !USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[8] = { 1, 255, 1, 255, 5, 255, 5, 255 }; + +static WEBP_INLINE uint8x16_t DoGreenShuffle(const uint8x16_t argb, + const uint8x8_t shuffle) { + return vcombine_u8(vtbl1_u8(vget_low_u8(argb), shuffle), + vtbl1_u8(vget_high_u8(argb), shuffle)); +} +#endif // USE_VTBLQ + +static void SubtractGreenFromBlueAndRed(uint32_t* argb_data, int num_pixels) { + const uint32_t* const end = argb_data + (num_pixels & ~3); +#ifdef USE_VTBLQ + const uint8x16_t shuffle = vld1q_u8(kGreenShuffle); +#else + const uint8x8_t shuffle = vld1_u8(kGreenShuffle); +#endif + for (; argb_data < end; argb_data += 4) { + const uint8x16_t argb = vld1q_u8((uint8_t*)argb_data); + const uint8x16_t greens = DoGreenShuffle(argb, shuffle); + vst1q_u8((uint8_t*)argb_data, vsubq_u8(argb, greens)); + } + // fallthrough and finish off with plain-C + VP8LSubtractGreenFromBlueAndRed_C(argb_data, num_pixels & 3); +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColor(const VP8LMultipliers* const m, + uint32_t* argb_data, int num_pixels) { + // sign-extended multiplying constants, pre-shifted by 6. +#define CST(X) (((int16_t)(m->X << 8)) >> 6) + const int16_t rb[8] = { + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_) + }; + const int16x8_t mults_rb = vld1q_s16(rb); + const int16_t b2[8] = { + 0, CST(red_to_blue_), 0, CST(red_to_blue_), + 0, CST(red_to_blue_), 0, CST(red_to_blue_), + }; + const int16x8_t mults_b2 = vld1q_s16(b2); +#undef CST +#ifdef USE_VTBLQ + static const uint8_t kg0g0[16] = { + 255, 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13 + }; + const uint8x16_t shuffle = vld1q_u8(kg0g0); +#else + static const uint8_t k0g0g[8] = { 255, 1, 255, 1, 255, 5, 255, 5 }; + const uint8x8_t shuffle = vld1_u8(k0g0g); +#endif + const uint32x4_t mask_rb = vdupq_n_u32(0x00ff00ffu); // red-blue masks + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t in = vld1q_u8((uint8_t*)(argb_data + i)); + // 0 g 0 g + const uint8x16_t greens = DoGreenShuffle(in, shuffle); + // x dr x db1 + const int16x8_t A = vqdmulhq_s16(vreinterpretq_s16_u8(greens), mults_rb); + // r 0 b 0 + const int16x8_t B = vshlq_n_s16(vreinterpretq_s16_u8(in), 8); + // x db2 0 0 + const int16x8_t C = vqdmulhq_s16(B, mults_b2); + // 0 0 x db2 + const uint32x4_t D = vshrq_n_u32(vreinterpretq_u32_s16(C), 16); + // x dr x db + const int8x16_t E = vaddq_s8(vreinterpretq_s8_u32(D), + vreinterpretq_s8_s16(A)); + // 0 dr 0 db + const uint32x4_t F = vandq_u32(vreinterpretq_u32_s8(E), mask_rb); + const int8x16_t out = vsubq_s8(vreinterpretq_s8_u8(in), + vreinterpretq_s8_u32(F)); + vst1q_s8((int8_t*)(argb_data + i), out); + } + // fallthrough and finish off with plain-C + VP8LTransformColor_C(m, argb_data + i, num_pixels - i); +} + +#undef USE_VTBLQ + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitNEON(void) { + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed; + VP8LTransformColor = TransformColor; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8LEncDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/3rdparty/libwebp/dsp/lossless_enc_sse2.c b/3rdparty/libwebp/dsp/lossless_enc_sse2.c new file mode 100644 index 0000000000..8ad85d94d7 --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_enc_sse2.c @@ -0,0 +1,711 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 variant of methods for lossless encoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE2) +#include +#include +#include "./lossless.h" +#include "./common_sse2.h" +#include "./lossless_common.h" + +// For sign-extended multiplying constants, pre-shifted by 5: +#define CST_5b(X) (((int16_t)((uint16_t)X << 8)) >> 5) + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +static void SubtractGreenFromBlueAndRed(uint32_t* argb_data, int num_pixels) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((__m128i*)&argb_data[i]); // argb + const __m128i A = _mm_srli_epi16(in, 8); // 0 a 0 g + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // 0g0g + const __m128i out = _mm_sub_epi8(in, C); + _mm_storeu_si128((__m128i*)&argb_data[i], out); + } + // fallthrough and finish off with plain-C + if (i != num_pixels) { + VP8LSubtractGreenFromBlueAndRed_C(argb_data + i, num_pixels - i); + } +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColor(const VP8LMultipliers* const m, + uint32_t* argb_data, int num_pixels) { + const __m128i mults_rb = _mm_set_epi16( + CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_), + CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_), + CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_), + CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_)); + const __m128i mults_b2 = _mm_set_epi16( + CST_5b(m->red_to_blue_), 0, CST_5b(m->red_to_blue_), 0, + CST_5b(m->red_to_blue_), 0, CST_5b(m->red_to_blue_), 0); + const __m128i mask_ag = _mm_set1_epi32(0xff00ff00); // alpha-green masks + const __m128i mask_rb = _mm_set1_epi32(0x00ff00ff); // red-blue masks + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((__m128i*)&argb_data[i]); // argb + const __m128i A = _mm_and_si128(in, mask_ag); // a 0 g 0 + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // g0g0 + const __m128i D = _mm_mulhi_epi16(C, mults_rb); // x dr x db1 + const __m128i E = _mm_slli_epi16(in, 8); // r 0 b 0 + const __m128i F = _mm_mulhi_epi16(E, mults_b2); // x db2 0 0 + const __m128i G = _mm_srli_epi32(F, 16); // 0 0 x db2 + const __m128i H = _mm_add_epi8(G, D); // x dr x db + const __m128i I = _mm_and_si128(H, mask_rb); // 0 dr 0 db + const __m128i out = _mm_sub_epi8(in, I); + _mm_storeu_si128((__m128i*)&argb_data[i], out); + } + // fallthrough and finish off with plain-C + if (i != num_pixels) { + VP8LTransformColor_C(m, argb_data + i, num_pixels - i); + } +} + +//------------------------------------------------------------------------------ +#define SPAN 8 +static void CollectColorBlueTransforms(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]) { + const __m128i mults_r = _mm_set_epi16( + CST_5b(red_to_blue), 0, CST_5b(red_to_blue), 0, + CST_5b(red_to_blue), 0, CST_5b(red_to_blue), 0); + const __m128i mults_g = _mm_set_epi16( + 0, CST_5b(green_to_blue), 0, CST_5b(green_to_blue), + 0, CST_5b(green_to_blue), 0, CST_5b(green_to_blue)); + const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask + const __m128i mask_b = _mm_set1_epi32(0x0000ff); // blue mask + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + int i, x; + for (x = 0; x + SPAN <= tile_width; x += SPAN) { + uint16_t values[SPAN]; + const __m128i in0 = _mm_loadu_si128((__m128i*)&src[x + 0]); + const __m128i in1 = _mm_loadu_si128((__m128i*)&src[x + SPAN / 2]); + const __m128i A0 = _mm_slli_epi16(in0, 8); // r 0 | b 0 + const __m128i A1 = _mm_slli_epi16(in1, 8); + const __m128i B0 = _mm_and_si128(in0, mask_g); // 0 0 | g 0 + const __m128i B1 = _mm_and_si128(in1, mask_g); + const __m128i C0 = _mm_mulhi_epi16(A0, mults_r); // x db | 0 0 + const __m128i C1 = _mm_mulhi_epi16(A1, mults_r); + const __m128i D0 = _mm_mulhi_epi16(B0, mults_g); // 0 0 | x db + const __m128i D1 = _mm_mulhi_epi16(B1, mults_g); + const __m128i E0 = _mm_sub_epi8(in0, D0); // x x | x b' + const __m128i E1 = _mm_sub_epi8(in1, D1); + const __m128i F0 = _mm_srli_epi32(C0, 16); // 0 0 | x db + const __m128i F1 = _mm_srli_epi32(C1, 16); + const __m128i G0 = _mm_sub_epi8(E0, F0); // 0 0 | x b' + const __m128i G1 = _mm_sub_epi8(E1, F1); + const __m128i H0 = _mm_and_si128(G0, mask_b); // 0 0 | 0 b + const __m128i H1 = _mm_and_si128(G1, mask_b); + const __m128i I = _mm_packs_epi32(H0, H1); // 0 b' | 0 b' + _mm_storeu_si128((__m128i*)values, I); + for (i = 0; i < SPAN; ++i) ++histo[values[i]]; + } + } + { + const int left_over = tile_width & (SPAN - 1); + if (left_over > 0) { + VP8LCollectColorBlueTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, + green_to_blue, red_to_blue, histo); + } + } +} + +static void CollectColorRedTransforms(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]) { + const __m128i mults_g = _mm_set_epi16( + 0, CST_5b(green_to_red), 0, CST_5b(green_to_red), + 0, CST_5b(green_to_red), 0, CST_5b(green_to_red)); + const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask + const __m128i mask = _mm_set1_epi32(0xff); + + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + int i, x; + for (x = 0; x + SPAN <= tile_width; x += SPAN) { + uint16_t values[SPAN]; + const __m128i in0 = _mm_loadu_si128((__m128i*)&src[x + 0]); + const __m128i in1 = _mm_loadu_si128((__m128i*)&src[x + SPAN / 2]); + const __m128i A0 = _mm_and_si128(in0, mask_g); // 0 0 | g 0 + const __m128i A1 = _mm_and_si128(in1, mask_g); + const __m128i B0 = _mm_srli_epi32(in0, 16); // 0 0 | x r + const __m128i B1 = _mm_srli_epi32(in1, 16); + const __m128i C0 = _mm_mulhi_epi16(A0, mults_g); // 0 0 | x dr + const __m128i C1 = _mm_mulhi_epi16(A1, mults_g); + const __m128i E0 = _mm_sub_epi8(B0, C0); // x x | x r' + const __m128i E1 = _mm_sub_epi8(B1, C1); + const __m128i F0 = _mm_and_si128(E0, mask); // 0 0 | 0 r' + const __m128i F1 = _mm_and_si128(E1, mask); + const __m128i I = _mm_packs_epi32(F0, F1); + _mm_storeu_si128((__m128i*)values, I); + for (i = 0; i < SPAN; ++i) ++histo[values[i]]; + } + } + { + const int left_over = tile_width & (SPAN - 1); + if (left_over > 0) { + VP8LCollectColorRedTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, + green_to_red, histo); + } + } +} +#undef SPAN + +//------------------------------------------------------------------------------ + +#define LINE_SIZE 16 // 8 or 16 +static void AddVector(const uint32_t* a, const uint32_t* b, uint32_t* out, + int size) { + int i; + assert(size % LINE_SIZE == 0); + for (i = 0; i < size; i += LINE_SIZE) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)&a[i + 0]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&a[i + 4]); +#if (LINE_SIZE == 16) + const __m128i a2 = _mm_loadu_si128((const __m128i*)&a[i + 8]); + const __m128i a3 = _mm_loadu_si128((const __m128i*)&a[i + 12]); +#endif + const __m128i b0 = _mm_loadu_si128((const __m128i*)&b[i + 0]); + const __m128i b1 = _mm_loadu_si128((const __m128i*)&b[i + 4]); +#if (LINE_SIZE == 16) + const __m128i b2 = _mm_loadu_si128((const __m128i*)&b[i + 8]); + const __m128i b3 = _mm_loadu_si128((const __m128i*)&b[i + 12]); +#endif + _mm_storeu_si128((__m128i*)&out[i + 0], _mm_add_epi32(a0, b0)); + _mm_storeu_si128((__m128i*)&out[i + 4], _mm_add_epi32(a1, b1)); +#if (LINE_SIZE == 16) + _mm_storeu_si128((__m128i*)&out[i + 8], _mm_add_epi32(a2, b2)); + _mm_storeu_si128((__m128i*)&out[i + 12], _mm_add_epi32(a3, b3)); +#endif + } +} + +static void AddVectorEq(const uint32_t* a, uint32_t* out, int size) { + int i; + assert(size % LINE_SIZE == 0); + for (i = 0; i < size; i += LINE_SIZE) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)&a[i + 0]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&a[i + 4]); +#if (LINE_SIZE == 16) + const __m128i a2 = _mm_loadu_si128((const __m128i*)&a[i + 8]); + const __m128i a3 = _mm_loadu_si128((const __m128i*)&a[i + 12]); +#endif + const __m128i b0 = _mm_loadu_si128((const __m128i*)&out[i + 0]); + const __m128i b1 = _mm_loadu_si128((const __m128i*)&out[i + 4]); +#if (LINE_SIZE == 16) + const __m128i b2 = _mm_loadu_si128((const __m128i*)&out[i + 8]); + const __m128i b3 = _mm_loadu_si128((const __m128i*)&out[i + 12]); +#endif + _mm_storeu_si128((__m128i*)&out[i + 0], _mm_add_epi32(a0, b0)); + _mm_storeu_si128((__m128i*)&out[i + 4], _mm_add_epi32(a1, b1)); +#if (LINE_SIZE == 16) + _mm_storeu_si128((__m128i*)&out[i + 8], _mm_add_epi32(a2, b2)); + _mm_storeu_si128((__m128i*)&out[i + 12], _mm_add_epi32(a3, b3)); +#endif + } +} +#undef LINE_SIZE + +// Note we are adding uint32_t's as *signed* int32's (using _mm_add_epi32). But +// that's ok since the histogram values are less than 1<<28 (max picture size). +static void HistogramAdd(const VP8LHistogram* const a, + const VP8LHistogram* const b, + VP8LHistogram* const out) { + int i; + const int literal_size = VP8LHistogramNumCodes(a->palette_code_bits_); + assert(a->palette_code_bits_ == b->palette_code_bits_); + if (b != out) { + AddVector(a->literal_, b->literal_, out->literal_, NUM_LITERAL_CODES); + AddVector(a->red_, b->red_, out->red_, NUM_LITERAL_CODES); + AddVector(a->blue_, b->blue_, out->blue_, NUM_LITERAL_CODES); + AddVector(a->alpha_, b->alpha_, out->alpha_, NUM_LITERAL_CODES); + } else { + AddVectorEq(a->literal_, out->literal_, NUM_LITERAL_CODES); + AddVectorEq(a->red_, out->red_, NUM_LITERAL_CODES); + AddVectorEq(a->blue_, out->blue_, NUM_LITERAL_CODES); + AddVectorEq(a->alpha_, out->alpha_, NUM_LITERAL_CODES); + } + for (i = NUM_LITERAL_CODES; i < literal_size; ++i) { + out->literal_[i] = a->literal_[i] + b->literal_[i]; + } + for (i = 0; i < NUM_DISTANCE_CODES; ++i) { + out->distance_[i] = a->distance_[i] + b->distance_[i]; + } +} + +//------------------------------------------------------------------------------ +// Entropy + +// Checks whether the X or Y contribution is worth computing and adding. +// Used in loop unrolling. +#define ANALYZE_X_OR_Y(x_or_y, j) \ + do { \ + if (x_or_y[i + j] != 0) retval -= VP8LFastSLog2(x_or_y[i + j]); \ + } while (0) + +// Checks whether the X + Y contribution is worth computing and adding. +// Used in loop unrolling. +#define ANALYZE_XY(j) \ + do { \ + if (tmp[j] != 0) { \ + retval -= VP8LFastSLog2(tmp[j]); \ + ANALYZE_X_OR_Y(X, j); \ + } \ + } while (0) + +static float CombinedShannonEntropy(const int X[256], const int Y[256]) { + int i; + double retval = 0.; + int sumX, sumXY; + int32_t tmp[4]; + __m128i zero = _mm_setzero_si128(); + // Sums up X + Y, 4 ints at a time (and will merge it at the end for sumXY). + __m128i sumXY_128 = zero; + __m128i sumX_128 = zero; + + for (i = 0; i < 256; i += 4) { + const __m128i x = _mm_loadu_si128((const __m128i*)(X + i)); + const __m128i y = _mm_loadu_si128((const __m128i*)(Y + i)); + + // Check if any X is non-zero: this actually provides a speedup as X is + // usually sparse. + if (_mm_movemask_epi8(_mm_cmpeq_epi32(x, zero)) != 0xFFFF) { + const __m128i xy_128 = _mm_add_epi32(x, y); + sumXY_128 = _mm_add_epi32(sumXY_128, xy_128); + + sumX_128 = _mm_add_epi32(sumX_128, x); + + // Analyze the different X + Y. + _mm_storeu_si128((__m128i*)tmp, xy_128); + + ANALYZE_XY(0); + ANALYZE_XY(1); + ANALYZE_XY(2); + ANALYZE_XY(3); + } else { + // X is fully 0, so only deal with Y. + sumXY_128 = _mm_add_epi32(sumXY_128, y); + + ANALYZE_X_OR_Y(Y, 0); + ANALYZE_X_OR_Y(Y, 1); + ANALYZE_X_OR_Y(Y, 2); + ANALYZE_X_OR_Y(Y, 3); + } + } + + // Sum up sumX_128 to get sumX. + _mm_storeu_si128((__m128i*)tmp, sumX_128); + sumX = tmp[3] + tmp[2] + tmp[1] + tmp[0]; + + // Sum up sumXY_128 to get sumXY. + _mm_storeu_si128((__m128i*)tmp, sumXY_128); + sumXY = tmp[3] + tmp[2] + tmp[1] + tmp[0]; + + retval += VP8LFastSLog2(sumX) + VP8LFastSLog2(sumXY); + return (float)retval; +} +#undef ANALYZE_X_OR_Y +#undef ANALYZE_XY + +//------------------------------------------------------------------------------ + +static int VectorMismatch(const uint32_t* const array1, + const uint32_t* const array2, int length) { + int match_len; + + if (length >= 12) { + __m128i A0 = _mm_loadu_si128((const __m128i*)&array1[0]); + __m128i A1 = _mm_loadu_si128((const __m128i*)&array2[0]); + match_len = 0; + do { + // Loop unrolling and early load both provide a speedup of 10% for the + // current function. Also, max_limit can be MAX_LENGTH=4096 at most. + const __m128i cmpA = _mm_cmpeq_epi32(A0, A1); + const __m128i B0 = + _mm_loadu_si128((const __m128i*)&array1[match_len + 4]); + const __m128i B1 = + _mm_loadu_si128((const __m128i*)&array2[match_len + 4]); + if (_mm_movemask_epi8(cmpA) != 0xffff) break; + match_len += 4; + + { + const __m128i cmpB = _mm_cmpeq_epi32(B0, B1); + A0 = _mm_loadu_si128((const __m128i*)&array1[match_len + 4]); + A1 = _mm_loadu_si128((const __m128i*)&array2[match_len + 4]); + if (_mm_movemask_epi8(cmpB) != 0xffff) break; + match_len += 4; + } + } while (match_len + 12 < length); + } else { + match_len = 0; + // Unroll the potential first two loops. + if (length >= 4 && + _mm_movemask_epi8(_mm_cmpeq_epi32( + _mm_loadu_si128((const __m128i*)&array1[0]), + _mm_loadu_si128((const __m128i*)&array2[0]))) == 0xffff) { + match_len = 4; + if (length >= 8 && + _mm_movemask_epi8(_mm_cmpeq_epi32( + _mm_loadu_si128((const __m128i*)&array1[4]), + _mm_loadu_si128((const __m128i*)&array2[4]))) == 0xffff) { + match_len = 8; + } + } + } + + while (match_len < length && array1[match_len] == array2[match_len]) { + ++match_len; + } + return match_len; +} + +// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. +static void BundleColorMap_SSE2(const uint8_t* const row, int width, int xbits, + uint32_t* dst) { + int x; + assert(xbits >= 0); + assert(xbits <= 3); + switch (xbits) { + case 0: { + const __m128i ff = _mm_set1_epi16(0xff00); + const __m128i zero = _mm_setzero_si128(); + // Store 0xff000000 | (row[x] << 8). + for (x = 0; x + 16 <= width; x += 16, dst += 16) { + const __m128i in = _mm_loadu_si128((const __m128i*)&row[x]); + const __m128i in_lo = _mm_unpacklo_epi8(zero, in); + const __m128i dst0 = _mm_unpacklo_epi16(in_lo, ff); + const __m128i dst1 = _mm_unpackhi_epi16(in_lo, ff); + const __m128i in_hi = _mm_unpackhi_epi8(zero, in); + const __m128i dst2 = _mm_unpacklo_epi16(in_hi, ff); + const __m128i dst3 = _mm_unpackhi_epi16(in_hi, ff); + _mm_storeu_si128((__m128i*)&dst[0], dst0); + _mm_storeu_si128((__m128i*)&dst[4], dst1); + _mm_storeu_si128((__m128i*)&dst[8], dst2); + _mm_storeu_si128((__m128i*)&dst[12], dst3); + } + break; + } + case 1: { + const __m128i ff = _mm_set1_epi16(0xff00); + const __m128i mul = _mm_set1_epi16(0x110); + for (x = 0; x + 16 <= width; x += 16, dst += 8) { + // 0a0b | (where a/b are 4 bits). + const __m128i in = _mm_loadu_si128((const __m128i*)&row[x]); + const __m128i tmp = _mm_mullo_epi16(in, mul); // aba0 + const __m128i pack = _mm_and_si128(tmp, ff); // ab00 + const __m128i dst0 = _mm_unpacklo_epi16(pack, ff); + const __m128i dst1 = _mm_unpackhi_epi16(pack, ff); + _mm_storeu_si128((__m128i*)&dst[0], dst0); + _mm_storeu_si128((__m128i*)&dst[4], dst1); + } + break; + } + case 2: { + const __m128i mask_or = _mm_set1_epi32(0xff000000); + const __m128i mul_cst = _mm_set1_epi16(0x0104); + const __m128i mask_mul = _mm_set1_epi16(0x0f00); + for (x = 0; x + 16 <= width; x += 16, dst += 4) { + // 000a000b000c000d | (where a/b/c/d are 2 bits). + const __m128i in = _mm_loadu_si128((const __m128i*)&row[x]); + const __m128i mul = _mm_mullo_epi16(in, mul_cst); // 00ab00b000cd00d0 + const __m128i tmp = _mm_and_si128(mul, mask_mul); // 00ab000000cd0000 + const __m128i shift = _mm_srli_epi32(tmp, 12); // 00000000ab000000 + const __m128i pack = _mm_or_si128(shift, tmp); // 00000000abcd0000 + // Convert to 0xff00**00. + const __m128i res = _mm_or_si128(pack, mask_or); + _mm_storeu_si128((__m128i*)dst, res); + } + break; + } + default: { + assert(xbits == 3); + for (x = 0; x + 16 <= width; x += 16, dst += 2) { + // 0000000a00000000b... | (where a/b are 1 bit). + const __m128i in = _mm_loadu_si128((const __m128i*)&row[x]); + const __m128i shift = _mm_slli_epi64(in, 7); + const uint32_t move = _mm_movemask_epi8(shift); + dst[0] = 0xff000000 | ((move & 0xff) << 8); + dst[1] = 0xff000000 | (move & 0xff00); + } + break; + } + } + if (x != width) { + VP8LBundleColorMap_C(row + x, width - x, xbits, dst); + } +} + +//------------------------------------------------------------------------------ +// Batch version of Predictor Transform subtraction + +static WEBP_INLINE void Average2_m128i(const __m128i* const a0, + const __m128i* const a1, + __m128i* const avg) { + // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) + const __m128i ones = _mm_set1_epi8(1); + const __m128i avg1 = _mm_avg_epu8(*a0, *a1); + const __m128i one = _mm_and_si128(_mm_xor_si128(*a0, *a1), ones); + *avg = _mm_sub_epi8(avg1, one); +} + +// Predictor0: ARGB_BLACK. +static void PredictorSub0_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i black = _mm_set1_epi32(ARGB_BLACK); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i res = _mm_sub_epi8(src, black); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[0](in + i, upper + i, num_pixels - i, out + i); + } +} + +#define GENERATE_PREDICTOR_1(X, IN) \ +static void PredictorSub##X##_SSE2(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + const __m128i pred = _mm_loadu_si128((const __m128i*)&(IN)); \ + const __m128i res = _mm_sub_epi8(src, pred); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsSub_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ +} + +GENERATE_PREDICTOR_1(1, in[i - 1]) // Predictor1: L +GENERATE_PREDICTOR_1(2, upper[i]) // Predictor2: T +GENERATE_PREDICTOR_1(3, upper[i + 1]) // Predictor3: TR +GENERATE_PREDICTOR_1(4, upper[i - 1]) // Predictor4: TL +#undef GENERATE_PREDICTOR_1 + +// Predictor5: avg2(avg2(L, TR), T) +static void PredictorSub5_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i L = _mm_loadu_si128((const __m128i*)&in[i - 1]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i TR = _mm_loadu_si128((const __m128i*)&upper[i + 1]); + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + __m128i avg, pred, res; + Average2_m128i(&L, &TR, &avg); + Average2_m128i(&avg, &T, &pred); + res = _mm_sub_epi8(src, pred); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[5](in + i, upper + i, num_pixels - i, out + i); + } +} + +#define GENERATE_PREDICTOR_2(X, A, B) \ +static void PredictorSub##X##_SSE2(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i tA = _mm_loadu_si128((const __m128i*)&(A)); \ + const __m128i tB = _mm_loadu_si128((const __m128i*)&(B)); \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + __m128i pred, res; \ + Average2_m128i(&tA, &tB, &pred); \ + res = _mm_sub_epi8(src, pred); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsSub_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ +} + +GENERATE_PREDICTOR_2(6, in[i - 1], upper[i - 1]) // Predictor6: avg(L, TL) +GENERATE_PREDICTOR_2(7, in[i - 1], upper[i]) // Predictor7: avg(L, T) +GENERATE_PREDICTOR_2(8, upper[i - 1], upper[i]) // Predictor8: avg(TL, T) +GENERATE_PREDICTOR_2(9, upper[i], upper[i + 1]) // Predictor9: average(T, TR) +#undef GENERATE_PREDICTOR_2 + +// Predictor10: avg(avg(L,TL), avg(T, TR)). +static void PredictorSub10_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i L = _mm_loadu_si128((const __m128i*)&in[i - 1]); + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i TR = _mm_loadu_si128((const __m128i*)&upper[i + 1]); + __m128i avgTTR, avgLTL, avg, res; + Average2_m128i(&T, &TR, &avgTTR); + Average2_m128i(&L, &TL, &avgLTL); + Average2_m128i(&avgTTR, &avgLTL, &avg); + res = _mm_sub_epi8(src, avg); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[10](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Predictor11: select. +static void GetSumAbsDiff32(const __m128i* const A, const __m128i* const B, + __m128i* const out) { + // We can unpack with any value on the upper 32 bits, provided it's the same + // on both operands (to that their sum of abs diff is zero). Here we use *A. + const __m128i A_lo = _mm_unpacklo_epi32(*A, *A); + const __m128i B_lo = _mm_unpacklo_epi32(*B, *A); + const __m128i A_hi = _mm_unpackhi_epi32(*A, *A); + const __m128i B_hi = _mm_unpackhi_epi32(*B, *A); + const __m128i s_lo = _mm_sad_epu8(A_lo, B_lo); + const __m128i s_hi = _mm_sad_epu8(A_hi, B_hi); + *out = _mm_packs_epi32(s_lo, s_hi); +} + +static void PredictorSub11_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i L = _mm_loadu_si128((const __m128i*)&in[i - 1]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + __m128i pa, pb; + GetSumAbsDiff32(&T, &TL, &pa); // pa = sum |T-TL| + GetSumAbsDiff32(&L, &TL, &pb); // pb = sum |L-TL| + { + const __m128i mask = _mm_cmpgt_epi32(pb, pa); + const __m128i A = _mm_and_si128(mask, L); + const __m128i B = _mm_andnot_si128(mask, T); + const __m128i pred = _mm_or_si128(A, B); // pred = (L > T)? L : T + const __m128i res = _mm_sub_epi8(src, pred); + _mm_storeu_si128((__m128i*)&out[i], res); + } + } + if (i != num_pixels) { + VP8LPredictorsSub_C[11](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Predictor12: ClampedSubSubtractFull. +static void PredictorSub12_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i zero = _mm_setzero_si128(); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i L = _mm_loadu_si128((const __m128i*)&in[i - 1]); + const __m128i L_lo = _mm_unpacklo_epi8(L, zero); + const __m128i L_hi = _mm_unpackhi_epi8(L, zero); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i T_lo = _mm_unpacklo_epi8(T, zero); + const __m128i T_hi = _mm_unpackhi_epi8(T, zero); + const __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i TL_lo = _mm_unpacklo_epi8(TL, zero); + const __m128i TL_hi = _mm_unpackhi_epi8(TL, zero); + const __m128i diff_lo = _mm_sub_epi16(T_lo, TL_lo); + const __m128i diff_hi = _mm_sub_epi16(T_hi, TL_hi); + const __m128i pred_lo = _mm_add_epi16(L_lo, diff_lo); + const __m128i pred_hi = _mm_add_epi16(L_hi, diff_hi); + const __m128i pred = _mm_packus_epi16(pred_lo, pred_hi); + const __m128i res = _mm_sub_epi8(src, pred); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[12](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Predictors13: ClampedAddSubtractHalf +static void PredictorSub13_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i zero = _mm_setzero_si128(); + for (i = 0; i + 2 <= num_pixels; i += 2) { + // we can only process two pixels at a time + const __m128i L = _mm_loadl_epi64((const __m128i*)&in[i - 1]); + const __m128i src = _mm_loadl_epi64((const __m128i*)&in[i]); + const __m128i T = _mm_loadl_epi64((const __m128i*)&upper[i]); + const __m128i TL = _mm_loadl_epi64((const __m128i*)&upper[i - 1]); + const __m128i L_lo = _mm_unpacklo_epi8(L, zero); + const __m128i T_lo = _mm_unpacklo_epi8(T, zero); + const __m128i TL_lo = _mm_unpacklo_epi8(TL, zero); + const __m128i sum = _mm_add_epi16(T_lo, L_lo); + const __m128i avg = _mm_srli_epi16(sum, 1); + const __m128i A1 = _mm_sub_epi16(avg, TL_lo); + const __m128i bit_fix = _mm_cmpgt_epi16(TL_lo, avg); + const __m128i A2 = _mm_sub_epi16(A1, bit_fix); + const __m128i A3 = _mm_srai_epi16(A2, 1); + const __m128i A4 = _mm_add_epi16(avg, A3); + const __m128i pred = _mm_packus_epi16(A4, A4); + const __m128i res = _mm_sub_epi8(src, pred); + _mm_storel_epi64((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[13](in + i, upper + i, num_pixels - i, out + i); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitSSE2(void) { + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed; + VP8LTransformColor = TransformColor; + VP8LCollectColorBlueTransforms = CollectColorBlueTransforms; + VP8LCollectColorRedTransforms = CollectColorRedTransforms; + VP8LHistogramAdd = HistogramAdd; + VP8LCombinedShannonEntropy = CombinedShannonEntropy; + VP8LVectorMismatch = VectorMismatch; + VP8LBundleColorMap = BundleColorMap_SSE2; + + VP8LPredictorsSub[0] = PredictorSub0_SSE2; + VP8LPredictorsSub[1] = PredictorSub1_SSE2; + VP8LPredictorsSub[2] = PredictorSub2_SSE2; + VP8LPredictorsSub[3] = PredictorSub3_SSE2; + VP8LPredictorsSub[4] = PredictorSub4_SSE2; + VP8LPredictorsSub[5] = PredictorSub5_SSE2; + VP8LPredictorsSub[6] = PredictorSub6_SSE2; + VP8LPredictorsSub[7] = PredictorSub7_SSE2; + VP8LPredictorsSub[8] = PredictorSub8_SSE2; + VP8LPredictorsSub[9] = PredictorSub9_SSE2; + VP8LPredictorsSub[10] = PredictorSub10_SSE2; + VP8LPredictorsSub[11] = PredictorSub11_SSE2; + VP8LPredictorsSub[12] = PredictorSub12_SSE2; + VP8LPredictorsSub[13] = PredictorSub13_SSE2; + VP8LPredictorsSub[14] = PredictorSub0_SSE2; // <- padding security sentinels + VP8LPredictorsSub[15] = PredictorSub0_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8LEncDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/dsp/lossless_enc_sse41.c b/3rdparty/libwebp/dsp/lossless_enc_sse41.c new file mode 100644 index 0000000000..821057ccd4 --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_enc_sse41.c @@ -0,0 +1,53 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4.1 variant of methods for lossless encoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE41) +#include +#include +#include "./lossless.h" + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +static void SubtractGreenFromBlueAndRed(uint32_t* argb_data, int num_pixels) { + int i; + const __m128i kCstShuffle = _mm_set_epi8(-1, 13, -1, 13, -1, 9, -1, 9, + -1, 5, -1, 5, -1, 1, -1, 1); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((__m128i*)&argb_data[i]); + const __m128i in_0g0g = _mm_shuffle_epi8(in, kCstShuffle); + const __m128i out = _mm_sub_epi8(in, in_0g0g); + _mm_storeu_si128((__m128i*)&argb_data[i], out); + } + // fallthrough and finish off with plain-C + if (i != num_pixels) { + VP8LSubtractGreenFromBlueAndRed_C(argb_data + i, num_pixels - i); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitSSE41(void) { + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(VP8LEncDspInitSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/3rdparty/libwebp/dsp/lossless_mips_dsp_r2.c b/3rdparty/libwebp/dsp/lossless_mips_dsp_r2.c new file mode 100644 index 0000000000..2984ce8df7 --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_mips_dsp_r2.c @@ -0,0 +1,689 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transforms and color space conversion methods for lossless decoder. +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "./lossless.h" +#include "./lossless_common.h" + +#define MAP_COLOR_FUNCS(FUNC_NAME, TYPE, GET_INDEX, GET_VALUE) \ +static void FUNC_NAME(const TYPE* src, \ + const uint32_t* const color_map, \ + TYPE* dst, int y_start, int y_end, \ + int width) { \ + int y; \ + for (y = y_start; y < y_end; ++y) { \ + int x; \ + for (x = 0; x < (width >> 2); ++x) { \ + int tmp1, tmp2, tmp3, tmp4; \ + __asm__ volatile ( \ + ".ifc " #TYPE ", uint8_t \n\t" \ + "lbu %[tmp1], 0(%[src]) \n\t" \ + "lbu %[tmp2], 1(%[src]) \n\t" \ + "lbu %[tmp3], 2(%[src]) \n\t" \ + "lbu %[tmp4], 3(%[src]) \n\t" \ + "addiu %[src], %[src], 4 \n\t" \ + ".endif \n\t" \ + ".ifc " #TYPE ", uint32_t \n\t" \ + "lw %[tmp1], 0(%[src]) \n\t" \ + "lw %[tmp2], 4(%[src]) \n\t" \ + "lw %[tmp3], 8(%[src]) \n\t" \ + "lw %[tmp4], 12(%[src]) \n\t" \ + "ext %[tmp1], %[tmp1], 8, 8 \n\t" \ + "ext %[tmp2], %[tmp2], 8, 8 \n\t" \ + "ext %[tmp3], %[tmp3], 8, 8 \n\t" \ + "ext %[tmp4], %[tmp4], 8, 8 \n\t" \ + "addiu %[src], %[src], 16 \n\t" \ + ".endif \n\t" \ + "sll %[tmp1], %[tmp1], 2 \n\t" \ + "sll %[tmp2], %[tmp2], 2 \n\t" \ + "sll %[tmp3], %[tmp3], 2 \n\t" \ + "sll %[tmp4], %[tmp4], 2 \n\t" \ + "lwx %[tmp1], %[tmp1](%[color_map]) \n\t" \ + "lwx %[tmp2], %[tmp2](%[color_map]) \n\t" \ + "lwx %[tmp3], %[tmp3](%[color_map]) \n\t" \ + "lwx %[tmp4], %[tmp4](%[color_map]) \n\t" \ + ".ifc " #TYPE ", uint8_t \n\t" \ + "ext %[tmp1], %[tmp1], 8, 8 \n\t" \ + "ext %[tmp2], %[tmp2], 8, 8 \n\t" \ + "ext %[tmp3], %[tmp3], 8, 8 \n\t" \ + "ext %[tmp4], %[tmp4], 8, 8 \n\t" \ + "sb %[tmp1], 0(%[dst]) \n\t" \ + "sb %[tmp2], 1(%[dst]) \n\t" \ + "sb %[tmp3], 2(%[dst]) \n\t" \ + "sb %[tmp4], 3(%[dst]) \n\t" \ + "addiu %[dst], %[dst], 4 \n\t" \ + ".endif \n\t" \ + ".ifc " #TYPE ", uint32_t \n\t" \ + "sw %[tmp1], 0(%[dst]) \n\t" \ + "sw %[tmp2], 4(%[dst]) \n\t" \ + "sw %[tmp3], 8(%[dst]) \n\t" \ + "sw %[tmp4], 12(%[dst]) \n\t" \ + "addiu %[dst], %[dst], 16 \n\t" \ + ".endif \n\t" \ + : [tmp1]"=&r"(tmp1), [tmp2]"=&r"(tmp2), [tmp3]"=&r"(tmp3), \ + [tmp4]"=&r"(tmp4), [src]"+&r"(src), [dst]"+r"(dst) \ + : [color_map]"r"(color_map) \ + : "memory" \ + ); \ + } \ + for (x = 0; x < (width & 3); ++x) { \ + *dst++ = GET_VALUE(color_map[GET_INDEX(*src++)]); \ + } \ + } \ +} + +MAP_COLOR_FUNCS(MapARGB, uint32_t, VP8GetARGBIndex, VP8GetARGBValue) +MAP_COLOR_FUNCS(MapAlpha, uint8_t, VP8GetAlphaIndex, VP8GetAlphaValue) + +#undef MAP_COLOR_FUNCS + +static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, + uint32_t c2) { + int temp0, temp1, temp2, temp3, temp4, temp5; + __asm__ volatile ( + "preceu.ph.qbr %[temp1], %[c0] \n\t" + "preceu.ph.qbl %[temp2], %[c0] \n\t" + "preceu.ph.qbr %[temp3], %[c1] \n\t" + "preceu.ph.qbl %[temp4], %[c1] \n\t" + "preceu.ph.qbr %[temp5], %[c2] \n\t" + "preceu.ph.qbl %[temp0], %[c2] \n\t" + "subq.ph %[temp3], %[temp3], %[temp5] \n\t" + "subq.ph %[temp4], %[temp4], %[temp0] \n\t" + "addq.ph %[temp1], %[temp1], %[temp3] \n\t" + "addq.ph %[temp2], %[temp2], %[temp4] \n\t" + "shll_s.ph %[temp1], %[temp1], 7 \n\t" + "shll_s.ph %[temp2], %[temp2], 7 \n\t" + "precrqu_s.qb.ph %[temp2], %[temp2], %[temp1] \n\t" + : [temp0]"=r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5) + : [c0]"r"(c0), [c1]"r"(c1), [c2]"r"(c2) + : "memory" + ); + return temp2; +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1, + uint32_t c2) { + int temp0, temp1, temp2, temp3, temp4, temp5; + __asm__ volatile ( + "adduh.qb %[temp5], %[c0], %[c1] \n\t" + "preceu.ph.qbr %[temp3], %[c2] \n\t" + "preceu.ph.qbr %[temp1], %[temp5] \n\t" + "preceu.ph.qbl %[temp2], %[temp5] \n\t" + "preceu.ph.qbl %[temp4], %[c2] \n\t" + "subq.ph %[temp3], %[temp1], %[temp3] \n\t" + "subq.ph %[temp4], %[temp2], %[temp4] \n\t" + "shrl.ph %[temp5], %[temp3], 15 \n\t" + "shrl.ph %[temp0], %[temp4], 15 \n\t" + "addq.ph %[temp3], %[temp3], %[temp5] \n\t" + "addq.ph %[temp4], %[temp0], %[temp4] \n\t" + "shra.ph %[temp3], %[temp3], 1 \n\t" + "shra.ph %[temp4], %[temp4], 1 \n\t" + "addq.ph %[temp1], %[temp1], %[temp3] \n\t" + "addq.ph %[temp2], %[temp2], %[temp4] \n\t" + "shll_s.ph %[temp1], %[temp1], 7 \n\t" + "shll_s.ph %[temp2], %[temp2], 7 \n\t" + "precrqu_s.qb.ph %[temp1], %[temp2], %[temp1] \n\t" + : [temp0]"=r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=r"(temp4), [temp5]"=&r"(temp5) + : [c0]"r"(c0), [c1]"r"(c1), [c2]"r"(c2) + : "memory" + ); + return temp1; +} + +static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) { + int temp0, temp1, temp2, temp3, temp4, temp5; + __asm__ volatile ( + "cmpgdu.lt.qb %[temp1], %[c], %[b] \n\t" + "pick.qb %[temp1], %[b], %[c] \n\t" + "pick.qb %[temp2], %[c], %[b] \n\t" + "cmpgdu.lt.qb %[temp4], %[c], %[a] \n\t" + "pick.qb %[temp4], %[a], %[c] \n\t" + "pick.qb %[temp5], %[c], %[a] \n\t" + "subu.qb %[temp3], %[temp1], %[temp2] \n\t" + "subu.qb %[temp0], %[temp4], %[temp5] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "raddu.w.qb %[temp0], %[temp0] \n\t" + "subu %[temp3], %[temp3], %[temp0] \n\t" + "slti %[temp0], %[temp3], 0x1 \n\t" + "movz %[a], %[b], %[temp0] \n\t" + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp0]"=&r"(temp0), + [a]"+&r"(a) + : [b]"r"(b), [c]"r"(c) + ); + return a; +} + +static WEBP_INLINE uint32_t Average2(uint32_t a0, uint32_t a1) { + __asm__ volatile ( + "adduh.qb %[a0], %[a0], %[a1] \n\t" + : [a0]"+r"(a0) + : [a1]"r"(a1) + ); + return a0; +} + +static WEBP_INLINE uint32_t Average3(uint32_t a0, uint32_t a1, uint32_t a2) { + return Average2(Average2(a0, a2), a1); +} + +static WEBP_INLINE uint32_t Average4(uint32_t a0, uint32_t a1, + uint32_t a2, uint32_t a3) { + return Average2(Average2(a0, a1), Average2(a2, a3)); +} + +static uint32_t Predictor5(uint32_t left, const uint32_t* const top) { + return Average3(left, top[0], top[1]); +} + +static uint32_t Predictor6(uint32_t left, const uint32_t* const top) { + return Average2(left, top[-1]); +} + +static uint32_t Predictor7(uint32_t left, const uint32_t* const top) { + return Average2(left, top[0]); +} + +static uint32_t Predictor8(uint32_t left, const uint32_t* const top) { + (void)left; + return Average2(top[-1], top[0]); +} + +static uint32_t Predictor9(uint32_t left, const uint32_t* const top) { + (void)left; + return Average2(top[0], top[1]); +} + +static uint32_t Predictor10(uint32_t left, const uint32_t* const top) { + return Average4(left, top[-1], top[0], top[1]); +} + +static uint32_t Predictor11(uint32_t left, const uint32_t* const top) { + return Select(top[0], left, top[-1]); +} + +static uint32_t Predictor12(uint32_t left, const uint32_t* const top) { + return ClampedAddSubtractFull(left, top[0], top[-1]); +} + +static uint32_t Predictor13(uint32_t left, const uint32_t* const top) { + return ClampedAddSubtractHalf(left, top[0], top[-1]); +} + +// Add green to blue and red channels (i.e. perform the inverse transform of +// 'subtract green'). +static void AddGreenToBlueAndRed(const uint32_t* src, int num_pixels, + uint32_t* dst) { + uint32_t temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "ext %[temp4], %[temp0], 8, 8 \n\t" + "ext %[temp5], %[temp1], 8, 8 \n\t" + "ext %[temp6], %[temp2], 8, 8 \n\t" + "ext %[temp7], %[temp3], 8, 8 \n\t" + "addiu %[src], %[src], 16 \n\t" + "addiu %[dst], %[dst], 16 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "replv.ph %[temp5], %[temp5] \n\t" + "replv.ph %[temp6], %[temp6] \n\t" + "replv.ph %[temp7], %[temp7] \n\t" + "addu.qb %[temp0], %[temp0], %[temp4] \n\t" + "addu.qb %[temp1], %[temp1], %[temp5] \n\t" + "addu.qb %[temp2], %[temp2], %[temp6] \n\t" + "addu.qb %[temp3], %[temp3], %[temp7] \n\t" + "sw %[temp0], -16(%[dst]) \n\t" + "sw %[temp1], -12(%[dst]) \n\t" + "sw %[temp2], -8(%[dst]) \n\t" + "bne %[src], %[p_loop1_end], 0b \n\t" + " sw %[temp3], -4(%[dst]) \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "addiu %[src], %[src], 4 \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "ext %[temp4], %[temp0], 8, 8 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "addu.qb %[temp0], %[temp0], %[temp4] \n\t" + "bne %[src], %[p_loop2_end], 1b \n\t" + " sw %[temp0], -4(%[dst]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [dst]"+&r"(dst), [src]"+&r"(src), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp6]"=&r"(temp6), + [temp7]"=&r"(temp7) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void TransformColorInverse(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst) { + int temp0, temp1, temp2, temp3, temp4, temp5; + uint32_t argb, argb1, new_red; + const uint32_t G_to_R = m->green_to_red_; + const uint32_t G_to_B = m->green_to_blue_; + const uint32_t R_to_B = m->red_to_blue_; + const uint32_t* const p_loop_end = src + (num_pixels & ~1); + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop_end], 1f \n\t" + " nop \n\t" + "replv.ph %[temp0], %[G_to_R] \n\t" + "replv.ph %[temp1], %[G_to_B] \n\t" + "replv.ph %[temp2], %[R_to_B] \n\t" + "shll.ph %[temp0], %[temp0], 8 \n\t" + "shll.ph %[temp1], %[temp1], 8 \n\t" + "shll.ph %[temp2], %[temp2], 8 \n\t" + "shra.ph %[temp0], %[temp0], 8 \n\t" + "shra.ph %[temp1], %[temp1], 8 \n\t" + "shra.ph %[temp2], %[temp2], 8 \n\t" + "0: \n\t" + "lw %[argb], 0(%[src]) \n\t" + "lw %[argb1], 4(%[src]) \n\t" + "sw %[argb], 0(%[dst]) \n\t" + "sw %[argb1], 4(%[dst]) \n\t" + "addiu %[src], %[src], 8 \n\t" + "addiu %[dst], %[dst], 8 \n\t" + "precrq.qb.ph %[temp3], %[argb], %[argb1] \n\t" + "preceu.ph.qbra %[temp3], %[temp3] \n\t" + "shll.ph %[temp3], %[temp3], 8 \n\t" + "shra.ph %[temp3], %[temp3], 8 \n\t" + "mul.ph %[temp5], %[temp3], %[temp0] \n\t" + "mul.ph %[temp3], %[temp3], %[temp1] \n\t" + "precrq.ph.w %[new_red], %[argb], %[argb1] \n\t" + "ins %[argb1], %[argb], 16, 16 \n\t" + "shra.ph %[temp5], %[temp5], 5 \n\t" + "shra.ph %[temp3], %[temp3], 5 \n\t" + "addu.ph %[new_red], %[new_red], %[temp5] \n\t" + "addu.ph %[argb1], %[argb1], %[temp3] \n\t" + "preceu.ph.qbra %[temp5], %[new_red] \n\t" + "shll.ph %[temp4], %[temp5], 8 \n\t" + "shra.ph %[temp4], %[temp4], 8 \n\t" + "mul.ph %[temp4], %[temp4], %[temp2] \n\t" + "sb %[temp5], -2(%[dst]) \n\t" + "sra %[temp5], %[temp5], 16 \n\t" + "shra.ph %[temp4], %[temp4], 5 \n\t" + "addu.ph %[argb1], %[argb1], %[temp4] \n\t" + "preceu.ph.qbra %[temp3], %[argb1] \n\t" + "sb %[temp5], -6(%[dst]) \n\t" + "sb %[temp3], -4(%[dst]) \n\t" + "sra %[temp3], %[temp3], 16 \n\t" + "bne %[src], %[p_loop_end], 0b \n\t" + " sb %[temp3], -8(%[dst]) \n\t" + "1: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [new_red]"=&r"(new_red), [argb]"=&r"(argb), + [argb1]"=&r"(argb1), [dst]"+&r"(dst), [src]"+&r"(src) + : [G_to_R]"r"(G_to_R), [R_to_B]"r"(R_to_B), + [G_to_B]"r"(G_to_B), [p_loop_end]"r"(p_loop_end) + : "memory", "hi", "lo" + ); + + // Fall-back to C-version for left-overs. + if (num_pixels & 1) VP8LTransformColorInverse_C(m, src, 1, dst); +} + +static void ConvertBGRAToRGB(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "ins %[temp3], %[temp2], 24, 8 \n\t" + "sll %[temp2], %[temp2], 8 \n\t" + "rotr %[temp3], %[temp3], 16 \n\t" + "ins %[temp2], %[temp1], 0, 16 \n\t" + "sll %[temp1], %[temp1], 8 \n\t" + "wsbh %[temp3], %[temp3] \n\t" + "balign %[temp0], %[temp1], 1 \n\t" + "wsbh %[temp2], %[temp2] \n\t" + "wsbh %[temp0], %[temp0] \n\t" + "usw %[temp3], 8(%[dst]) \n\t" + "rotr %[temp0], %[temp0], 16 \n\t" + "usw %[temp2], 4(%[dst]) \n\t" + "addiu %[src], %[src], 16 \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 12 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "addiu %[src], %[src], 4 \n\t" + "wsbh %[temp1], %[temp0] \n\t" + "addiu %[dst], %[dst], 3 \n\t" + "ush %[temp1], -2(%[dst]) \n\t" + "sra %[temp0], %[temp0], 16 \n\t" + "bne %[src], %[p_loop2_end], 1b \n\t" + " sb %[temp0], -3(%[dst]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void ConvertBGRAToRGBA(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "wsbh %[temp0], %[temp0] \n\t" + "wsbh %[temp1], %[temp1] \n\t" + "wsbh %[temp2], %[temp2] \n\t" + "wsbh %[temp3], %[temp3] \n\t" + "addiu %[src], %[src], 16 \n\t" + "balign %[temp0], %[temp0], 1 \n\t" + "balign %[temp1], %[temp1], 1 \n\t" + "balign %[temp2], %[temp2], 1 \n\t" + "balign %[temp3], %[temp3], 1 \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "usw %[temp1], 4(%[dst]) \n\t" + "usw %[temp2], 8(%[dst]) \n\t" + "usw %[temp3], 12(%[dst]) \n\t" + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 16 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "wsbh %[temp0], %[temp0] \n\t" + "addiu %[src], %[src], 4 \n\t" + "balign %[temp0], %[temp0], 1 \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "bne %[src], %[p_loop2_end], 1b \n\t" + " addiu %[dst], %[dst], 4 \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void ConvertBGRAToRGBA4444(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3, temp4, temp5; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "ext %[temp4], %[temp0], 28, 4 \n\t" + "ext %[temp5], %[temp0], 12, 4 \n\t" + "ins %[temp0], %[temp4], 0, 4 \n\t" + "ext %[temp4], %[temp1], 28, 4 \n\t" + "ins %[temp0], %[temp5], 16, 4 \n\t" + "ext %[temp5], %[temp1], 12, 4 \n\t" + "ins %[temp1], %[temp4], 0, 4 \n\t" + "ext %[temp4], %[temp2], 28, 4 \n\t" + "ins %[temp1], %[temp5], 16, 4 \n\t" + "ext %[temp5], %[temp2], 12, 4 \n\t" + "ins %[temp2], %[temp4], 0, 4 \n\t" + "ext %[temp4], %[temp3], 28, 4 \n\t" + "ins %[temp2], %[temp5], 16, 4 \n\t" + "ext %[temp5], %[temp3], 12, 4 \n\t" + "ins %[temp3], %[temp4], 0, 4 \n\t" + "precr.qb.ph %[temp1], %[temp1], %[temp0] \n\t" + "ins %[temp3], %[temp5], 16, 4 \n\t" + "addiu %[src], %[src], 16 \n\t" + "precr.qb.ph %[temp3], %[temp3], %[temp2] \n\t" +#ifdef WEBP_SWAP_16BIT_CSP + "usw %[temp1], 0(%[dst]) \n\t" + "usw %[temp3], 4(%[dst]) \n\t" +#else + "wsbh %[temp1], %[temp1] \n\t" + "wsbh %[temp3], %[temp3] \n\t" + "usw %[temp1], 0(%[dst]) \n\t" + "usw %[temp3], 4(%[dst]) \n\t" +#endif + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 8 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "ext %[temp4], %[temp0], 28, 4 \n\t" + "ext %[temp5], %[temp0], 12, 4 \n\t" + "ins %[temp0], %[temp4], 0, 4 \n\t" + "ins %[temp0], %[temp5], 16, 4 \n\t" + "addiu %[src], %[src], 4 \n\t" + "precr.qb.ph %[temp0], %[temp0], %[temp0] \n\t" +#ifdef WEBP_SWAP_16BIT_CSP + "ush %[temp0], 0(%[dst]) \n\t" +#else + "wsbh %[temp0], %[temp0] \n\t" + "ush %[temp0], 0(%[dst]) \n\t" +#endif + "bne %[src], %[p_loop2_end], 1b \n\t" + " addiu %[dst], %[dst], 2 \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void ConvertBGRAToRGB565(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3, temp4, temp5; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "ext %[temp4], %[temp0], 8, 16 \n\t" + "ext %[temp5], %[temp0], 5, 11 \n\t" + "ext %[temp0], %[temp0], 3, 5 \n\t" + "ins %[temp4], %[temp5], 0, 11 \n\t" + "ext %[temp5], %[temp1], 5, 11 \n\t" + "ins %[temp4], %[temp0], 0, 5 \n\t" + "ext %[temp0], %[temp1], 8, 16 \n\t" + "ext %[temp1], %[temp1], 3, 5 \n\t" + "ins %[temp0], %[temp5], 0, 11 \n\t" + "ext %[temp5], %[temp2], 5, 11 \n\t" + "ins %[temp0], %[temp1], 0, 5 \n\t" + "ext %[temp1], %[temp2], 8, 16 \n\t" + "ext %[temp2], %[temp2], 3, 5 \n\t" + "ins %[temp1], %[temp5], 0, 11 \n\t" + "ext %[temp5], %[temp3], 5, 11 \n\t" + "ins %[temp1], %[temp2], 0, 5 \n\t" + "ext %[temp2], %[temp3], 8, 16 \n\t" + "ext %[temp3], %[temp3], 3, 5 \n\t" + "ins %[temp2], %[temp5], 0, 11 \n\t" + "append %[temp0], %[temp4], 16 \n\t" + "ins %[temp2], %[temp3], 0, 5 \n\t" + "addiu %[src], %[src], 16 \n\t" + "append %[temp2], %[temp1], 16 \n\t" +#ifdef WEBP_SWAP_16BIT_CSP + "usw %[temp0], 0(%[dst]) \n\t" + "usw %[temp2], 4(%[dst]) \n\t" +#else + "wsbh %[temp0], %[temp0] \n\t" + "wsbh %[temp2], %[temp2] \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "usw %[temp2], 4(%[dst]) \n\t" +#endif + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 8 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "ext %[temp4], %[temp0], 8, 16 \n\t" + "ext %[temp5], %[temp0], 5, 11 \n\t" + "ext %[temp0], %[temp0], 3, 5 \n\t" + "ins %[temp4], %[temp5], 0, 11 \n\t" + "addiu %[src], %[src], 4 \n\t" + "ins %[temp4], %[temp0], 0, 5 \n\t" +#ifdef WEBP_SWAP_16BIT_CSP + "ush %[temp4], 0(%[dst]) \n\t" +#else + "wsbh %[temp4], %[temp4] \n\t" + "ush %[temp4], 0(%[dst]) \n\t" +#endif + "bne %[src], %[p_loop2_end], 1b \n\t" + " addiu %[dst], %[dst], 2 \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void ConvertBGRAToBGR(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "ins %[temp0], %[temp1], 24, 8 \n\t" + "sra %[temp1], %[temp1], 8 \n\t" + "ins %[temp1], %[temp2], 16, 16 \n\t" + "sll %[temp2], %[temp2], 8 \n\t" + "balign %[temp3], %[temp2], 1 \n\t" + "addiu %[src], %[src], 16 \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "usw %[temp1], 4(%[dst]) \n\t" + "usw %[temp3], 8(%[dst]) \n\t" + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 12 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "addiu %[src], %[src], 4 \n\t" + "addiu %[dst], %[dst], 3 \n\t" + "ush %[temp0], -3(%[dst]) \n\t" + "sra %[temp0], %[temp0], 16 \n\t" + "bne %[src], %[p_loop2_end], 1b \n\t" + " sb %[temp0], -1(%[dst]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitMIPSdspR2(void) { + VP8LMapColor32b = MapARGB; + VP8LMapColor8b = MapAlpha; + VP8LPredictors[5] = Predictor5; + VP8LPredictors[6] = Predictor6; + VP8LPredictors[7] = Predictor7; + VP8LPredictors[8] = Predictor8; + VP8LPredictors[9] = Predictor9; + VP8LPredictors[10] = Predictor10; + VP8LPredictors[11] = Predictor11; + VP8LPredictors[12] = Predictor12; + VP8LPredictors[13] = Predictor13; + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed; + VP8LTransformColorInverse = TransformColorInverse; + VP8LConvertBGRAToRGB = ConvertBGRAToRGB; + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA; + VP8LConvertBGRAToRGBA4444 = ConvertBGRAToRGBA4444; + VP8LConvertBGRAToRGB565 = ConvertBGRAToRGB565; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8LDspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/lossless_msa.c b/3rdparty/libwebp/dsp/lossless_msa.c new file mode 100644 index 0000000000..f6dd5649ac --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_msa.c @@ -0,0 +1,355 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA variant of methods for lossless decoder +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "./lossless.h" +#include "./msa_macro.h" + +//------------------------------------------------------------------------------ +// Colorspace conversion functions + +#define CONVERT16_BGRA_XXX(psrc, pdst, m0, m1, m2) do { \ + v16u8 src0, src1, src2, src3, dst0, dst1, dst2; \ + LD_UB4(psrc, 16, src0, src1, src2, src3); \ + VSHF_B2_UB(src0, src1, src1, src2, m0, m1, dst0, dst1); \ + dst2 = VSHF_UB(src2, src3, m2); \ + ST_UB2(dst0, dst1, pdst, 16); \ + ST_UB(dst2, pdst + 32); \ +} while (0) + +#define CONVERT12_BGRA_XXX(psrc, pdst, m0, m1, m2) do { \ + uint32_t pix_w; \ + v16u8 src0, src1, src2, dst0, dst1, dst2; \ + LD_UB3(psrc, 16, src0, src1, src2); \ + VSHF_B2_UB(src0, src1, src1, src2, m0, m1, dst0, dst1); \ + dst2 = VSHF_UB(src2, src2, m2); \ + ST_UB2(dst0, dst1, pdst, 16); \ + pix_w = __msa_copy_s_w((v4i32)dst2, 0); \ + SW(pix_w, pdst + 32); \ +} while (0) + +#define CONVERT8_BGRA_XXX(psrc, pdst, m0, m1) do { \ + uint64_t pix_d; \ + v16u8 src0, src1, src2, dst0, dst1; \ + LD_UB2(psrc, 16, src0, src1); \ + VSHF_B2_UB(src0, src1, src1, src2, m0, m1, dst0, dst1); \ + ST_UB(dst0, pdst); \ + pix_d = __msa_copy_s_d((v2i64)dst1, 0); \ + SD(pix_d, pdst + 16); \ +} while (0) + +#define CONVERT4_BGRA_XXX(psrc, pdst, m) do { \ + const v16u8 src0 = LD_UB(psrc); \ + const v16u8 dst0 = VSHF_UB(src0, src0, m); \ + uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); \ + uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 2); \ + SD(pix_d, pdst + 0); \ + SW(pix_w, pdst + 8); \ +} while (0) + +#define CONVERT1_BGRA_BGR(psrc, pdst) do { \ + const int32_t b = (psrc)[0]; \ + const int32_t g = (psrc)[1]; \ + const int32_t r = (psrc)[2]; \ + (pdst)[0] = b; \ + (pdst)[1] = g; \ + (pdst)[2] = r; \ +} while (0) + +#define CONVERT1_BGRA_RGB(psrc, pdst) do { \ + const int32_t b = (psrc)[0]; \ + const int32_t g = (psrc)[1]; \ + const int32_t r = (psrc)[2]; \ + (pdst)[0] = r; \ + (pdst)[1] = g; \ + (pdst)[2] = b; \ +} while (0) + +#define TRANSFORM_COLOR_INVERSE_8(src0, src1, dst0, dst1, \ + c0, c1, mask0, mask1) do { \ + v8i16 g0, g1, t0, t1, t2, t3; \ + v4i32 t4, t5; \ + VSHF_B2_SH(src0, src0, src1, src1, mask0, mask0, g0, g1); \ + DOTP_SB2_SH(g0, g1, c0, c0, t0, t1); \ + SRAI_H2_SH(t0, t1, 5); \ + t0 = __msa_addv_h(t0, (v8i16)src0); \ + t1 = __msa_addv_h(t1, (v8i16)src1); \ + t4 = __msa_srli_w((v4i32)t0, 16); \ + t5 = __msa_srli_w((v4i32)t1, 16); \ + DOTP_SB2_SH(t4, t5, c1, c1, t2, t3); \ + SRAI_H2_SH(t2, t3, 5); \ + ADD2(t0, t2, t1, t3, t0, t1); \ + VSHF_B2_UB(src0, t0, src1, t1, mask1, mask1, dst0, dst1); \ +} while (0) + +#define TRANSFORM_COLOR_INVERSE_4(src, dst, c0, c1, mask0, mask1) do { \ + const v16i8 g0 = VSHF_SB(src, src, mask0); \ + v8i16 t0 = __msa_dotp_s_h(c0, g0); \ + v8i16 t1; \ + v4i32 t2; \ + t0 = SRAI_H(t0, 5); \ + t0 = __msa_addv_h(t0, (v8i16)src); \ + t2 = __msa_srli_w((v4i32)t0, 16); \ + t1 = __msa_dotp_s_h(c1, (v16i8)t2); \ + t1 = SRAI_H(t1, 5); \ + t0 = t0 + t1; \ + dst = VSHF_UB(src, t0, mask1); \ +} while (0) + +static void ConvertBGRAToRGBA(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int i; + const uint8_t* ptemp_src = (const uint8_t*)src; + uint8_t* ptemp_dst = (uint8_t*)dst; + v16u8 src0, dst0; + const v16u8 mask = { 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1; + LD_UB2(ptemp_src, 16, src0, src1); + VSHF_B2_UB(src0, src0, src1, src1, mask, mask, dst0, dst1); + ST_UB2(dst0, dst1, ptemp_dst, 16); + ptemp_src += 32; + ptemp_dst += 32; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(ptemp_src); + dst0 = VSHF_UB(src0, src0, mask); + ST_UB(dst0, ptemp_dst); + ptemp_src += 16; + ptemp_dst += 16; + num_pixels -= 4; + } + for (i = 0; i < num_pixels; i++) { + const uint8_t b = ptemp_src[2]; + const uint8_t g = ptemp_src[1]; + const uint8_t r = ptemp_src[0]; + const uint8_t a = ptemp_src[3]; + ptemp_dst[0] = b; + ptemp_dst[1] = g; + ptemp_dst[2] = r; + ptemp_dst[3] = a; + ptemp_src += 4; + ptemp_dst += 4; + } + } +} + +static void ConvertBGRAToBGR(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint8_t* ptemp_src = (const uint8_t*)src; + uint8_t* ptemp_dst = (uint8_t*)dst; + const v16u8 mask0 = { 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, + 16, 17, 18, 20 }; + const v16u8 mask1 = { 5, 6, 8, 9, 10, 12, 13, 14, 16, 17, 18, 20, + 21, 22, 24, 25 }; + const v16u8 mask2 = { 10, 12, 13, 14, 16, 17, 18, 20, 21, 22, 24, 25, + 26, 28, 29, 30 }; + + while (num_pixels >= 16) { + CONVERT16_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1, mask2); + ptemp_src += 64; + ptemp_dst += 48; + num_pixels -= 16; + } + if (num_pixels > 0) { + if (num_pixels >= 12) { + CONVERT12_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1, mask2); + ptemp_src += 48; + ptemp_dst += 36; + num_pixels -= 12; + } else if (num_pixels >= 8) { + CONVERT8_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1); + ptemp_src += 32; + ptemp_dst += 24; + num_pixels -= 8; + } else if (num_pixels >= 4) { + CONVERT4_BGRA_XXX(ptemp_src, ptemp_dst, mask0); + ptemp_src += 16; + ptemp_dst += 12; + num_pixels -= 4; + } + if (num_pixels == 3) { + CONVERT1_BGRA_BGR(ptemp_src + 0, ptemp_dst + 0); + CONVERT1_BGRA_BGR(ptemp_src + 4, ptemp_dst + 3); + CONVERT1_BGRA_BGR(ptemp_src + 8, ptemp_dst + 6); + } else if (num_pixels == 2) { + CONVERT1_BGRA_BGR(ptemp_src + 0, ptemp_dst + 0); + CONVERT1_BGRA_BGR(ptemp_src + 4, ptemp_dst + 3); + } else if (num_pixels == 1) { + CONVERT1_BGRA_BGR(ptemp_src, ptemp_dst); + } + } +} + +static void ConvertBGRAToRGB(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint8_t* ptemp_src = (const uint8_t*)src; + uint8_t* ptemp_dst = (uint8_t*)dst; + const v16u8 mask0 = { 2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, + 18, 17, 16, 22 }; + const v16u8 mask1 = { 5, 4, 10, 9, 8, 14, 13, 12, 18, 17, 16, 22, + 21, 20, 26, 25 }; + const v16u8 mask2 = { 8, 14, 13, 12, 18, 17, 16, 22, 21, 20, 26, 25, + 24, 30, 29, 28 }; + + while (num_pixels >= 16) { + CONVERT16_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1, mask2); + ptemp_src += 64; + ptemp_dst += 48; + num_pixels -= 16; + } + if (num_pixels) { + if (num_pixels >= 12) { + CONVERT12_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1, mask2); + ptemp_src += 48; + ptemp_dst += 36; + num_pixels -= 12; + } else if (num_pixels >= 8) { + CONVERT8_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1); + ptemp_src += 32; + ptemp_dst += 24; + num_pixels -= 8; + } else if (num_pixels >= 4) { + CONVERT4_BGRA_XXX(ptemp_src, ptemp_dst, mask0); + ptemp_src += 16; + ptemp_dst += 12; + num_pixels -= 4; + } + if (num_pixels == 3) { + CONVERT1_BGRA_RGB(ptemp_src + 0, ptemp_dst + 0); + CONVERT1_BGRA_RGB(ptemp_src + 4, ptemp_dst + 3); + CONVERT1_BGRA_RGB(ptemp_src + 8, ptemp_dst + 6); + } else if (num_pixels == 2) { + CONVERT1_BGRA_RGB(ptemp_src + 0, ptemp_dst + 0); + CONVERT1_BGRA_RGB(ptemp_src + 4, ptemp_dst + 3); + } else if (num_pixels == 1) { + CONVERT1_BGRA_RGB(ptemp_src, ptemp_dst); + } + } +} + +static void AddGreenToBlueAndRed(const uint32_t* const src, int num_pixels, + uint32_t* dst) { + int i; + const uint8_t* in = (const uint8_t*)src; + uint8_t* out = (uint8_t*)dst; + v16u8 src0, dst0, tmp0; + const v16u8 mask = { 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, + 13, 255, 13, 255 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1, tmp1; + LD_UB2(in, 16, src0, src1); + VSHF_B2_UB(src0, src1, src1, src0, mask, mask, tmp0, tmp1); + ADD2(src0, tmp0, src1, tmp1, dst0, dst1); + ST_UB2(dst0, dst1, out, 16); + in += 32; + out += 32; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(in); + tmp0 = VSHF_UB(src0, src0, mask); + dst0 = src0 + tmp0; + ST_UB(dst0, out); + in += 16; + out += 16; + num_pixels -= 4; + } + for (i = 0; i < num_pixels; i++) { + const uint8_t b = in[0]; + const uint8_t g = in[1]; + const uint8_t r = in[2]; + out[0] = (b + g) & 0xff; + out[1] = g; + out[2] = (r + g) & 0xff; + out[4] = in[4]; + out += 4; + } + } +} + +static void TransformColorInverse(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst) { + v16u8 src0, dst0; + const v16i8 g2br = (v16i8)__msa_fill_w(m->green_to_blue_ | + (m->green_to_red_ << 16)); + const v16i8 r2b = (v16i8)__msa_fill_w(m->red_to_blue_); + const v16u8 mask0 = { 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, + 13, 255, 13, 255 }; + const v16u8 mask1 = { 16, 1, 18, 3, 20, 5, 22, 7, 24, 9, 26, 11, + 28, 13, 30, 15 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1; + LD_UB2(src, 4, src0, src1); + TRANSFORM_COLOR_INVERSE_8(src0, src1, dst0, dst1, g2br, r2b, mask0, mask1); + ST_UB2(dst0, dst1, dst, 4); + src += 8; + dst += 8; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(src); + TRANSFORM_COLOR_INVERSE_4(src0, dst0, g2br, r2b, mask0, mask1); + ST_UB(dst0, dst); + src += 4; + dst += 4; + num_pixels -= 4; + } + if (num_pixels > 0) { + src0 = LD_UB(src); + TRANSFORM_COLOR_INVERSE_4(src0, dst0, g2br, r2b, mask0, mask1); + if (num_pixels == 3) { + const uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); + const uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 2); + SD(pix_d, dst + 0); + SW(pix_w, dst + 2); + } else if (num_pixels == 2) { + const uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); + SD(pix_d, dst); + } else { + const uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 0); + SW(pix_w, dst); + } + } + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitMSA(void) { + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR; + VP8LConvertBGRAToRGB = ConvertBGRAToRGB; + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed; + VP8LTransformColorInverse = TransformColorInverse; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8LDspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/3rdparty/libwebp/dsp/lossless_neon.c b/3rdparty/libwebp/dsp/lossless_neon.c new file mode 100644 index 0000000000..1145d5fad0 --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_neon.c @@ -0,0 +1,642 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON variant of methods for lossless decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_NEON) + +#include + +#include "./lossless.h" +#include "./neon.h" + +//------------------------------------------------------------------------------ +// Colorspace conversion functions + +#if !defined(WORK_AROUND_GCC) +// gcc 4.6.0 had some trouble (NDK-r9) with this code. We only use it for +// gcc-4.8.x at least. +static void ConvertBGRAToRGBA(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~15); + for (; src < end; src += 16) { + uint8x16x4_t pixel = vld4q_u8((uint8_t*)src); + // swap B and R. (VSWP d0,d2 has no intrinsics equivalent!) + const uint8x16_t tmp = pixel.val[0]; + pixel.val[0] = pixel.val[2]; + pixel.val[2] = tmp; + vst4q_u8(dst, pixel); + dst += 64; + } + VP8LConvertBGRAToRGBA_C(src, num_pixels & 15, dst); // left-overs +} + +static void ConvertBGRAToBGR(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~15); + for (; src < end; src += 16) { + const uint8x16x4_t pixel = vld4q_u8((uint8_t*)src); + const uint8x16x3_t tmp = { { pixel.val[0], pixel.val[1], pixel.val[2] } }; + vst3q_u8(dst, tmp); + dst += 48; + } + VP8LConvertBGRAToBGR_C(src, num_pixels & 15, dst); // left-overs +} + +static void ConvertBGRAToRGB(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~15); + for (; src < end; src += 16) { + const uint8x16x4_t pixel = vld4q_u8((uint8_t*)src); + const uint8x16x3_t tmp = { { pixel.val[2], pixel.val[1], pixel.val[0] } }; + vst3q_u8(dst, tmp); + dst += 48; + } + VP8LConvertBGRAToRGB_C(src, num_pixels & 15, dst); // left-overs +} + +#else // WORK_AROUND_GCC + +// gcc-4.6.0 fallback + +static const uint8_t kRGBAShuffle[8] = { 2, 1, 0, 3, 6, 5, 4, 7 }; + +static void ConvertBGRAToRGBA(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~1); + const uint8x8_t shuffle = vld1_u8(kRGBAShuffle); + for (; src < end; src += 2) { + const uint8x8_t pixels = vld1_u8((uint8_t*)src); + vst1_u8(dst, vtbl1_u8(pixels, shuffle)); + dst += 8; + } + VP8LConvertBGRAToRGBA_C(src, num_pixels & 1, dst); // left-overs +} + +static const uint8_t kBGRShuffle[3][8] = { + { 0, 1, 2, 4, 5, 6, 8, 9 }, + { 10, 12, 13, 14, 16, 17, 18, 20 }, + { 21, 22, 24, 25, 26, 28, 29, 30 } +}; + +static void ConvertBGRAToBGR(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~7); + const uint8x8_t shuffle0 = vld1_u8(kBGRShuffle[0]); + const uint8x8_t shuffle1 = vld1_u8(kBGRShuffle[1]); + const uint8x8_t shuffle2 = vld1_u8(kBGRShuffle[2]); + for (; src < end; src += 8) { + uint8x8x4_t pixels; + INIT_VECTOR4(pixels, + vld1_u8((const uint8_t*)(src + 0)), + vld1_u8((const uint8_t*)(src + 2)), + vld1_u8((const uint8_t*)(src + 4)), + vld1_u8((const uint8_t*)(src + 6))); + vst1_u8(dst + 0, vtbl4_u8(pixels, shuffle0)); + vst1_u8(dst + 8, vtbl4_u8(pixels, shuffle1)); + vst1_u8(dst + 16, vtbl4_u8(pixels, shuffle2)); + dst += 8 * 3; + } + VP8LConvertBGRAToBGR_C(src, num_pixels & 7, dst); // left-overs +} + +static const uint8_t kRGBShuffle[3][8] = { + { 2, 1, 0, 6, 5, 4, 10, 9 }, + { 8, 14, 13, 12, 18, 17, 16, 22 }, + { 21, 20, 26, 25, 24, 30, 29, 28 } +}; + +static void ConvertBGRAToRGB(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~7); + const uint8x8_t shuffle0 = vld1_u8(kRGBShuffle[0]); + const uint8x8_t shuffle1 = vld1_u8(kRGBShuffle[1]); + const uint8x8_t shuffle2 = vld1_u8(kRGBShuffle[2]); + for (; src < end; src += 8) { + uint8x8x4_t pixels; + INIT_VECTOR4(pixels, + vld1_u8((const uint8_t*)(src + 0)), + vld1_u8((const uint8_t*)(src + 2)), + vld1_u8((const uint8_t*)(src + 4)), + vld1_u8((const uint8_t*)(src + 6))); + vst1_u8(dst + 0, vtbl4_u8(pixels, shuffle0)); + vst1_u8(dst + 8, vtbl4_u8(pixels, shuffle1)); + vst1_u8(dst + 16, vtbl4_u8(pixels, shuffle2)); + dst += 8 * 3; + } + VP8LConvertBGRAToRGB_C(src, num_pixels & 7, dst); // left-overs +} + +#endif // !WORK_AROUND_GCC + + +//------------------------------------------------------------------------------ +// Predictor Transform + +#define LOAD_U32_AS_U8(IN) vreinterpret_u8_u32(vdup_n_u32((IN))) +#define LOAD_U32P_AS_U8(IN) vreinterpret_u8_u32(vld1_u32((IN))) +#define LOADQ_U32_AS_U8(IN) vreinterpretq_u8_u32(vdupq_n_u32((IN))) +#define LOADQ_U32P_AS_U8(IN) vreinterpretq_u8_u32(vld1q_u32((IN))) +#define GET_U8_AS_U32(IN) vget_lane_u32(vreinterpret_u32_u8((IN)), 0); +#define GETQ_U8_AS_U32(IN) vgetq_lane_u32(vreinterpretq_u32_u8((IN)), 0); +#define STOREQ_U8_AS_U32P(OUT, IN) vst1q_u32((OUT), vreinterpretq_u32_u8((IN))); +#define ROTATE32_LEFT(L) vextq_u8((L), (L), 12) // D|C|B|A -> C|B|A|D + +static WEBP_INLINE uint8x8_t Average2_u8_NEON(uint32_t a0, uint32_t a1) { + const uint8x8_t A0 = LOAD_U32_AS_U8(a0); + const uint8x8_t A1 = LOAD_U32_AS_U8(a1); + return vhadd_u8(A0, A1); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf_NEON(uint32_t c0, + uint32_t c1, + uint32_t c2) { + const uint8x8_t avg = Average2_u8_NEON(c0, c1); + // Remove one to c2 when bigger than avg. + const uint8x8_t C2 = LOAD_U32_AS_U8(c2); + const uint8x8_t cmp = vcgt_u8(C2, avg); + const uint8x8_t C2_1 = vadd_u8(C2, cmp); + // Compute half of the difference between avg and c2. + const int8x8_t diff_avg = vreinterpret_s8_u8(vhsub_u8(avg, C2_1)); + // Compute the sum with avg and saturate. + const int16x8_t avg_16 = vreinterpretq_s16_u16(vmovl_u8(avg)); + const uint8x8_t res = vqmovun_s16(vaddw_s8(avg_16, diff_avg)); + const uint32_t output = GET_U8_AS_U32(res); + return output; +} + +static WEBP_INLINE uint32_t Average2_NEON(uint32_t a0, uint32_t a1) { + const uint8x8_t avg_u8x8 = Average2_u8_NEON(a0, a1); + const uint32_t avg = GET_U8_AS_U32(avg_u8x8); + return avg; +} + +static WEBP_INLINE uint32_t Average3_NEON(uint32_t a0, uint32_t a1, + uint32_t a2) { + const uint8x8_t avg0 = Average2_u8_NEON(a0, a2); + const uint8x8_t A1 = LOAD_U32_AS_U8(a1); + const uint32_t avg = GET_U8_AS_U32(vhadd_u8(avg0, A1)); + return avg; +} + +static uint32_t Predictor5_NEON(uint32_t left, const uint32_t* const top) { + return Average3_NEON(left, top[0], top[1]); +} +static uint32_t Predictor6_NEON(uint32_t left, const uint32_t* const top) { + return Average2_NEON(left, top[-1]); +} +static uint32_t Predictor7_NEON(uint32_t left, const uint32_t* const top) { + return Average2_NEON(left, top[0]); +} +static uint32_t Predictor13_NEON(uint32_t left, const uint32_t* const top) { + return ClampedAddSubtractHalf_NEON(left, top[0], top[-1]); +} + +// Batch versions of those functions. + +// Predictor0: ARGB_BLACK. +static void PredictorAdd0_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const uint8x16_t black = vreinterpretq_u8_u32(vdupq_n_u32(ARGB_BLACK)); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t res = vaddq_u8(src, black); + STOREQ_U8_AS_U32P(&out[i], res); + } + VP8LPredictorsAdd_C[0](in + i, upper + i, num_pixels - i, out + i); +} + +// Predictor1: left. +static void PredictorAdd1_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const uint8x16_t zero = LOADQ_U32_AS_U8(0); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // a | b | c | d + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + // 0 | a | b | c + const uint8x16_t shift0 = vextq_u8(zero, src, 12); + // a | a + b | b + c | c + d + const uint8x16_t sum0 = vaddq_u8(src, shift0); + // 0 | 0 | a | a + b + const uint8x16_t shift1 = vextq_u8(zero, sum0, 8); + // a | a + b | a + b + c | a + b + c + d + const uint8x16_t sum1 = vaddq_u8(sum0, shift1); + const uint8x16_t prev = LOADQ_U32_AS_U8(out[i - 1]); + const uint8x16_t res = vaddq_u8(sum1, prev); + STOREQ_U8_AS_U32P(&out[i], res); + } + VP8LPredictorsAdd_C[1](in + i, upper + i, num_pixels - i, out + i); +} + +// Macro that adds 32-bit integers from IN using mod 256 arithmetic +// per 8 bit channel. +#define GENERATE_PREDICTOR_1(X, IN) \ +static void PredictorAdd##X##_NEON(const uint32_t* in, \ + const uint32_t* upper, int num_pixels, \ + uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); \ + const uint8x16_t other = LOADQ_U32P_AS_U8(&(IN)); \ + const uint8x16_t res = vaddq_u8(src, other); \ + STOREQ_U8_AS_U32P(&out[i], res); \ + } \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ +} +// Predictor2: Top. +GENERATE_PREDICTOR_1(2, upper[i]) +// Predictor3: Top-right. +GENERATE_PREDICTOR_1(3, upper[i + 1]) +// Predictor4: Top-left. +GENERATE_PREDICTOR_1(4, upper[i - 1]) +#undef GENERATE_PREDICTOR_1 + +// Predictor5: average(average(left, TR), T) +#define DO_PRED5(LANE) do { \ + const uint8x16_t avgLTR = vhaddq_u8(L, TR); \ + const uint8x16_t avg = vhaddq_u8(avgLTR, T); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +static void PredictorAdd5_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i + 0]); + const uint8x16_t TR = LOADQ_U32P_AS_U8(&upper[i + 1]); + DO_PRED5(0); + DO_PRED5(1); + DO_PRED5(2); + DO_PRED5(3); + } + VP8LPredictorsAdd_C[5](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED5 + +#define DO_PRED67(LANE) do { \ + const uint8x16_t avg = vhaddq_u8(L, top); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +// Predictor6: average(left, TL) +static void PredictorAdd6_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t top = LOADQ_U32P_AS_U8(&upper[i - 1]); + DO_PRED67(0); + DO_PRED67(1); + DO_PRED67(2); + DO_PRED67(3); + } + VP8LPredictorsAdd_C[6](in + i, upper + i, num_pixels - i, out + i); +} + +// Predictor7: average(left, T) +static void PredictorAdd7_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t top = LOADQ_U32P_AS_U8(&upper[i]); + DO_PRED67(0); + DO_PRED67(1); + DO_PRED67(2); + DO_PRED67(3); + } + VP8LPredictorsAdd_C[7](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED67 + +#define GENERATE_PREDICTOR_2(X, IN) \ +static void PredictorAdd##X##_NEON(const uint32_t* in, \ + const uint32_t* upper, int num_pixels, \ + uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); \ + const uint8x16_t Tother = LOADQ_U32P_AS_U8(&(IN)); \ + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); \ + const uint8x16_t avg = vhaddq_u8(T, Tother); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + STOREQ_U8_AS_U32P(&out[i], res); \ + } \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ +} +// Predictor8: average TL T. +GENERATE_PREDICTOR_2(8, upper[i - 1]) +// Predictor9: average T TR. +GENERATE_PREDICTOR_2(9, upper[i + 1]) +#undef GENERATE_PREDICTOR_2 + +// Predictor10: average of (average of (L,TL), average of (T, TR)). +#define DO_PRED10(LANE) do { \ + const uint8x16_t avgLTL = vhaddq_u8(L, TL); \ + const uint8x16_t avg = vhaddq_u8(avgTTR, avgLTL); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +static void PredictorAdd10_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const uint8x16_t TR = LOADQ_U32P_AS_U8(&upper[i + 1]); + const uint8x16_t avgTTR = vhaddq_u8(T, TR); + DO_PRED10(0); + DO_PRED10(1); + DO_PRED10(2); + DO_PRED10(3); + } + VP8LPredictorsAdd_C[10](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED10 + +// Predictor11: select. +#define DO_PRED11(LANE) do { \ + const uint8x16_t sumLin = vaddq_u8(L, src); /* in + L */ \ + const uint8x16_t pLTL = vabdq_u8(L, TL); /* |L - TL| */ \ + const uint16x8_t sum_LTL = vpaddlq_u8(pLTL); \ + const uint32x4_t pa = vpaddlq_u16(sum_LTL); \ + const uint32x4_t mask = vcleq_u32(pa, pb); \ + const uint8x16_t res = vbslq_u8(vreinterpretq_u8_u32(mask), sumTin, sumLin); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +static void PredictorAdd11_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + const uint8x16_t pTTL = vabdq_u8(T, TL); // |T - TL| + const uint16x8_t sum_TTL = vpaddlq_u8(pTTL); + const uint32x4_t pb = vpaddlq_u16(sum_TTL); + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t sumTin = vaddq_u8(T, src); // in + T + DO_PRED11(0); + DO_PRED11(1); + DO_PRED11(2); + DO_PRED11(3); + } + VP8LPredictorsAdd_C[11](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED11 + +// Predictor12: ClampedAddSubtractFull. +#define DO_PRED12(DIFF, LANE) do { \ + const uint8x8_t pred = \ + vqmovun_s16(vaddq_s16(vreinterpretq_s16_u16(L), (DIFF))); \ + const uint8x8_t res = \ + vadd_u8(pred, (LANE <= 1) ? vget_low_u8(src) : vget_high_u8(src)); \ + const uint16x8_t res16 = vmovl_u8(res); \ + vst1_lane_u32(&out[i + (LANE)], vreinterpret_u32_u8(res), (LANE) & 1); \ + /* rotate in the left predictor for next iteration */ \ + L = vextq_u16(res16, res16, 4); \ +} while (0) + +static void PredictorAdd12_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint16x8_t L = vmovl_u8(LOAD_U32_AS_U8(out[-1])); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // load four pixels of source + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + // precompute the difference T - TL once for all, stored as s16 + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const int16x8_t diff_lo = + vreinterpretq_s16_u16(vsubl_u8(vget_low_u8(T), vget_low_u8(TL))); + const int16x8_t diff_hi = + vreinterpretq_s16_u16(vsubl_u8(vget_high_u8(T), vget_high_u8(TL))); + // loop over the four reconstructed pixels + DO_PRED12(diff_lo, 0); + DO_PRED12(diff_lo, 1); + DO_PRED12(diff_hi, 2); + DO_PRED12(diff_hi, 3); + } + VP8LPredictorsAdd_C[12](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED12 + +// Predictor13: ClampedAddSubtractHalf +#define DO_PRED13(LANE, LOW_OR_HI) do { \ + const uint8x16_t avg = vhaddq_u8(L, T); \ + const uint8x16_t cmp = vcgtq_u8(TL, avg); \ + const uint8x16_t TL_1 = vaddq_u8(TL, cmp); \ + /* Compute half of the difference between avg and TL'. */ \ + const int8x8_t diff_avg = \ + vreinterpret_s8_u8(LOW_OR_HI(vhsubq_u8(avg, TL_1))); \ + /* Compute the sum with avg and saturate. */ \ + const int16x8_t avg_16 = vreinterpretq_s16_u16(vmovl_u8(LOW_OR_HI(avg))); \ + const uint8x8_t delta = vqmovun_s16(vaddw_s8(avg_16, diff_avg)); \ + const uint8x8_t res = vadd_u8(LOW_OR_HI(src), delta); \ + const uint8x16_t res2 = vcombine_u8(res, res); \ + vst1_lane_u32(&out[i + (LANE)], vreinterpret_u32_u8(res), (LANE) & 1); \ + L = ROTATE32_LEFT(res2); \ +} while (0) + +static void PredictorAdd13_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + DO_PRED13(0, vget_low_u8); + DO_PRED13(1, vget_low_u8); + DO_PRED13(2, vget_high_u8); + DO_PRED13(3, vget_high_u8); + } + VP8LPredictorsAdd_C[13](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED13 + +#undef LOAD_U32_AS_U8 +#undef LOAD_U32P_AS_U8 +#undef LOADQ_U32_AS_U8 +#undef LOADQ_U32P_AS_U8 +#undef GET_U8_AS_U32 +#undef GETQ_U8_AS_U32 +#undef STOREQ_U8_AS_U32P +#undef ROTATE32_LEFT + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +// vtbl?_u8 are marked unavailable for iOS arm64 with Xcode < 6.3, use +// non-standard versions there. +#if defined(__APPLE__) && defined(__aarch64__) && \ + defined(__apple_build_version__) && (__apple_build_version__< 6020037) +#define USE_VTBLQ +#endif + +#ifdef USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[16] = { + 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255 +}; + +static WEBP_INLINE uint8x16_t DoGreenShuffle(const uint8x16_t argb, + const uint8x16_t shuffle) { + return vcombine_u8(vtbl1q_u8(argb, vget_low_u8(shuffle)), + vtbl1q_u8(argb, vget_high_u8(shuffle))); +} +#else // !USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[8] = { 1, 255, 1, 255, 5, 255, 5, 255 }; + +static WEBP_INLINE uint8x16_t DoGreenShuffle(const uint8x16_t argb, + const uint8x8_t shuffle) { + return vcombine_u8(vtbl1_u8(vget_low_u8(argb), shuffle), + vtbl1_u8(vget_high_u8(argb), shuffle)); +} +#endif // USE_VTBLQ + +static void AddGreenToBlueAndRed(const uint32_t* src, int num_pixels, + uint32_t* dst) { + const uint32_t* const end = src + (num_pixels & ~3); +#ifdef USE_VTBLQ + const uint8x16_t shuffle = vld1q_u8(kGreenShuffle); +#else + const uint8x8_t shuffle = vld1_u8(kGreenShuffle); +#endif + for (; src < end; src += 4, dst += 4) { + const uint8x16_t argb = vld1q_u8((const uint8_t*)src); + const uint8x16_t greens = DoGreenShuffle(argb, shuffle); + vst1q_u8((uint8_t*)dst, vaddq_u8(argb, greens)); + } + // fallthrough and finish off with plain-C + VP8LAddGreenToBlueAndRed_C(src, num_pixels & 3, dst); +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColorInverse(const VP8LMultipliers* const m, + const uint32_t* const src, int num_pixels, + uint32_t* dst) { +// sign-extended multiplying constants, pre-shifted by 6. +#define CST(X) (((int16_t)(m->X << 8)) >> 6) + const int16_t rb[8] = { + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_) + }; + const int16x8_t mults_rb = vld1q_s16(rb); + const int16_t b2[8] = { + 0, CST(red_to_blue_), 0, CST(red_to_blue_), + 0, CST(red_to_blue_), 0, CST(red_to_blue_), + }; + const int16x8_t mults_b2 = vld1q_s16(b2); +#undef CST +#ifdef USE_VTBLQ + static const uint8_t kg0g0[16] = { + 255, 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13 + }; + const uint8x16_t shuffle = vld1q_u8(kg0g0); +#else + static const uint8_t k0g0g[8] = { 255, 1, 255, 1, 255, 5, 255, 5 }; + const uint8x8_t shuffle = vld1_u8(k0g0g); +#endif + const uint32x4_t mask_ag = vdupq_n_u32(0xff00ff00u); + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t in = vld1q_u8((const uint8_t*)(src + i)); + const uint32x4_t a0g0 = vandq_u32(vreinterpretq_u32_u8(in), mask_ag); + // 0 g 0 g + const uint8x16_t greens = DoGreenShuffle(in, shuffle); + // x dr x db1 + const int16x8_t A = vqdmulhq_s16(vreinterpretq_s16_u8(greens), mults_rb); + // x r' x b' + const int8x16_t B = vaddq_s8(vreinterpretq_s8_u8(in), + vreinterpretq_s8_s16(A)); + // r' 0 b' 0 + const int16x8_t C = vshlq_n_s16(vreinterpretq_s16_s8(B), 8); + // x db2 0 0 + const int16x8_t D = vqdmulhq_s16(C, mults_b2); + // 0 x db2 0 + const uint32x4_t E = vshrq_n_u32(vreinterpretq_u32_s16(D), 8); + // r' x b'' 0 + const int8x16_t F = vaddq_s8(vreinterpretq_s8_u32(E), + vreinterpretq_s8_s16(C)); + // 0 r' 0 b'' + const uint16x8_t G = vshrq_n_u16(vreinterpretq_u16_s8(F), 8); + const uint32x4_t out = vorrq_u32(vreinterpretq_u32_u16(G), a0g0); + vst1q_u32(dst + i, out); + } + // Fall-back to C-version for left-overs. + VP8LTransformColorInverse_C(m, src + i, num_pixels - i, dst + i); +} + +#undef USE_VTBLQ + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitNEON(void) { + VP8LPredictors[5] = Predictor5_NEON; + VP8LPredictors[6] = Predictor6_NEON; + VP8LPredictors[7] = Predictor7_NEON; + VP8LPredictors[13] = Predictor13_NEON; + + VP8LPredictorsAdd[0] = PredictorAdd0_NEON; + VP8LPredictorsAdd[1] = PredictorAdd1_NEON; + VP8LPredictorsAdd[2] = PredictorAdd2_NEON; + VP8LPredictorsAdd[3] = PredictorAdd3_NEON; + VP8LPredictorsAdd[4] = PredictorAdd4_NEON; + VP8LPredictorsAdd[5] = PredictorAdd5_NEON; + VP8LPredictorsAdd[6] = PredictorAdd6_NEON; + VP8LPredictorsAdd[7] = PredictorAdd7_NEON; + VP8LPredictorsAdd[8] = PredictorAdd8_NEON; + VP8LPredictorsAdd[9] = PredictorAdd9_NEON; + VP8LPredictorsAdd[10] = PredictorAdd10_NEON; + VP8LPredictorsAdd[11] = PredictorAdd11_NEON; + VP8LPredictorsAdd[12] = PredictorAdd12_NEON; + VP8LPredictorsAdd[13] = PredictorAdd13_NEON; + + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR; + VP8LConvertBGRAToRGB = ConvertBGRAToRGB; + + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed; + VP8LTransformColorInverse = TransformColorInverse; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8LDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/3rdparty/libwebp/dsp/lossless_sse2.c b/3rdparty/libwebp/dsp/lossless_sse2.c new file mode 100644 index 0000000000..15aae93869 --- /dev/null +++ b/3rdparty/libwebp/dsp/lossless_sse2.c @@ -0,0 +1,677 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 variant of methods for lossless decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE2) + +#include "./common_sse2.h" +#include "./lossless.h" +#include "./lossless_common.h" +#include +#include + +//------------------------------------------------------------------------------ +// Predictor Transform + +static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, + uint32_t c2) { + const __m128i zero = _mm_setzero_si128(); + const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c0), zero); + const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c1), zero); + const __m128i C2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c2), zero); + const __m128i V1 = _mm_add_epi16(C0, C1); + const __m128i V2 = _mm_sub_epi16(V1, C2); + const __m128i b = _mm_packus_epi16(V2, V2); + const uint32_t output = _mm_cvtsi128_si32(b); + return output; +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1, + uint32_t c2) { + const __m128i zero = _mm_setzero_si128(); + const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c0), zero); + const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c1), zero); + const __m128i B0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c2), zero); + const __m128i avg = _mm_add_epi16(C1, C0); + const __m128i A0 = _mm_srli_epi16(avg, 1); + const __m128i A1 = _mm_sub_epi16(A0, B0); + const __m128i BgtA = _mm_cmpgt_epi16(B0, A0); + const __m128i A2 = _mm_sub_epi16(A1, BgtA); + const __m128i A3 = _mm_srai_epi16(A2, 1); + const __m128i A4 = _mm_add_epi16(A0, A3); + const __m128i A5 = _mm_packus_epi16(A4, A4); + const uint32_t output = _mm_cvtsi128_si32(A5); + return output; +} + +static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) { + int pa_minus_pb; + const __m128i zero = _mm_setzero_si128(); + const __m128i A0 = _mm_cvtsi32_si128(a); + const __m128i B0 = _mm_cvtsi32_si128(b); + const __m128i C0 = _mm_cvtsi32_si128(c); + const __m128i AC0 = _mm_subs_epu8(A0, C0); + const __m128i CA0 = _mm_subs_epu8(C0, A0); + const __m128i BC0 = _mm_subs_epu8(B0, C0); + const __m128i CB0 = _mm_subs_epu8(C0, B0); + const __m128i AC = _mm_or_si128(AC0, CA0); + const __m128i BC = _mm_or_si128(BC0, CB0); + const __m128i pa = _mm_unpacklo_epi8(AC, zero); // |a - c| + const __m128i pb = _mm_unpacklo_epi8(BC, zero); // |b - c| + const __m128i diff = _mm_sub_epi16(pb, pa); + { + int16_t out[8]; + _mm_storeu_si128((__m128i*)out, diff); + pa_minus_pb = out[0] + out[1] + out[2] + out[3]; + } + return (pa_minus_pb <= 0) ? a : b; +} + +static WEBP_INLINE void Average2_m128i(const __m128i* const a0, + const __m128i* const a1, + __m128i* const avg) { + // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) + const __m128i ones = _mm_set1_epi8(1); + const __m128i avg1 = _mm_avg_epu8(*a0, *a1); + const __m128i one = _mm_and_si128(_mm_xor_si128(*a0, *a1), ones); + *avg = _mm_sub_epi8(avg1, one); +} + +static WEBP_INLINE void Average2_uint32(const uint32_t a0, const uint32_t a1, + __m128i* const avg) { + // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) + const __m128i ones = _mm_set1_epi8(1); + const __m128i A0 = _mm_cvtsi32_si128(a0); + const __m128i A1 = _mm_cvtsi32_si128(a1); + const __m128i avg1 = _mm_avg_epu8(A0, A1); + const __m128i one = _mm_and_si128(_mm_xor_si128(A0, A1), ones); + *avg = _mm_sub_epi8(avg1, one); +} + +static WEBP_INLINE __m128i Average2_uint32_16(uint32_t a0, uint32_t a1) { + const __m128i zero = _mm_setzero_si128(); + const __m128i A0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(a0), zero); + const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(a1), zero); + const __m128i sum = _mm_add_epi16(A1, A0); + return _mm_srli_epi16(sum, 1); +} + +static WEBP_INLINE uint32_t Average2(uint32_t a0, uint32_t a1) { + __m128i output; + Average2_uint32(a0, a1, &output); + return _mm_cvtsi128_si32(output); +} + +static WEBP_INLINE uint32_t Average3(uint32_t a0, uint32_t a1, uint32_t a2) { + const __m128i zero = _mm_setzero_si128(); + const __m128i avg1 = Average2_uint32_16(a0, a2); + const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(a1), zero); + const __m128i sum = _mm_add_epi16(avg1, A1); + const __m128i avg2 = _mm_srli_epi16(sum, 1); + const __m128i A2 = _mm_packus_epi16(avg2, avg2); + const uint32_t output = _mm_cvtsi128_si32(A2); + return output; +} + +static WEBP_INLINE uint32_t Average4(uint32_t a0, uint32_t a1, + uint32_t a2, uint32_t a3) { + const __m128i avg1 = Average2_uint32_16(a0, a1); + const __m128i avg2 = Average2_uint32_16(a2, a3); + const __m128i sum = _mm_add_epi16(avg2, avg1); + const __m128i avg3 = _mm_srli_epi16(sum, 1); + const __m128i A0 = _mm_packus_epi16(avg3, avg3); + const uint32_t output = _mm_cvtsi128_si32(A0); + return output; +} + +static uint32_t Predictor5_SSE2(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average3(left, top[0], top[1]); + return pred; +} +static uint32_t Predictor6_SSE2(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average2(left, top[-1]); + return pred; +} +static uint32_t Predictor7_SSE2(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average2(left, top[0]); + return pred; +} +static uint32_t Predictor8_SSE2(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average2(top[-1], top[0]); + (void)left; + return pred; +} +static uint32_t Predictor9_SSE2(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average2(top[0], top[1]); + (void)left; + return pred; +} +static uint32_t Predictor10_SSE2(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Average4(left, top[-1], top[0], top[1]); + return pred; +} +static uint32_t Predictor11_SSE2(uint32_t left, const uint32_t* const top) { + const uint32_t pred = Select(top[0], left, top[-1]); + return pred; +} +static uint32_t Predictor12_SSE2(uint32_t left, const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractFull(left, top[0], top[-1]); + return pred; +} +static uint32_t Predictor13_SSE2(uint32_t left, const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractHalf(left, top[0], top[-1]); + return pred; +} + +// Batch versions of those functions. + +// Predictor0: ARGB_BLACK. +static void PredictorAdd0_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i black = _mm_set1_epi32(ARGB_BLACK); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i res = _mm_add_epi8(src, black); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[0](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Predictor1: left. +static void PredictorAdd1_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + __m128i prev = _mm_set1_epi32(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // a | b | c | d + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + // 0 | a | b | c + const __m128i shift0 = _mm_slli_si128(src, 4); + // a | a + b | b + c | c + d + const __m128i sum0 = _mm_add_epi8(src, shift0); + // 0 | 0 | a | a + b + const __m128i shift1 = _mm_slli_si128(sum0, 8); + // a | a + b | a + b + c | a + b + c + d + const __m128i sum1 = _mm_add_epi8(sum0, shift1); + const __m128i res = _mm_add_epi8(sum1, prev); + _mm_storeu_si128((__m128i*)&out[i], res); + // replicate prev output on the four lanes + prev = _mm_shuffle_epi32(res, (3 << 0) | (3 << 2) | (3 << 4) | (3 << 6)); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[1](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Macro that adds 32-bit integers from IN using mod 256 arithmetic +// per 8 bit channel. +#define GENERATE_PREDICTOR_1(X, IN) \ +static void PredictorAdd##X##_SSE2(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + const __m128i other = _mm_loadu_si128((const __m128i*)&(IN)); \ + const __m128i res = _mm_add_epi8(src, other); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ +} + +// Predictor2: Top. +GENERATE_PREDICTOR_1(2, upper[i]) +// Predictor3: Top-right. +GENERATE_PREDICTOR_1(3, upper[i + 1]) +// Predictor4: Top-left. +GENERATE_PREDICTOR_1(4, upper[i - 1]) +#undef GENERATE_PREDICTOR_1 + +// Due to averages with integers, values cannot be accumulated in parallel for +// predictors 5 to 7. +GENERATE_PREDICTOR_ADD(Predictor5_SSE2, PredictorAdd5_SSE2) +GENERATE_PREDICTOR_ADD(Predictor6_SSE2, PredictorAdd6_SSE2) +GENERATE_PREDICTOR_ADD(Predictor7_SSE2, PredictorAdd7_SSE2) + +#define GENERATE_PREDICTOR_2(X, IN) \ +static void PredictorAdd##X##_SSE2(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i Tother = _mm_loadu_si128((const __m128i*)&(IN)); \ + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + __m128i avg, res; \ + Average2_m128i(&T, &Tother, &avg); \ + res = _mm_add_epi8(avg, src); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ +} +// Predictor8: average TL T. +GENERATE_PREDICTOR_2(8, upper[i - 1]) +// Predictor9: average T TR. +GENERATE_PREDICTOR_2(9, upper[i + 1]) +#undef GENERATE_PREDICTOR_2 + +// Predictor10: average of (average of (L,TL), average of (T, TR)). +static void PredictorAdd10_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i, j; + __m128i L = _mm_cvtsi32_si128(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i TR = _mm_loadu_si128((const __m128i*)&upper[i + 1]); + __m128i avgTTR; + Average2_m128i(&T, &TR, &avgTTR); + for (j = 0; j < 4; ++j) { + __m128i avgLTL, avg; + Average2_m128i(&L, &TL, &avgLTL); + Average2_m128i(&avgTTR, &avgLTL, &avg); + L = _mm_add_epi8(avg, src); + out[i + j] = _mm_cvtsi128_si32(L); + // Rotate the pre-computed values for the next iteration. + avgTTR = _mm_srli_si128(avgTTR, 4); + TL = _mm_srli_si128(TL, 4); + src = _mm_srli_si128(src, 4); + } + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[10](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Predictor11: select. +static void GetSumAbsDiff32(const __m128i* const A, const __m128i* const B, + __m128i* const out) { + // We can unpack with any value on the upper 32 bits, provided it's the same + // on both operands (to that their sum of abs diff is zero). Here we use *A. + const __m128i A_lo = _mm_unpacklo_epi32(*A, *A); + const __m128i B_lo = _mm_unpacklo_epi32(*B, *A); + const __m128i A_hi = _mm_unpackhi_epi32(*A, *A); + const __m128i B_hi = _mm_unpackhi_epi32(*B, *A); + const __m128i s_lo = _mm_sad_epu8(A_lo, B_lo); + const __m128i s_hi = _mm_sad_epu8(A_hi, B_hi); + *out = _mm_packs_epi32(s_lo, s_hi); +} + +static void PredictorAdd11_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i, j; + __m128i L = _mm_cvtsi32_si128(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + __m128i pa; + GetSumAbsDiff32(&T, &TL, &pa); // pa = sum |T-TL| + for (j = 0; j < 4; ++j) { + const __m128i L_lo = _mm_unpacklo_epi32(L, L); + const __m128i TL_lo = _mm_unpacklo_epi32(TL, L); + const __m128i pb = _mm_sad_epu8(L_lo, TL_lo); // pb = sum |L-TL| + const __m128i mask = _mm_cmpgt_epi32(pb, pa); + const __m128i A = _mm_and_si128(mask, L); + const __m128i B = _mm_andnot_si128(mask, T); + const __m128i pred = _mm_or_si128(A, B); // pred = (L > T)? L : T + L = _mm_add_epi8(src, pred); + out[i + j] = _mm_cvtsi128_si32(L); + // Shift the pre-computed value for the next iteration. + T = _mm_srli_si128(T, 4); + TL = _mm_srli_si128(TL, 4); + src = _mm_srli_si128(src, 4); + pa = _mm_srli_si128(pa, 4); + } + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[11](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Predictor12: ClampedAddSubtractFull. +#define DO_PRED12(DIFF, LANE, OUT) \ +do { \ + const __m128i all = _mm_add_epi16(L, (DIFF)); \ + const __m128i alls = _mm_packus_epi16(all, all); \ + const __m128i res = _mm_add_epi8(src, alls); \ + out[i + (OUT)] = _mm_cvtsi128_si32(res); \ + L = _mm_unpacklo_epi8(res, zero); \ + /* Shift the pre-computed value for the next iteration.*/ \ + if (LANE == 0) (DIFF) = _mm_srli_si128((DIFF), 8); \ + src = _mm_srli_si128(src, 4); \ +} while (0) + +static void PredictorAdd12_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i zero = _mm_setzero_si128(); + const __m128i L8 = _mm_cvtsi32_si128(out[-1]); + __m128i L = _mm_unpacklo_epi8(L8, zero); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // Load 4 pixels at a time. + __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i T_lo = _mm_unpacklo_epi8(T, zero); + const __m128i T_hi = _mm_unpackhi_epi8(T, zero); + const __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i TL_lo = _mm_unpacklo_epi8(TL, zero); + const __m128i TL_hi = _mm_unpackhi_epi8(TL, zero); + __m128i diff_lo = _mm_sub_epi16(T_lo, TL_lo); + __m128i diff_hi = _mm_sub_epi16(T_hi, TL_hi); + DO_PRED12(diff_lo, 0, 0); + DO_PRED12(diff_lo, 1, 1); + DO_PRED12(diff_hi, 0, 2); + DO_PRED12(diff_hi, 1, 3); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[12](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED12 + +// Due to averages with integers, values cannot be accumulated in parallel for +// predictors 13. +GENERATE_PREDICTOR_ADD(Predictor13_SSE2, PredictorAdd13_SSE2) + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +static void AddGreenToBlueAndRed(const uint32_t* const src, int num_pixels, + uint32_t* dst) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((const __m128i*)&src[i]); // argb + const __m128i A = _mm_srli_epi16(in, 8); // 0 a 0 g + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // 0g0g + const __m128i out = _mm_add_epi8(in, C); + _mm_storeu_si128((__m128i*)&dst[i], out); + } + // fallthrough and finish off with plain-C + if (i != num_pixels) { + VP8LAddGreenToBlueAndRed_C(src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColorInverse(const VP8LMultipliers* const m, + const uint32_t* const src, int num_pixels, + uint32_t* dst) { +// sign-extended multiplying constants, pre-shifted by 5. +#define CST(X) (((int16_t)(m->X << 8)) >> 5) // sign-extend + const __m128i mults_rb = _mm_set_epi16( + CST(green_to_red_), CST(green_to_blue_), + CST(green_to_red_), CST(green_to_blue_), + CST(green_to_red_), CST(green_to_blue_), + CST(green_to_red_), CST(green_to_blue_)); + const __m128i mults_b2 = _mm_set_epi16( + CST(red_to_blue_), 0, CST(red_to_blue_), 0, + CST(red_to_blue_), 0, CST(red_to_blue_), 0); +#undef CST + const __m128i mask_ag = _mm_set1_epi32(0xff00ff00); // alpha-green masks + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((const __m128i*)&src[i]); // argb + const __m128i A = _mm_and_si128(in, mask_ag); // a 0 g 0 + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // g0g0 + const __m128i D = _mm_mulhi_epi16(C, mults_rb); // x dr x db1 + const __m128i E = _mm_add_epi8(in, D); // x r' x b' + const __m128i F = _mm_slli_epi16(E, 8); // r' 0 b' 0 + const __m128i G = _mm_mulhi_epi16(F, mults_b2); // x db2 0 0 + const __m128i H = _mm_srli_epi32(G, 8); // 0 x db2 0 + const __m128i I = _mm_add_epi8(H, F); // r' x b'' 0 + const __m128i J = _mm_srli_epi16(I, 8); // 0 r' 0 b'' + const __m128i out = _mm_or_si128(J, A); + _mm_storeu_si128((__m128i*)&dst[i], out); + } + // Fall-back to C-version for left-overs. + if (i != num_pixels) { + VP8LTransformColorInverse_C(m, src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ +// Color-space conversion functions + +static void ConvertBGRAToRGB(const uint32_t* src, int num_pixels, + uint8_t* dst) { + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + + while (num_pixels >= 32) { + // Load the BGRA buffers. + __m128i in0 = _mm_loadu_si128(in + 0); + __m128i in1 = _mm_loadu_si128(in + 1); + __m128i in2 = _mm_loadu_si128(in + 2); + __m128i in3 = _mm_loadu_si128(in + 3); + __m128i in4 = _mm_loadu_si128(in + 4); + __m128i in5 = _mm_loadu_si128(in + 5); + __m128i in6 = _mm_loadu_si128(in + 6); + __m128i in7 = _mm_loadu_si128(in + 7); + VP8L32bToPlanar(&in0, &in1, &in2, &in3); + VP8L32bToPlanar(&in4, &in5, &in6, &in7); + // At this points, in1/in5 contains red only, in2/in6 green only ... + // Pack the colors in 24b RGB. + VP8PlanarTo24b(&in1, &in5, &in2, &in6, &in3, &in7); + _mm_storeu_si128(out + 0, in1); + _mm_storeu_si128(out + 1, in5); + _mm_storeu_si128(out + 2, in2); + _mm_storeu_si128(out + 3, in6); + _mm_storeu_si128(out + 4, in3); + _mm_storeu_si128(out + 5, in7); + in += 8; + out += 6; + num_pixels -= 32; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGB_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToRGBA(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + while (num_pixels >= 8) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i v0l = _mm_unpacklo_epi8(bgra0, bgra4); // b0b4g0g4r0r4a0a4... + const __m128i v0h = _mm_unpackhi_epi8(bgra0, bgra4); // b2b6g2g6r2r6a2a6... + const __m128i v1l = _mm_unpacklo_epi8(v0l, v0h); // b0b2b4b6g0g2g4g6... + const __m128i v1h = _mm_unpackhi_epi8(v0l, v0h); // b1b3b5b7g1g3g5g7... + const __m128i v2l = _mm_unpacklo_epi8(v1l, v1h); // b0...b7 | g0...g7 + const __m128i v2h = _mm_unpackhi_epi8(v1l, v1h); // r0...r7 | a0...a7 + const __m128i ga0 = _mm_unpackhi_epi64(v2l, v2h); // g0...g7 | a0...a7 + const __m128i rb0 = _mm_unpacklo_epi64(v2h, v2l); // r0...r7 | b0...b7 + const __m128i rg0 = _mm_unpacklo_epi8(rb0, ga0); // r0g0r1g1 ... r6g6r7g7 + const __m128i ba0 = _mm_unpackhi_epi8(rb0, ga0); // b0a0b1a1 ... b6a6b7a7 + const __m128i rgba0 = _mm_unpacklo_epi16(rg0, ba0); // rgba0|rgba1... + const __m128i rgba4 = _mm_unpackhi_epi16(rg0, ba0); // rgba4|rgba5... + _mm_storeu_si128(out++, rgba0); + _mm_storeu_si128(out++, rgba4); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGBA_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToRGBA4444(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const __m128i mask_0x0f = _mm_set1_epi8(0x0f); + const __m128i mask_0xf0 = _mm_set1_epi8(0xf0); + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + while (num_pixels >= 8) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i v0l = _mm_unpacklo_epi8(bgra0, bgra4); // b0b4g0g4r0r4a0a4... + const __m128i v0h = _mm_unpackhi_epi8(bgra0, bgra4); // b2b6g2g6r2r6a2a6... + const __m128i v1l = _mm_unpacklo_epi8(v0l, v0h); // b0b2b4b6g0g2g4g6... + const __m128i v1h = _mm_unpackhi_epi8(v0l, v0h); // b1b3b5b7g1g3g5g7... + const __m128i v2l = _mm_unpacklo_epi8(v1l, v1h); // b0...b7 | g0...g7 + const __m128i v2h = _mm_unpackhi_epi8(v1l, v1h); // r0...r7 | a0...a7 + const __m128i ga0 = _mm_unpackhi_epi64(v2l, v2h); // g0...g7 | a0...a7 + const __m128i rb0 = _mm_unpacklo_epi64(v2h, v2l); // r0...r7 | b0...b7 + const __m128i ga1 = _mm_srli_epi16(ga0, 4); // g0-|g1-|...|a6-|a7- + const __m128i rb1 = _mm_and_si128(rb0, mask_0xf0); // -r0|-r1|...|-b6|-a7 + const __m128i ga2 = _mm_and_si128(ga1, mask_0x0f); // g0-|g1-|...|a6-|a7- + const __m128i rgba0 = _mm_or_si128(ga2, rb1); // rg0..rg7 | ba0..ba7 + const __m128i rgba1 = _mm_srli_si128(rgba0, 8); // ba0..ba7 | 0 +#ifdef WEBP_SWAP_16BIT_CSP + const __m128i rgba = _mm_unpacklo_epi8(rgba1, rgba0); // barg0...barg7 +#else + const __m128i rgba = _mm_unpacklo_epi8(rgba0, rgba1); // rgba0...rgba7 +#endif + _mm_storeu_si128(out++, rgba); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGBA4444_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToRGB565(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const __m128i mask_0xe0 = _mm_set1_epi8(0xe0); + const __m128i mask_0xf8 = _mm_set1_epi8(0xf8); + const __m128i mask_0x07 = _mm_set1_epi8(0x07); + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + while (num_pixels >= 8) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i v0l = _mm_unpacklo_epi8(bgra0, bgra4); // b0b4g0g4r0r4a0a4... + const __m128i v0h = _mm_unpackhi_epi8(bgra0, bgra4); // b2b6g2g6r2r6a2a6... + const __m128i v1l = _mm_unpacklo_epi8(v0l, v0h); // b0b2b4b6g0g2g4g6... + const __m128i v1h = _mm_unpackhi_epi8(v0l, v0h); // b1b3b5b7g1g3g5g7... + const __m128i v2l = _mm_unpacklo_epi8(v1l, v1h); // b0...b7 | g0...g7 + const __m128i v2h = _mm_unpackhi_epi8(v1l, v1h); // r0...r7 | a0...a7 + const __m128i ga0 = _mm_unpackhi_epi64(v2l, v2h); // g0...g7 | a0...a7 + const __m128i rb0 = _mm_unpacklo_epi64(v2h, v2l); // r0...r7 | b0...b7 + const __m128i rb1 = _mm_and_si128(rb0, mask_0xf8); // -r0..-r7|-b0..-b7 + const __m128i g_lo1 = _mm_srli_epi16(ga0, 5); + const __m128i g_lo2 = _mm_and_si128(g_lo1, mask_0x07); // g0-...g7-|xx (3b) + const __m128i g_hi1 = _mm_slli_epi16(ga0, 3); + const __m128i g_hi2 = _mm_and_si128(g_hi1, mask_0xe0); // -g0...-g7|xx (3b) + const __m128i b0 = _mm_srli_si128(rb1, 8); // -b0...-b7|0 + const __m128i rg1 = _mm_or_si128(rb1, g_lo2); // gr0...gr7|xx + const __m128i b1 = _mm_srli_epi16(b0, 3); + const __m128i gb1 = _mm_or_si128(b1, g_hi2); // bg0...bg7|xx +#ifdef WEBP_SWAP_16BIT_CSP + const __m128i rgba = _mm_unpacklo_epi8(gb1, rg1); // rggb0...rggb7 +#else + const __m128i rgba = _mm_unpacklo_epi8(rg1, gb1); // bgrb0...bgrb7 +#endif + _mm_storeu_si128(out++, rgba); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGB565_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToBGR(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const __m128i mask_l = _mm_set_epi32(0, 0x00ffffff, 0, 0x00ffffff); + const __m128i mask_h = _mm_set_epi32(0x00ffffff, 0, 0x00ffffff, 0); + const __m128i* in = (const __m128i*)src; + const uint8_t* const end = dst + num_pixels * 3; + // the last storel_epi64 below writes 8 bytes starting at offset 18 + while (dst + 26 <= end) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i a0l = _mm_and_si128(bgra0, mask_l); // bgr0|0|bgr0|0 + const __m128i a4l = _mm_and_si128(bgra4, mask_l); // bgr0|0|bgr0|0 + const __m128i a0h = _mm_and_si128(bgra0, mask_h); // 0|bgr0|0|bgr0 + const __m128i a4h = _mm_and_si128(bgra4, mask_h); // 0|bgr0|0|bgr0 + const __m128i b0h = _mm_srli_epi64(a0h, 8); // 000b|gr00|000b|gr00 + const __m128i b4h = _mm_srli_epi64(a4h, 8); // 000b|gr00|000b|gr00 + const __m128i c0 = _mm_or_si128(a0l, b0h); // rgbrgb00|rgbrgb00 + const __m128i c4 = _mm_or_si128(a4l, b4h); // rgbrgb00|rgbrgb00 + const __m128i c2 = _mm_srli_si128(c0, 8); + const __m128i c6 = _mm_srli_si128(c4, 8); + _mm_storel_epi64((__m128i*)(dst + 0), c0); + _mm_storel_epi64((__m128i*)(dst + 6), c2); + _mm_storel_epi64((__m128i*)(dst + 12), c4); + _mm_storel_epi64((__m128i*)(dst + 18), c6); + dst += 24; + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToBGR_C((const uint32_t*)in, num_pixels, dst); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitSSE2(void) { + VP8LPredictors[5] = Predictor5_SSE2; + VP8LPredictors[6] = Predictor6_SSE2; + VP8LPredictors[7] = Predictor7_SSE2; + VP8LPredictors[8] = Predictor8_SSE2; + VP8LPredictors[9] = Predictor9_SSE2; + VP8LPredictors[10] = Predictor10_SSE2; + VP8LPredictors[11] = Predictor11_SSE2; + VP8LPredictors[12] = Predictor12_SSE2; + VP8LPredictors[13] = Predictor13_SSE2; + + VP8LPredictorsAdd[0] = PredictorAdd0_SSE2; + VP8LPredictorsAdd[1] = PredictorAdd1_SSE2; + VP8LPredictorsAdd[2] = PredictorAdd2_SSE2; + VP8LPredictorsAdd[3] = PredictorAdd3_SSE2; + VP8LPredictorsAdd[4] = PredictorAdd4_SSE2; + VP8LPredictorsAdd[5] = PredictorAdd5_SSE2; + VP8LPredictorsAdd[6] = PredictorAdd6_SSE2; + VP8LPredictorsAdd[7] = PredictorAdd7_SSE2; + VP8LPredictorsAdd[8] = PredictorAdd8_SSE2; + VP8LPredictorsAdd[9] = PredictorAdd9_SSE2; + VP8LPredictorsAdd[10] = PredictorAdd10_SSE2; + VP8LPredictorsAdd[11] = PredictorAdd11_SSE2; + VP8LPredictorsAdd[12] = PredictorAdd12_SSE2; + VP8LPredictorsAdd[13] = PredictorAdd13_SSE2; + + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed; + VP8LTransformColorInverse = TransformColorInverse; + + VP8LConvertBGRAToRGB = ConvertBGRAToRGB; + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA; + VP8LConvertBGRAToRGBA4444 = ConvertBGRAToRGBA4444; + VP8LConvertBGRAToRGB565 = ConvertBGRAToRGB565; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8LDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/dsp/mips_macro.h b/3rdparty/libwebp/dsp/mips_macro.h new file mode 100644 index 0000000000..44aba9b71d --- /dev/null +++ b/3rdparty/libwebp/dsp/mips_macro.h @@ -0,0 +1,200 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS common macros + +#ifndef WEBP_DSP_MIPS_MACRO_H_ +#define WEBP_DSP_MIPS_MACRO_H_ + +#if defined(__GNUC__) && defined(__ANDROID__) && LOCAL_GCC_VERSION == 0x409 +#define WORK_AROUND_GCC +#endif + +#define STR(s) #s +#define XSTR(s) STR(s) + +// O0[31..16 | 15..0] = I0[31..16 | 15..0] + I1[31..16 | 15..0] +// O1[31..16 | 15..0] = I0[31..16 | 15..0] - I1[31..16 | 15..0] +// O - output +// I - input (macro doesn't change it) +#define ADD_SUB_HALVES(O0, O1, \ + I0, I1) \ + "addq.ph %[" #O0 "], %[" #I0 "], %[" #I1 "] \n\t" \ + "subq.ph %[" #O1 "], %[" #I0 "], %[" #I1 "] \n\t" + +// O - output +// I - input (macro doesn't change it) +// I[0/1] - offset in bytes +#define LOAD_IN_X2(O0, O1, \ + I0, I1) \ + "lh %[" #O0 "], " #I0 "(%[in]) \n\t" \ + "lh %[" #O1 "], " #I1 "(%[in]) \n\t" + +// I0 - location +// I1..I9 - offsets in bytes +#define LOAD_WITH_OFFSET_X4(O0, O1, O2, O3, \ + I0, I1, I2, I3, I4, I5, I6, I7, I8, I9) \ + "ulw %[" #O0 "], " #I1 "+" XSTR(I9) "*" #I5 "(%[" #I0 "]) \n\t" \ + "ulw %[" #O1 "], " #I2 "+" XSTR(I9) "*" #I6 "(%[" #I0 "]) \n\t" \ + "ulw %[" #O2 "], " #I3 "+" XSTR(I9) "*" #I7 "(%[" #I0 "]) \n\t" \ + "ulw %[" #O3 "], " #I4 "+" XSTR(I9) "*" #I8 "(%[" #I0 "]) \n\t" + +// O - output +// IO - input/output +// I - input (macro doesn't change it) +#define MUL_SHIFT_SUM(O0, O1, O2, O3, O4, O5, O6, O7, \ + IO0, IO1, IO2, IO3, \ + I0, I1, I2, I3, I4, I5, I6, I7) \ + "mul %[" #O0 "], %[" #I0 "], %[kC2] \n\t" \ + "mul %[" #O1 "], %[" #I0 "], %[kC1] \n\t" \ + "mul %[" #O2 "], %[" #I1 "], %[kC2] \n\t" \ + "mul %[" #O3 "], %[" #I1 "], %[kC1] \n\t" \ + "mul %[" #O4 "], %[" #I2 "], %[kC2] \n\t" \ + "mul %[" #O5 "], %[" #I2 "], %[kC1] \n\t" \ + "mul %[" #O6 "], %[" #I3 "], %[kC2] \n\t" \ + "mul %[" #O7 "], %[" #I3 "], %[kC1] \n\t" \ + "sra %[" #O0 "], %[" #O0 "], 16 \n\t" \ + "sra %[" #O1 "], %[" #O1 "], 16 \n\t" \ + "sra %[" #O2 "], %[" #O2 "], 16 \n\t" \ + "sra %[" #O3 "], %[" #O3 "], 16 \n\t" \ + "sra %[" #O4 "], %[" #O4 "], 16 \n\t" \ + "sra %[" #O5 "], %[" #O5 "], 16 \n\t" \ + "sra %[" #O6 "], %[" #O6 "], 16 \n\t" \ + "sra %[" #O7 "], %[" #O7 "], 16 \n\t" \ + "addu %[" #IO0 "], %[" #IO0 "], %[" #I4 "] \n\t" \ + "addu %[" #IO1 "], %[" #IO1 "], %[" #I5 "] \n\t" \ + "subu %[" #IO2 "], %[" #IO2 "], %[" #I6 "] \n\t" \ + "subu %[" #IO3 "], %[" #IO3 "], %[" #I7 "] \n\t" + +// O - output +// I - input (macro doesn't change it) +#define INSERT_HALF_X2(O0, O1, \ + I0, I1) \ + "ins %[" #O0 "], %[" #I0 "], 16, 16 \n\t" \ + "ins %[" #O1 "], %[" #I1 "], 16, 16 \n\t" + +// O - output +// I - input (macro doesn't change it) +#define SRA_16(O0, O1, O2, O3, \ + I0, I1, I2, I3) \ + "sra %[" #O0 "], %[" #I0 "], 16 \n\t" \ + "sra %[" #O1 "], %[" #I1 "], 16 \n\t" \ + "sra %[" #O2 "], %[" #I2 "], 16 \n\t" \ + "sra %[" #O3 "], %[" #I3 "], 16 \n\t" + +// temp0[31..16 | 15..0] = temp8[31..16 | 15..0] + temp12[31..16 | 15..0] +// temp1[31..16 | 15..0] = temp8[31..16 | 15..0] - temp12[31..16 | 15..0] +// temp0[31..16 | 15..0] = temp0[31..16 >> 3 | 15..0 >> 3] +// temp1[31..16 | 15..0] = temp1[31..16 >> 3 | 15..0 >> 3] +// O - output +// I - input (macro doesn't change it) +#define SHIFT_R_SUM_X2(O0, O1, O2, O3, O4, O5, O6, O7, \ + I0, I1, I2, I3, I4, I5, I6, I7) \ + "addq.ph %[" #O0 "], %[" #I0 "], %[" #I4 "] \n\t" \ + "subq.ph %[" #O1 "], %[" #I0 "], %[" #I4 "] \n\t" \ + "addq.ph %[" #O2 "], %[" #I1 "], %[" #I5 "] \n\t" \ + "subq.ph %[" #O3 "], %[" #I1 "], %[" #I5 "] \n\t" \ + "addq.ph %[" #O4 "], %[" #I2 "], %[" #I6 "] \n\t" \ + "subq.ph %[" #O5 "], %[" #I2 "], %[" #I6 "] \n\t" \ + "addq.ph %[" #O6 "], %[" #I3 "], %[" #I7 "] \n\t" \ + "subq.ph %[" #O7 "], %[" #I3 "], %[" #I7 "] \n\t" \ + "shra.ph %[" #O0 "], %[" #O0 "], 3 \n\t" \ + "shra.ph %[" #O1 "], %[" #O1 "], 3 \n\t" \ + "shra.ph %[" #O2 "], %[" #O2 "], 3 \n\t" \ + "shra.ph %[" #O3 "], %[" #O3 "], 3 \n\t" \ + "shra.ph %[" #O4 "], %[" #O4 "], 3 \n\t" \ + "shra.ph %[" #O5 "], %[" #O5 "], 3 \n\t" \ + "shra.ph %[" #O6 "], %[" #O6 "], 3 \n\t" \ + "shra.ph %[" #O7 "], %[" #O7 "], 3 \n\t" + +// precrq.ph.w temp0, temp8, temp2 +// temp0 = temp8[31..16] | temp2[31..16] +// ins temp2, temp8, 16, 16 +// temp2 = temp8[31..16] | temp2[15..0] +// O - output +// IO - input/output +// I - input (macro doesn't change it) +#define PACK_2_HALVES_TO_WORD(O0, O1, O2, O3, \ + IO0, IO1, IO2, IO3, \ + I0, I1, I2, I3) \ + "precrq.ph.w %[" #O0 "], %[" #I0 "], %[" #IO0 "] \n\t" \ + "precrq.ph.w %[" #O1 "], %[" #I1 "], %[" #IO1 "] \n\t" \ + "ins %[" #IO0 "], %[" #I0 "], 16, 16 \n\t" \ + "ins %[" #IO1 "], %[" #I1 "], 16, 16 \n\t" \ + "precrq.ph.w %[" #O2 "], %[" #I2 "], %[" #IO2 "] \n\t" \ + "precrq.ph.w %[" #O3 "], %[" #I3 "], %[" #IO3 "] \n\t" \ + "ins %[" #IO2 "], %[" #I2 "], 16, 16 \n\t" \ + "ins %[" #IO3 "], %[" #I3 "], 16, 16 \n\t" + +// preceu.ph.qbr temp0, temp8 +// temp0 = 0 | 0 | temp8[23..16] | temp8[7..0] +// preceu.ph.qbl temp1, temp8 +// temp1 = temp8[23..16] | temp8[7..0] | 0 | 0 +// O - output +// I - input (macro doesn't change it) +#define CONVERT_2_BYTES_TO_HALF(O0, O1, O2, O3, O4, O5, O6, O7, \ + I0, I1, I2, I3) \ + "preceu.ph.qbr %[" #O0 "], %[" #I0 "] \n\t" \ + "preceu.ph.qbl %[" #O1 "], %[" #I0 "] \n\t" \ + "preceu.ph.qbr %[" #O2 "], %[" #I1 "] \n\t" \ + "preceu.ph.qbl %[" #O3 "], %[" #I1 "] \n\t" \ + "preceu.ph.qbr %[" #O4 "], %[" #I2 "] \n\t" \ + "preceu.ph.qbl %[" #O5 "], %[" #I2 "] \n\t" \ + "preceu.ph.qbr %[" #O6 "], %[" #I3 "] \n\t" \ + "preceu.ph.qbl %[" #O7 "], %[" #I3 "] \n\t" + +// temp0[31..16 | 15..0] = temp0[31..16 | 15..0] + temp8[31..16 | 15..0] +// temp0[31..16 | 15..0] = temp0[31..16 <<(s) 7 | 15..0 <<(s) 7] +// temp1..temp7 same as temp0 +// precrqu_s.qb.ph temp0, temp1, temp0: +// temp0 = temp1[31..24] | temp1[15..8] | temp0[31..24] | temp0[15..8] +// store temp0 to dst +// IO - input/output +// I - input (macro doesn't change it) +#define STORE_SAT_SUM_X2(IO0, IO1, IO2, IO3, IO4, IO5, IO6, IO7, \ + I0, I1, I2, I3, I4, I5, I6, I7, \ + I8, I9, I10, I11, I12, I13) \ + "addq.ph %[" #IO0 "], %[" #IO0 "], %[" #I0 "] \n\t" \ + "addq.ph %[" #IO1 "], %[" #IO1 "], %[" #I1 "] \n\t" \ + "addq.ph %[" #IO2 "], %[" #IO2 "], %[" #I2 "] \n\t" \ + "addq.ph %[" #IO3 "], %[" #IO3 "], %[" #I3 "] \n\t" \ + "addq.ph %[" #IO4 "], %[" #IO4 "], %[" #I4 "] \n\t" \ + "addq.ph %[" #IO5 "], %[" #IO5 "], %[" #I5 "] \n\t" \ + "addq.ph %[" #IO6 "], %[" #IO6 "], %[" #I6 "] \n\t" \ + "addq.ph %[" #IO7 "], %[" #IO7 "], %[" #I7 "] \n\t" \ + "shll_s.ph %[" #IO0 "], %[" #IO0 "], 7 \n\t" \ + "shll_s.ph %[" #IO1 "], %[" #IO1 "], 7 \n\t" \ + "shll_s.ph %[" #IO2 "], %[" #IO2 "], 7 \n\t" \ + "shll_s.ph %[" #IO3 "], %[" #IO3 "], 7 \n\t" \ + "shll_s.ph %[" #IO4 "], %[" #IO4 "], 7 \n\t" \ + "shll_s.ph %[" #IO5 "], %[" #IO5 "], 7 \n\t" \ + "shll_s.ph %[" #IO6 "], %[" #IO6 "], 7 \n\t" \ + "shll_s.ph %[" #IO7 "], %[" #IO7 "], 7 \n\t" \ + "precrqu_s.qb.ph %[" #IO0 "], %[" #IO1 "], %[" #IO0 "] \n\t" \ + "precrqu_s.qb.ph %[" #IO2 "], %[" #IO3 "], %[" #IO2 "] \n\t" \ + "precrqu_s.qb.ph %[" #IO4 "], %[" #IO5 "], %[" #IO4 "] \n\t" \ + "precrqu_s.qb.ph %[" #IO6 "], %[" #IO7 "], %[" #IO6 "] \n\t" \ + "usw %[" #IO0 "], " XSTR(I13) "*" #I9 "(%[" #I8 "]) \n\t" \ + "usw %[" #IO2 "], " XSTR(I13) "*" #I10 "(%[" #I8 "]) \n\t" \ + "usw %[" #IO4 "], " XSTR(I13) "*" #I11 "(%[" #I8 "]) \n\t" \ + "usw %[" #IO6 "], " XSTR(I13) "*" #I12 "(%[" #I8 "]) \n\t" + +#define OUTPUT_EARLY_CLOBBER_REGS_10() \ + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), \ + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp6]"=&r"(temp6), \ + [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), [temp9]"=&r"(temp9), \ + [temp10]"=&r"(temp10) + +#define OUTPUT_EARLY_CLOBBER_REGS_18() \ + OUTPUT_EARLY_CLOBBER_REGS_10(), \ + [temp11]"=&r"(temp11), [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), \ + [temp14]"=&r"(temp14), [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), \ + [temp17]"=&r"(temp17), [temp18]"=&r"(temp18) + +#endif // WEBP_DSP_MIPS_MACRO_H_ diff --git a/3rdparty/libwebp/dsp/msa_macro.h b/3rdparty/libwebp/dsp/msa_macro.h new file mode 100644 index 0000000000..d0e5f45e01 --- /dev/null +++ b/3rdparty/libwebp/dsp/msa_macro.h @@ -0,0 +1,1390 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA common macros +// +// Author(s): Prashant Patil (prashant.patil@imgtec.com) + +#ifndef WEBP_DSP_MSA_MACRO_H_ +#define WEBP_DSP_MSA_MACRO_H_ + +#include +#include + +#if defined(__clang__) + #define CLANG_BUILD +#endif + +#ifdef CLANG_BUILD + #define ADDVI_H(a, b) __msa_addvi_h((v8i16)a, b) + #define ADDVI_W(a, b) __msa_addvi_w((v4i32)a, b) + #define SRAI_B(a, b) __msa_srai_b((v16i8)a, b) + #define SRAI_H(a, b) __msa_srai_h((v8i16)a, b) + #define SRAI_W(a, b) __msa_srai_w((v4i32)a, b) + #define SRLI_H(a, b) __msa_srli_h((v8i16)a, b) + #define SLLI_B(a, b) __msa_slli_b((v4i32)a, b) + #define ANDI_B(a, b) __msa_andi_b((v16u8)a, b) + #define ORI_B(a, b) __msa_ori_b((v16u8)a, b) +#else + #define ADDVI_H(a, b) (a + b) + #define ADDVI_W(a, b) (a + b) + #define SRAI_B(a, b) (a >> b) + #define SRAI_H(a, b) (a >> b) + #define SRAI_W(a, b) (a >> b) + #define SRLI_H(a, b) (a << b) + #define SLLI_B(a, b) (a << b) + #define ANDI_B(a, b) (a & b) + #define ORI_B(a, b) (a | b) +#endif + +#define LD_B(RTYPE, psrc) *((RTYPE*)(psrc)) +#define LD_UB(...) LD_B(v16u8, __VA_ARGS__) +#define LD_SB(...) LD_B(v16i8, __VA_ARGS__) + +#define LD_H(RTYPE, psrc) *((RTYPE*)(psrc)) +#define LD_UH(...) LD_H(v8u16, __VA_ARGS__) +#define LD_SH(...) LD_H(v8i16, __VA_ARGS__) + +#define LD_W(RTYPE, psrc) *((RTYPE*)(psrc)) +#define LD_UW(...) LD_W(v4u32, __VA_ARGS__) +#define LD_SW(...) LD_W(v4i32, __VA_ARGS__) + +#define ST_B(RTYPE, in, pdst) *((RTYPE*)(pdst)) = in +#define ST_UB(...) ST_B(v16u8, __VA_ARGS__) +#define ST_SB(...) ST_B(v16i8, __VA_ARGS__) + +#define ST_H(RTYPE, in, pdst) *((RTYPE*)(pdst)) = in +#define ST_UH(...) ST_H(v8u16, __VA_ARGS__) +#define ST_SH(...) ST_H(v8i16, __VA_ARGS__) + +#define ST_W(RTYPE, in, pdst) *((RTYPE*)(pdst)) = in +#define ST_UW(...) ST_W(v4u32, __VA_ARGS__) +#define ST_SW(...) ST_W(v4i32, __VA_ARGS__) + +#define MSA_LOAD_FUNC(TYPE, INSTR, FUNC_NAME) \ + static inline TYPE FUNC_NAME(const void* const psrc) { \ + const uint8_t* const psrc_m = (const uint8_t*)psrc; \ + TYPE val_m; \ + asm volatile ( \ + "" #INSTR " %[val_m], %[psrc_m] \n\t" \ + : [val_m] "=r" (val_m) \ + : [psrc_m] "m" (*psrc_m)); \ + return val_m; \ + } + +#define MSA_LOAD(psrc, FUNC_NAME) FUNC_NAME(psrc) + +#define MSA_STORE_FUNC(TYPE, INSTR, FUNC_NAME) \ + static inline void FUNC_NAME(TYPE val, void* const pdst) { \ + uint8_t* const pdst_m = (uint8_t*)pdst; \ + TYPE val_m = val; \ + asm volatile ( \ + " " #INSTR " %[val_m], %[pdst_m] \n\t" \ + : [pdst_m] "=m" (*pdst_m) \ + : [val_m] "r" (val_m)); \ + } + +#define MSA_STORE(val, pdst, FUNC_NAME) FUNC_NAME(val, pdst) + +#if (__mips_isa_rev >= 6) + MSA_LOAD_FUNC(uint16_t, lh, msa_lh); + #define LH(psrc) MSA_LOAD(psrc, msa_lh) + MSA_LOAD_FUNC(uint32_t, lw, msa_lw); + #define LW(psrc) MSA_LOAD(psrc, msa_lw) + #if (__mips == 64) + MSA_LOAD_FUNC(uint64_t, ld, msa_ld); + #define LD(psrc) MSA_LOAD(psrc, msa_ld) + #else // !(__mips == 64) + #define LD(psrc) ((((uint64_t)MSA_LOAD(psrc + 4, msa_lw)) << 32) | \ + MSA_LOAD(psrc, msa_lw)) + #endif // (__mips == 64) + + MSA_STORE_FUNC(uint16_t, sh, msa_sh); + #define SH(val, pdst) MSA_STORE(val, pdst, msa_sh) + MSA_STORE_FUNC(uint32_t, sw, msa_sw); + #define SW(val, pdst) MSA_STORE(val, pdst, msa_sw) + MSA_STORE_FUNC(uint64_t, sd, msa_sd); + #define SD(val, pdst) MSA_STORE(val, pdst, msa_sd) +#else // !(__mips_isa_rev >= 6) + MSA_LOAD_FUNC(uint16_t, ulh, msa_ulh); + #define LH(psrc) MSA_LOAD(psrc, msa_ulh) + MSA_LOAD_FUNC(uint32_t, ulw, msa_ulw); + #define LW(psrc) MSA_LOAD(psrc, msa_ulw) + #if (__mips == 64) + MSA_LOAD_FUNC(uint64_t, uld, msa_uld); + #define LD(psrc) MSA_LOAD(psrc, msa_uld) + #else // !(__mips == 64) + #define LD(psrc) ((((uint64_t)MSA_LOAD(psrc + 4, msa_ulw)) << 32) | \ + MSA_LOAD(psrc, msa_ulw)) + #endif // (__mips == 64) + + MSA_STORE_FUNC(uint16_t, ush, msa_ush); + #define SH(val, pdst) MSA_STORE(val, pdst, msa_ush) + MSA_STORE_FUNC(uint32_t, usw, msa_usw); + #define SW(val, pdst) MSA_STORE(val, pdst, msa_usw) + #define SD(val, pdst) do { \ + uint8_t* const pdst_sd_m = (uint8_t*)(pdst); \ + const uint32_t val0_m = (uint32_t)(val & 0x00000000FFFFFFFF); \ + const uint32_t val1_m = (uint32_t)((val >> 32) & 0x00000000FFFFFFFF); \ + SW(val0_m, pdst_sd_m); \ + SW(val1_m, pdst_sd_m + 4); \ + } while (0) +#endif // (__mips_isa_rev >= 6) + +/* Description : Load 4 words with stride + * Arguments : Inputs - psrc, stride + * Outputs - out0, out1, out2, out3 + * Details : Load word in 'out0' from (psrc) + * Load word in 'out1' from (psrc + stride) + * Load word in 'out2' from (psrc + 2 * stride) + * Load word in 'out3' from (psrc + 3 * stride) + */ +#define LW4(psrc, stride, out0, out1, out2, out3) do { \ + const uint8_t* ptmp = (const uint8_t*)psrc; \ + out0 = LW(ptmp); \ + ptmp += stride; \ + out1 = LW(ptmp); \ + ptmp += stride; \ + out2 = LW(ptmp); \ + ptmp += stride; \ + out3 = LW(ptmp); \ +} while (0) + +/* Description : Store words with stride + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + * Details : Store word from 'in0' to (pdst) + * Store word from 'in1' to (pdst + stride) + * Store word from 'in2' to (pdst + 2 * stride) + * Store word from 'in3' to (pdst + 3 * stride) + */ +#define SW4(in0, in1, in2, in3, pdst, stride) do { \ + uint8_t* ptmp = (uint8_t*)pdst; \ + SW(in0, ptmp); \ + ptmp += stride; \ + SW(in1, ptmp); \ + ptmp += stride; \ + SW(in2, ptmp); \ + ptmp += stride; \ + SW(in3, ptmp); \ +} while (0) + +#define SW3(in0, in1, in2, pdst, stride) do { \ + uint8_t* ptmp = (uint8_t*)pdst; \ + SW(in0, ptmp); \ + ptmp += stride; \ + SW(in1, ptmp); \ + ptmp += stride; \ + SW(in2, ptmp); \ +} while (0) + +#define SW2(in0, in1, pdst, stride) do { \ + uint8_t* ptmp = (uint8_t*)pdst; \ + SW(in0, ptmp); \ + ptmp += stride; \ + SW(in1, ptmp); \ +} while (0) + +/* Description : Store 4 double words with stride + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + * Details : Store double word from 'in0' to (pdst) + * Store double word from 'in1' to (pdst + stride) + * Store double word from 'in2' to (pdst + 2 * stride) + * Store double word from 'in3' to (pdst + 3 * stride) + */ +#define SD4(in0, in1, in2, in3, pdst, stride) do { \ + uint8_t* ptmp = (uint8_t*)pdst; \ + SD(in0, ptmp); \ + ptmp += stride; \ + SD(in1, ptmp); \ + ptmp += stride; \ + SD(in2, ptmp); \ + ptmp += stride; \ + SD(in3, ptmp); \ +} while (0) + +/* Description : Load vectors with 16 byte elements with stride + * Arguments : Inputs - psrc, stride + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Load 16 byte elements in 'out0' from (psrc) + * Load 16 byte elements in 'out1' from (psrc + stride) + */ +#define LD_B2(RTYPE, psrc, stride, out0, out1) do { \ + out0 = LD_B(RTYPE, psrc); \ + out1 = LD_B(RTYPE, psrc + stride); \ +} while (0) +#define LD_UB2(...) LD_B2(v16u8, __VA_ARGS__) +#define LD_SB2(...) LD_B2(v16i8, __VA_ARGS__) + +#define LD_B3(RTYPE, psrc, stride, out0, out1, out2) do { \ + LD_B2(RTYPE, psrc, stride, out0, out1); \ + out2 = LD_B(RTYPE, psrc + 2 * stride); \ +} while (0) +#define LD_UB3(...) LD_B3(v16u8, __VA_ARGS__) +#define LD_SB3(...) LD_B3(v16i8, __VA_ARGS__) + +#define LD_B4(RTYPE, psrc, stride, out0, out1, out2, out3) do { \ + LD_B2(RTYPE, psrc, stride, out0, out1); \ + LD_B2(RTYPE, psrc + 2 * stride , stride, out2, out3); \ +} while (0) +#define LD_UB4(...) LD_B4(v16u8, __VA_ARGS__) +#define LD_SB4(...) LD_B4(v16i8, __VA_ARGS__) + +#define LD_B8(RTYPE, psrc, stride, \ + out0, out1, out2, out3, out4, out5, out6, out7) do { \ + LD_B4(RTYPE, psrc, stride, out0, out1, out2, out3); \ + LD_B4(RTYPE, psrc + 4 * stride, stride, out4, out5, out6, out7); \ +} while (0) +#define LD_UB8(...) LD_B8(v16u8, __VA_ARGS__) +#define LD_SB8(...) LD_B8(v16i8, __VA_ARGS__) + +/* Description : Load vectors with 8 halfword elements with stride + * Arguments : Inputs - psrc, stride + * Outputs - out0, out1 + * Details : Load 8 halfword elements in 'out0' from (psrc) + * Load 8 halfword elements in 'out1' from (psrc + stride) + */ +#define LD_H2(RTYPE, psrc, stride, out0, out1) do { \ + out0 = LD_H(RTYPE, psrc); \ + out1 = LD_H(RTYPE, psrc + stride); \ +} while (0) +#define LD_UH2(...) LD_H2(v8u16, __VA_ARGS__) +#define LD_SH2(...) LD_H2(v8i16, __VA_ARGS__) + +/* Description : Load vectors with 4 word elements with stride + * Arguments : Inputs - psrc, stride + * Outputs - out0, out1, out2, out3 + * Details : Load 4 word elements in 'out0' from (psrc + 0 * stride) + * Load 4 word elements in 'out1' from (psrc + 1 * stride) + * Load 4 word elements in 'out2' from (psrc + 2 * stride) + * Load 4 word elements in 'out3' from (psrc + 3 * stride) + */ +#define LD_W2(RTYPE, psrc, stride, out0, out1) do { \ + out0 = LD_W(RTYPE, psrc); \ + out1 = LD_W(RTYPE, psrc + stride); \ +} while (0) +#define LD_UW2(...) LD_W2(v4u32, __VA_ARGS__) +#define LD_SW2(...) LD_W2(v4i32, __VA_ARGS__) + +#define LD_W3(RTYPE, psrc, stride, out0, out1, out2) do { \ + LD_W2(RTYPE, psrc, stride, out0, out1); \ + out2 = LD_W(RTYPE, psrc + 2 * stride); \ +} while (0) +#define LD_UW3(...) LD_W3(v4u32, __VA_ARGS__) +#define LD_SW3(...) LD_W3(v4i32, __VA_ARGS__) + +#define LD_W4(RTYPE, psrc, stride, out0, out1, out2, out3) do { \ + LD_W2(RTYPE, psrc, stride, out0, out1); \ + LD_W2(RTYPE, psrc + 2 * stride, stride, out2, out3); \ +} while (0) +#define LD_UW4(...) LD_W4(v4u32, __VA_ARGS__) +#define LD_SW4(...) LD_W4(v4i32, __VA_ARGS__) + +/* Description : Store vectors of 16 byte elements with stride + * Arguments : Inputs - in0, in1, pdst, stride + * Details : Store 16 byte elements from 'in0' to (pdst) + * Store 16 byte elements from 'in1' to (pdst + stride) + */ +#define ST_B2(RTYPE, in0, in1, pdst, stride) do { \ + ST_B(RTYPE, in0, pdst); \ + ST_B(RTYPE, in1, pdst + stride); \ +} while (0) +#define ST_UB2(...) ST_B2(v16u8, __VA_ARGS__) +#define ST_SB2(...) ST_B2(v16i8, __VA_ARGS__) + +#define ST_B4(RTYPE, in0, in1, in2, in3, pdst, stride) do { \ + ST_B2(RTYPE, in0, in1, pdst, stride); \ + ST_B2(RTYPE, in2, in3, pdst + 2 * stride, stride); \ +} while (0) +#define ST_UB4(...) ST_B4(v16u8, __VA_ARGS__) +#define ST_SB4(...) ST_B4(v16i8, __VA_ARGS__) + +#define ST_B8(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + pdst, stride) do { \ + ST_B4(RTYPE, in0, in1, in2, in3, pdst, stride); \ + ST_B4(RTYPE, in4, in5, in6, in7, pdst + 4 * stride, stride); \ +} while (0) +#define ST_UB8(...) ST_B8(v16u8, __VA_ARGS__) + +/* Description : Store vectors of 4 word elements with stride + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + * Details : Store 4 word elements from 'in0' to (pdst + 0 * stride) + * Store 4 word elements from 'in1' to (pdst + 1 * stride) + * Store 4 word elements from 'in2' to (pdst + 2 * stride) + * Store 4 word elements from 'in3' to (pdst + 3 * stride) + */ +#define ST_W2(RTYPE, in0, in1, pdst, stride) do { \ + ST_W(RTYPE, in0, pdst); \ + ST_W(RTYPE, in1, pdst + stride); \ +} while (0) +#define ST_UW2(...) ST_W2(v4u32, __VA_ARGS__) +#define ST_SW2(...) ST_W2(v4i32, __VA_ARGS__) + +#define ST_W3(RTYPE, in0, in1, in2, pdst, stride) do { \ + ST_W2(RTYPE, in0, in1, pdst, stride); \ + ST_W(RTYPE, in2, pdst + 2 * stride); \ +} while (0) +#define ST_UW3(...) ST_W3(v4u32, __VA_ARGS__) +#define ST_SW3(...) ST_W3(v4i32, __VA_ARGS__) + +#define ST_W4(RTYPE, in0, in1, in2, in3, pdst, stride) do { \ + ST_W2(RTYPE, in0, in1, pdst, stride); \ + ST_W2(RTYPE, in2, in3, pdst + 2 * stride, stride); \ +} while (0) +#define ST_UW4(...) ST_W4(v4u32, __VA_ARGS__) +#define ST_SW4(...) ST_W4(v4i32, __VA_ARGS__) + +/* Description : Store vectors of 8 halfword elements with stride + * Arguments : Inputs - in0, in1, pdst, stride + * Details : Store 8 halfword elements from 'in0' to (pdst) + * Store 8 halfword elements from 'in1' to (pdst + stride) + */ +#define ST_H2(RTYPE, in0, in1, pdst, stride) do { \ + ST_H(RTYPE, in0, pdst); \ + ST_H(RTYPE, in1, pdst + stride); \ +} while (0) +#define ST_UH2(...) ST_H2(v8u16, __VA_ARGS__) +#define ST_SH2(...) ST_H2(v8i16, __VA_ARGS__) + +/* Description : Store 2x4 byte block to destination memory from input vector + * Arguments : Inputs - in, stidx, pdst, stride + * Details : Index 'stidx' halfword element from 'in' vector is copied to + * the GP register and stored to (pdst) + * Index 'stidx+1' halfword element from 'in' vector is copied to + * the GP register and stored to (pdst + stride) + * Index 'stidx+2' halfword element from 'in' vector is copied to + * the GP register and stored to (pdst + 2 * stride) + * Index 'stidx+3' halfword element from 'in' vector is copied to + * the GP register and stored to (pdst + 3 * stride) + */ +#define ST2x4_UB(in, stidx, pdst, stride) do { \ + uint8_t* pblk_2x4_m = (uint8_t*)pdst; \ + const uint16_t out0_m = __msa_copy_s_h((v8i16)in, stidx); \ + const uint16_t out1_m = __msa_copy_s_h((v8i16)in, stidx + 1); \ + const uint16_t out2_m = __msa_copy_s_h((v8i16)in, stidx + 2); \ + const uint16_t out3_m = __msa_copy_s_h((v8i16)in, stidx + 3); \ + SH(out0_m, pblk_2x4_m); \ + pblk_2x4_m += stride; \ + SH(out1_m, pblk_2x4_m); \ + pblk_2x4_m += stride; \ + SH(out2_m, pblk_2x4_m); \ + pblk_2x4_m += stride; \ + SH(out3_m, pblk_2x4_m); \ +} while (0) + +/* Description : Store 4x4 byte block to destination memory from input vector + * Arguments : Inputs - in0, in1, pdst, stride + * Details : 'Idx0' word element from input vector 'in0' is copied to the + * GP register and stored to (pdst) + * 'Idx1' word element from input vector 'in0' is copied to the + * GP register and stored to (pdst + stride) + * 'Idx2' word element from input vector 'in0' is copied to the + * GP register and stored to (pdst + 2 * stride) + * 'Idx3' word element from input vector 'in0' is copied to the + * GP register and stored to (pdst + 3 * stride) + */ +#define ST4x4_UB(in0, in1, idx0, idx1, idx2, idx3, pdst, stride) do { \ + uint8_t* const pblk_4x4_m = (uint8_t*)pdst; \ + const uint32_t out0_m = __msa_copy_s_w((v4i32)in0, idx0); \ + const uint32_t out1_m = __msa_copy_s_w((v4i32)in0, idx1); \ + const uint32_t out2_m = __msa_copy_s_w((v4i32)in1, idx2); \ + const uint32_t out3_m = __msa_copy_s_w((v4i32)in1, idx3); \ + SW4(out0_m, out1_m, out2_m, out3_m, pblk_4x4_m, stride); \ +} while (0) + +#define ST4x8_UB(in0, in1, pdst, stride) do { \ + uint8_t* const pblk_4x8 = (uint8_t*)pdst; \ + ST4x4_UB(in0, in0, 0, 1, 2, 3, pblk_4x8, stride); \ + ST4x4_UB(in1, in1, 0, 1, 2, 3, pblk_4x8 + 4 * stride, stride); \ +} while (0) + +/* Description : Immediate number of elements to slide + * Arguments : Inputs - in0, in1, slide_val + * Outputs - out + * Return Type - as per RTYPE + * Details : Byte elements from 'in1' vector are slid into 'in0' by + * value specified in the 'slide_val' + */ +#define SLDI_B(RTYPE, in0, in1, slide_val) \ + (RTYPE)__msa_sldi_b((v16i8)in0, (v16i8)in1, slide_val) \ + +#define SLDI_UB(...) SLDI_B(v16u8, __VA_ARGS__) +#define SLDI_SB(...) SLDI_B(v16i8, __VA_ARGS__) +#define SLDI_SH(...) SLDI_B(v8i16, __VA_ARGS__) + +/* Description : Shuffle byte vector elements as per mask vector + * Arguments : Inputs - in0, in1, in2, in3, mask0, mask1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Byte elements from 'in0' & 'in1' are copied selectively to + * 'out0' as per control vector 'mask0' + */ +#define VSHF_B(RTYPE, in0, in1, mask) \ + (RTYPE)__msa_vshf_b((v16i8)mask, (v16i8)in1, (v16i8)in0) + +#define VSHF_UB(...) VSHF_B(v16u8, __VA_ARGS__) +#define VSHF_SB(...) VSHF_B(v16i8, __VA_ARGS__) +#define VSHF_UH(...) VSHF_B(v8u16, __VA_ARGS__) +#define VSHF_SH(...) VSHF_B(v8i16, __VA_ARGS__) + +#define VSHF_B2(RTYPE, in0, in1, in2, in3, mask0, mask1, out0, out1) do { \ + out0 = VSHF_B(RTYPE, in0, in1, mask0); \ + out1 = VSHF_B(RTYPE, in2, in3, mask1); \ +} while (0) +#define VSHF_B2_UB(...) VSHF_B2(v16u8, __VA_ARGS__) +#define VSHF_B2_SB(...) VSHF_B2(v16i8, __VA_ARGS__) +#define VSHF_B2_UH(...) VSHF_B2(v8u16, __VA_ARGS__) +#define VSHF_B2_SH(...) VSHF_B2(v8i16, __VA_ARGS__) + +/* Description : Shuffle halfword vector elements as per mask vector + * Arguments : Inputs - in0, in1, in2, in3, mask0, mask1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : halfword elements from 'in0' & 'in1' are copied selectively to + * 'out0' as per control vector 'mask0' + */ +#define VSHF_H2(RTYPE, in0, in1, in2, in3, mask0, mask1, out0, out1) do { \ + out0 = (RTYPE)__msa_vshf_h((v8i16)mask0, (v8i16)in1, (v8i16)in0); \ + out1 = (RTYPE)__msa_vshf_h((v8i16)mask1, (v8i16)in3, (v8i16)in2); \ +} while (0) +#define VSHF_H2_UH(...) VSHF_H2(v8u16, __VA_ARGS__) +#define VSHF_H2_SH(...) VSHF_H2(v8i16, __VA_ARGS__) + +/* Description : Dot product of byte vector elements + * Arguments : Inputs - mult0, mult1, cnst0, cnst1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Signed byte elements from 'mult0' are multiplied with + * signed byte elements from 'cnst0' producing a result + * twice the size of input i.e. signed halfword. + * The multiplication result of adjacent odd-even elements + * are added together and written to the 'out0' vector +*/ +#define DOTP_SB2(RTYPE, mult0, mult1, cnst0, cnst1, out0, out1) do { \ + out0 = (RTYPE)__msa_dotp_s_h((v16i8)mult0, (v16i8)cnst0); \ + out1 = (RTYPE)__msa_dotp_s_h((v16i8)mult1, (v16i8)cnst1); \ +} while (0) +#define DOTP_SB2_SH(...) DOTP_SB2(v8i16, __VA_ARGS__) + +/* Description : Dot product of halfword vector elements + * Arguments : Inputs - mult0, mult1, cnst0, cnst1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Signed halfword elements from 'mult0' are multiplied with + * signed halfword elements from 'cnst0' producing a result + * twice the size of input i.e. signed word. + * The multiplication result of adjacent odd-even elements + * are added together and written to the 'out0' vector + */ +#define DOTP_SH2(RTYPE, mult0, mult1, cnst0, cnst1, out0, out1) do { \ + out0 = (RTYPE)__msa_dotp_s_w((v8i16)mult0, (v8i16)cnst0); \ + out1 = (RTYPE)__msa_dotp_s_w((v8i16)mult1, (v8i16)cnst1); \ +} while (0) +#define DOTP_SH2_SW(...) DOTP_SH2(v4i32, __VA_ARGS__) + +/* Description : Dot product of unsigned word vector elements + * Arguments : Inputs - mult0, mult1, cnst0, cnst1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Unsigned word elements from 'mult0' are multiplied with + * unsigned word elements from 'cnst0' producing a result + * twice the size of input i.e. unsigned double word. + * The multiplication result of adjacent odd-even elements + * are added together and written to the 'out0' vector + */ +#define DOTP_UW2(RTYPE, mult0, mult1, cnst0, cnst1, out0, out1) do { \ + out0 = (RTYPE)__msa_dotp_u_d((v4u32)mult0, (v4u32)cnst0); \ + out1 = (RTYPE)__msa_dotp_u_d((v4u32)mult1, (v4u32)cnst1); \ +} while (0) +#define DOTP_UW2_UD(...) DOTP_UW2(v2u64, __VA_ARGS__) + +/* Description : Dot product & addition of halfword vector elements + * Arguments : Inputs - mult0, mult1, cnst0, cnst1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Signed halfword elements from 'mult0' are multiplied with + * signed halfword elements from 'cnst0' producing a result + * twice the size of input i.e. signed word. + * The multiplication result of adjacent odd-even elements + * are added to the 'out0' vector + */ +#define DPADD_SH2(RTYPE, mult0, mult1, cnst0, cnst1, out0, out1) do { \ + out0 = (RTYPE)__msa_dpadd_s_w((v4i32)out0, (v8i16)mult0, (v8i16)cnst0); \ + out1 = (RTYPE)__msa_dpadd_s_w((v4i32)out1, (v8i16)mult1, (v8i16)cnst1); \ +} while (0) +#define DPADD_SH2_SW(...) DPADD_SH2(v4i32, __VA_ARGS__) + +/* Description : Clips all signed halfword elements of input vector + * between 0 & 255 + * Arguments : Input/output - val + * Return Type - signed halfword + */ +#define CLIP_SH_0_255(val) do { \ + const v8i16 max_m = __msa_ldi_h(255); \ + val = __msa_maxi_s_h((v8i16)val, 0); \ + val = __msa_min_s_h(max_m, (v8i16)val); \ +} while (0) + +#define CLIP_SH2_0_255(in0, in1) do { \ + CLIP_SH_0_255(in0); \ + CLIP_SH_0_255(in1); \ +} while (0) + +#define CLIP_SH4_0_255(in0, in1, in2, in3) do { \ + CLIP_SH2_0_255(in0, in1); \ + CLIP_SH2_0_255(in2, in3); \ +} while (0) + +/* Description : Clips all unsigned halfword elements of input vector + * between 0 & 255 + * Arguments : Input - in + * Output - out_m + * Return Type - unsigned halfword + */ +#define CLIP_UH_0_255(in) do { \ + const v8u16 max_m = (v8u16)__msa_ldi_h(255); \ + in = __msa_maxi_u_h((v8u16) in, 0); \ + in = __msa_min_u_h((v8u16) max_m, (v8u16) in); \ +} while (0) + +#define CLIP_UH2_0_255(in0, in1) do { \ + CLIP_UH_0_255(in0); \ + CLIP_UH_0_255(in1); \ +} while (0) + +/* Description : Clips all signed word elements of input vector + * between 0 & 255 + * Arguments : Input/output - val + * Return Type - signed word + */ +#define CLIP_SW_0_255(val) do { \ + const v4i32 max_m = __msa_ldi_w(255); \ + val = __msa_maxi_s_w((v4i32)val, 0); \ + val = __msa_min_s_w(max_m, (v4i32)val); \ +} while (0) + +#define CLIP_SW4_0_255(in0, in1, in2, in3) do { \ + CLIP_SW_0_255(in0); \ + CLIP_SW_0_255(in1); \ + CLIP_SW_0_255(in2); \ + CLIP_SW_0_255(in3); \ +} while (0) + +/* Description : Horizontal addition of 4 signed word elements of input vector + * Arguments : Input - in (signed word vector) + * Output - sum_m (i32 sum) + * Return Type - signed word (GP) + * Details : 4 signed word elements of 'in' vector are added together and + * the resulting integer sum is returned + */ +static WEBP_INLINE int32_t func_hadd_sw_s32(v4i32 in) { + const v2i64 res0_m = __msa_hadd_s_d((v4i32)in, (v4i32)in); + const v2i64 res1_m = __msa_splati_d(res0_m, 1); + const v2i64 out = res0_m + res1_m; + int32_t sum_m = __msa_copy_s_w((v4i32)out, 0); + return sum_m; +} +#define HADD_SW_S32(in) func_hadd_sw_s32(in) + +/* Description : Horizontal addition of 8 signed halfword elements + * Arguments : Input - in (signed halfword vector) + * Output - sum_m (s32 sum) + * Return Type - signed word + * Details : 8 signed halfword elements of input vector are added + * together and the resulting integer sum is returned + */ +static WEBP_INLINE int32_t func_hadd_sh_s32(v8i16 in) { + const v4i32 res = __msa_hadd_s_w(in, in); + const v2i64 res0 = __msa_hadd_s_d(res, res); + const v2i64 res1 = __msa_splati_d(res0, 1); + const v2i64 res2 = res0 + res1; + const int32_t sum_m = __msa_copy_s_w((v4i32)res2, 0); + return sum_m; +} +#define HADD_SH_S32(in) func_hadd_sh_s32(in) + +/* Description : Horizontal addition of 8 unsigned halfword elements + * Arguments : Input - in (unsigned halfword vector) + * Output - sum_m (u32 sum) + * Return Type - unsigned word + * Details : 8 unsigned halfword elements of input vector are added + * together and the resulting integer sum is returned + */ +static WEBP_INLINE uint32_t func_hadd_uh_u32(v8u16 in) { + uint32_t sum_m; + const v4u32 res_m = __msa_hadd_u_w(in, in); + v2u64 res0_m = __msa_hadd_u_d(res_m, res_m); + v2u64 res1_m = (v2u64)__msa_splati_d((v2i64)res0_m, 1); + res0_m = res0_m + res1_m; + sum_m = __msa_copy_s_w((v4i32)res0_m, 0); + return sum_m; +} +#define HADD_UH_U32(in) func_hadd_uh_u32(in) + +/* Description : Horizontal addition of signed half word vector elements + Arguments : Inputs - in0, in1 + Outputs - out0, out1 + Return Type - as per RTYPE + Details : Each signed odd half word element from 'in0' is added to + even signed half word element from 'in0' (pairwise) and the + halfword result is written in 'out0' +*/ +#define HADD_SH2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_hadd_s_w((v8i16)in0, (v8i16)in0); \ + out1 = (RTYPE)__msa_hadd_s_w((v8i16)in1, (v8i16)in1); \ +} while (0) +#define HADD_SH2_SW(...) HADD_SH2(v4i32, __VA_ARGS__) + +#define HADD_SH4(RTYPE, in0, in1, in2, in3, out0, out1, out2, out3) do { \ + HADD_SH2(RTYPE, in0, in1, out0, out1); \ + HADD_SH2(RTYPE, in2, in3, out2, out3); \ +} while (0) +#define HADD_SH4_SW(...) HADD_SH4(v4i32, __VA_ARGS__) + +/* Description : Horizontal subtraction of unsigned byte vector elements + * Arguments : Inputs - in0, in1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Each unsigned odd byte element from 'in0' is subtracted from + * even unsigned byte element from 'in0' (pairwise) and the + * halfword result is written to 'out0' + */ +#define HSUB_UB2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_hsub_u_h((v16u8)in0, (v16u8)in0); \ + out1 = (RTYPE)__msa_hsub_u_h((v16u8)in1, (v16u8)in1); \ +} while (0) +#define HSUB_UB2_UH(...) HSUB_UB2(v8u16, __VA_ARGS__) +#define HSUB_UB2_SH(...) HSUB_UB2(v8i16, __VA_ARGS__) +#define HSUB_UB2_SW(...) HSUB_UB2(v4i32, __VA_ARGS__) + +/* Description : Set element n input vector to GPR value + * Arguments : Inputs - in0, in1, in2, in3 + * Output - out + * Return Type - as per RTYPE + * Details : Set element 0 in vector 'out' to value specified in 'in0' + */ +#define INSERT_W2(RTYPE, in0, in1, out) do { \ + out = (RTYPE)__msa_insert_w((v4i32)out, 0, in0); \ + out = (RTYPE)__msa_insert_w((v4i32)out, 1, in1); \ +} while (0) +#define INSERT_W2_UB(...) INSERT_W2(v16u8, __VA_ARGS__) +#define INSERT_W2_SB(...) INSERT_W2(v16i8, __VA_ARGS__) + +#define INSERT_W4(RTYPE, in0, in1, in2, in3, out) do { \ + out = (RTYPE)__msa_insert_w((v4i32)out, 0, in0); \ + out = (RTYPE)__msa_insert_w((v4i32)out, 1, in1); \ + out = (RTYPE)__msa_insert_w((v4i32)out, 2, in2); \ + out = (RTYPE)__msa_insert_w((v4i32)out, 3, in3); \ +} while (0) +#define INSERT_W4_UB(...) INSERT_W4(v16u8, __VA_ARGS__) +#define INSERT_W4_SB(...) INSERT_W4(v16i8, __VA_ARGS__) +#define INSERT_W4_SW(...) INSERT_W4(v4i32, __VA_ARGS__) + +/* Description : Set element n of double word input vector to GPR value + * Arguments : Inputs - in0, in1 + * Output - out + * Return Type - as per RTYPE + * Details : Set element 0 in vector 'out' to GPR value specified in 'in0' + * Set element 1 in vector 'out' to GPR value specified in 'in1' + */ +#define INSERT_D2(RTYPE, in0, in1, out) do { \ + out = (RTYPE)__msa_insert_d((v2i64)out, 0, in0); \ + out = (RTYPE)__msa_insert_d((v2i64)out, 1, in1); \ +} while (0) +#define INSERT_D2_UB(...) INSERT_D2(v16u8, __VA_ARGS__) +#define INSERT_D2_SB(...) INSERT_D2(v16i8, __VA_ARGS__) + +/* Description : Interleave even byte elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even byte elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVEV_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_b((v16i8)in1, (v16i8)in0); \ + out1 = (RTYPE)__msa_ilvev_b((v16i8)in3, (v16i8)in2); \ +} while (0) +#define ILVEV_B2_UB(...) ILVEV_B2(v16u8, __VA_ARGS__) +#define ILVEV_B2_SB(...) ILVEV_B2(v16i8, __VA_ARGS__) +#define ILVEV_B2_UH(...) ILVEV_B2(v8u16, __VA_ARGS__) +#define ILVEV_B2_SH(...) ILVEV_B2(v8i16, __VA_ARGS__) +#define ILVEV_B2_SD(...) ILVEV_B2(v2i64, __VA_ARGS__) + +/* Description : Interleave odd byte elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Odd byte elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVOD_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvod_b((v16i8)in1, (v16i8)in0); \ + out1 = (RTYPE)__msa_ilvod_b((v16i8)in3, (v16i8)in2); \ +} while (0) +#define ILVOD_B2_UB(...) ILVOD_B2(v16u8, __VA_ARGS__) +#define ILVOD_B2_SB(...) ILVOD_B2(v16i8, __VA_ARGS__) +#define ILVOD_B2_UH(...) ILVOD_B2(v8u16, __VA_ARGS__) +#define ILVOD_B2_SH(...) ILVOD_B2(v8i16, __VA_ARGS__) +#define ILVOD_B2_SD(...) ILVOD_B2(v2i64, __VA_ARGS__) + +/* Description : Interleave even halfword elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even halfword elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVEV_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_h((v8i16)in1, (v8i16)in0); \ + out1 = (RTYPE)__msa_ilvev_h((v8i16)in3, (v8i16)in2); \ +} while (0) +#define ILVEV_H2_UB(...) ILVEV_H2(v16u8, __VA_ARGS__) +#define ILVEV_H2_UH(...) ILVEV_H2(v8u16, __VA_ARGS__) +#define ILVEV_H2_SH(...) ILVEV_H2(v8i16, __VA_ARGS__) +#define ILVEV_H2_SW(...) ILVEV_H2(v4i32, __VA_ARGS__) + +/* Description : Interleave odd halfword elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Odd halfword elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVOD_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvod_h((v8i16)in1, (v8i16)in0); \ + out1 = (RTYPE)__msa_ilvod_h((v8i16)in3, (v8i16)in2); \ +} while (0) +#define ILVOD_H2_UB(...) ILVOD_H2(v16u8, __VA_ARGS__) +#define ILVOD_H2_UH(...) ILVOD_H2(v8u16, __VA_ARGS__) +#define ILVOD_H2_SH(...) ILVOD_H2(v8i16, __VA_ARGS__) +#define ILVOD_H2_SW(...) ILVOD_H2(v4i32, __VA_ARGS__) + +/* Description : Interleave even word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even word elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVEV_W2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_w((v4i32)in1, (v4i32)in0); \ + out1 = (RTYPE)__msa_ilvev_w((v4i32)in3, (v4i32)in2); \ +} while (0) +#define ILVEV_W2_UB(...) ILVEV_W2(v16u8, __VA_ARGS__) +#define ILVEV_W2_SB(...) ILVEV_W2(v16i8, __VA_ARGS__) +#define ILVEV_W2_UH(...) ILVEV_W2(v8u16, __VA_ARGS__) +#define ILVEV_W2_SD(...) ILVEV_W2(v2i64, __VA_ARGS__) + +/* Description : Interleave even-odd word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even word elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + * Odd word elements of 'in2' and 'in3' are interleaved + * and written to 'out1' + */ +#define ILVEVOD_W2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_w((v4i32)in1, (v4i32)in0); \ + out1 = (RTYPE)__msa_ilvod_w((v4i32)in3, (v4i32)in2); \ +} while (0) +#define ILVEVOD_W2_UB(...) ILVEVOD_W2(v16u8, __VA_ARGS__) +#define ILVEVOD_W2_UH(...) ILVEVOD_W2(v8u16, __VA_ARGS__) +#define ILVEVOD_W2_SH(...) ILVEVOD_W2(v8i16, __VA_ARGS__) +#define ILVEVOD_W2_SW(...) ILVEVOD_W2(v4i32, __VA_ARGS__) + +/* Description : Interleave even-odd half-word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even half-word elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + * Odd half-word elements of 'in2' and 'in3' are interleaved + * and written to 'out1' + */ +#define ILVEVOD_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_h((v8i16)in1, (v8i16)in0); \ + out1 = (RTYPE)__msa_ilvod_h((v8i16)in3, (v8i16)in2); \ +} while (0) +#define ILVEVOD_H2_UB(...) ILVEVOD_H2(v16u8, __VA_ARGS__) +#define ILVEVOD_H2_UH(...) ILVEVOD_H2(v8u16, __VA_ARGS__) +#define ILVEVOD_H2_SH(...) ILVEVOD_H2(v8i16, __VA_ARGS__) +#define ILVEVOD_H2_SW(...) ILVEVOD_H2(v4i32, __VA_ARGS__) + +/* Description : Interleave even double word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even double word elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVEV_D2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_d((v2i64)in1, (v2i64)in0); \ + out1 = (RTYPE)__msa_ilvev_d((v2i64)in3, (v2i64)in2); \ +} while (0) +#define ILVEV_D2_UB(...) ILVEV_D2(v16u8, __VA_ARGS__) +#define ILVEV_D2_SB(...) ILVEV_D2(v16i8, __VA_ARGS__) +#define ILVEV_D2_SW(...) ILVEV_D2(v4i32, __VA_ARGS__) +#define ILVEV_D2_SD(...) ILVEV_D2(v2i64, __VA_ARGS__) + +/* Description : Interleave left half of byte elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Left half of byte elements of 'in0' and 'in1' are interleaved + * and written to 'out0'. + */ +#define ILVL_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvl_b((v16i8)in0, (v16i8)in1); \ + out1 = (RTYPE)__msa_ilvl_b((v16i8)in2, (v16i8)in3); \ +} while (0) +#define ILVL_B2_UB(...) ILVL_B2(v16u8, __VA_ARGS__) +#define ILVL_B2_SB(...) ILVL_B2(v16i8, __VA_ARGS__) +#define ILVL_B2_UH(...) ILVL_B2(v8u16, __VA_ARGS__) +#define ILVL_B2_SH(...) ILVL_B2(v8i16, __VA_ARGS__) +#define ILVL_B2_SW(...) ILVL_B2(v4i32, __VA_ARGS__) + +/* Description : Interleave right half of byte elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Right half of byte elements of 'in0' and 'in1' are interleaved + * and written to out0. + */ +#define ILVR_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_b((v16i8)in0, (v16i8)in1); \ + out1 = (RTYPE)__msa_ilvr_b((v16i8)in2, (v16i8)in3); \ +} while (0) +#define ILVR_B2_UB(...) ILVR_B2(v16u8, __VA_ARGS__) +#define ILVR_B2_SB(...) ILVR_B2(v16i8, __VA_ARGS__) +#define ILVR_B2_UH(...) ILVR_B2(v8u16, __VA_ARGS__) +#define ILVR_B2_SH(...) ILVR_B2(v8i16, __VA_ARGS__) +#define ILVR_B2_SW(...) ILVR_B2(v4i32, __VA_ARGS__) + +#define ILVR_B4(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + ILVR_B2(RTYPE, in0, in1, in2, in3, out0, out1); \ + ILVR_B2(RTYPE, in4, in5, in6, in7, out2, out3); \ +} while (0) +#define ILVR_B4_UB(...) ILVR_B4(v16u8, __VA_ARGS__) +#define ILVR_B4_SB(...) ILVR_B4(v16i8, __VA_ARGS__) +#define ILVR_B4_UH(...) ILVR_B4(v8u16, __VA_ARGS__) +#define ILVR_B4_SH(...) ILVR_B4(v8i16, __VA_ARGS__) +#define ILVR_B4_SW(...) ILVR_B4(v4i32, __VA_ARGS__) + +/* Description : Interleave right half of halfword elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Right half of halfword elements of 'in0' and 'in1' are + * interleaved and written to 'out0'. + */ +#define ILVR_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_h((v8i16)in0, (v8i16)in1); \ + out1 = (RTYPE)__msa_ilvr_h((v8i16)in2, (v8i16)in3); \ +} while (0) +#define ILVR_H2_UB(...) ILVR_H2(v16u8, __VA_ARGS__) +#define ILVR_H2_SH(...) ILVR_H2(v8i16, __VA_ARGS__) +#define ILVR_H2_SW(...) ILVR_H2(v4i32, __VA_ARGS__) + +#define ILVR_H4(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + ILVR_H2(RTYPE, in0, in1, in2, in3, out0, out1); \ + ILVR_H2(RTYPE, in4, in5, in6, in7, out2, out3); \ +} while (0) +#define ILVR_H4_UB(...) ILVR_H4(v16u8, __VA_ARGS__) +#define ILVR_H4_SH(...) ILVR_H4(v8i16, __VA_ARGS__) +#define ILVR_H4_SW(...) ILVR_H4(v4i32, __VA_ARGS__) + +/* Description : Interleave right half of double word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Right half of double word elements of 'in0' and 'in1' are + * interleaved and written to 'out0'. + */ +#define ILVR_D2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_d((v2i64)in0, (v2i64)in1); \ + out1 = (RTYPE)__msa_ilvr_d((v2i64)in2, (v2i64)in3); \ +} while (0) +#define ILVR_D2_UB(...) ILVR_D2(v16u8, __VA_ARGS__) +#define ILVR_D2_SB(...) ILVR_D2(v16i8, __VA_ARGS__) +#define ILVR_D2_SH(...) ILVR_D2(v8i16, __VA_ARGS__) + +#define ILVR_D4(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + ILVR_D2(RTYPE, in0, in1, in2, in3, out0, out1); \ + ILVR_D2(RTYPE, in4, in5, in6, in7, out2, out3); \ +} while (0) +#define ILVR_D4_SB(...) ILVR_D4(v16i8, __VA_ARGS__) +#define ILVR_D4_UB(...) ILVR_D4(v16u8, __VA_ARGS__) + +/* Description : Interleave both left and right half of input vectors + * Arguments : Inputs - in0, in1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Right half of byte elements from 'in0' and 'in1' are + * interleaved and written to 'out0' + */ +#define ILVRL_B2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_b((v16i8)in0, (v16i8)in1); \ + out1 = (RTYPE)__msa_ilvl_b((v16i8)in0, (v16i8)in1); \ +} while (0) +#define ILVRL_B2_UB(...) ILVRL_B2(v16u8, __VA_ARGS__) +#define ILVRL_B2_SB(...) ILVRL_B2(v16i8, __VA_ARGS__) +#define ILVRL_B2_UH(...) ILVRL_B2(v8u16, __VA_ARGS__) +#define ILVRL_B2_SH(...) ILVRL_B2(v8i16, __VA_ARGS__) +#define ILVRL_B2_SW(...) ILVRL_B2(v4i32, __VA_ARGS__) + +#define ILVRL_H2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_h((v8i16)in0, (v8i16)in1); \ + out1 = (RTYPE)__msa_ilvl_h((v8i16)in0, (v8i16)in1); \ +} while (0) +#define ILVRL_H2_UB(...) ILVRL_H2(v16u8, __VA_ARGS__) +#define ILVRL_H2_SB(...) ILVRL_H2(v16i8, __VA_ARGS__) +#define ILVRL_H2_SH(...) ILVRL_H2(v8i16, __VA_ARGS__) +#define ILVRL_H2_SW(...) ILVRL_H2(v4i32, __VA_ARGS__) +#define ILVRL_H2_UW(...) ILVRL_H2(v4u32, __VA_ARGS__) + +#define ILVRL_W2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_w((v4i32)in0, (v4i32)in1); \ + out1 = (RTYPE)__msa_ilvl_w((v4i32)in0, (v4i32)in1); \ +} while (0) +#define ILVRL_W2_UB(...) ILVRL_W2(v16u8, __VA_ARGS__) +#define ILVRL_W2_SH(...) ILVRL_W2(v8i16, __VA_ARGS__) +#define ILVRL_W2_SW(...) ILVRL_W2(v4i32, __VA_ARGS__) +#define ILVRL_W2_UW(...) ILVRL_W2(v4u32, __VA_ARGS__) + +/* Description : Pack even byte elements of vector pairs + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even byte elements of 'in0' are copied to the left half of + * 'out0' & even byte elements of 'in1' are copied to the right + * half of 'out0'. + */ +#define PCKEV_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_pckev_b((v16i8)in0, (v16i8)in1); \ + out1 = (RTYPE)__msa_pckev_b((v16i8)in2, (v16i8)in3); \ +} while (0) +#define PCKEV_B2_SB(...) PCKEV_B2(v16i8, __VA_ARGS__) +#define PCKEV_B2_UB(...) PCKEV_B2(v16u8, __VA_ARGS__) +#define PCKEV_B2_SH(...) PCKEV_B2(v8i16, __VA_ARGS__) +#define PCKEV_B2_SW(...) PCKEV_B2(v4i32, __VA_ARGS__) + +#define PCKEV_B4(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + PCKEV_B2(RTYPE, in0, in1, in2, in3, out0, out1); \ + PCKEV_B2(RTYPE, in4, in5, in6, in7, out2, out3); \ +} while (0) +#define PCKEV_B4_SB(...) PCKEV_B4(v16i8, __VA_ARGS__) +#define PCKEV_B4_UB(...) PCKEV_B4(v16u8, __VA_ARGS__) +#define PCKEV_B4_SH(...) PCKEV_B4(v8i16, __VA_ARGS__) +#define PCKEV_B4_SW(...) PCKEV_B4(v4i32, __VA_ARGS__) + +/* Description : Pack even halfword elements of vector pairs + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even halfword elements of 'in0' are copied to the left half of + * 'out0' & even halfword elements of 'in1' are copied to the + * right half of 'out0'. + */ +#define PCKEV_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_pckev_h((v8i16)in0, (v8i16)in1); \ + out1 = (RTYPE)__msa_pckev_h((v8i16)in2, (v8i16)in3); \ +} while (0) +#define PCKEV_H2_UH(...) PCKEV_H2(v8u16, __VA_ARGS__) +#define PCKEV_H2_SH(...) PCKEV_H2(v8i16, __VA_ARGS__) +#define PCKEV_H2_SW(...) PCKEV_H2(v4i32, __VA_ARGS__) +#define PCKEV_H2_UW(...) PCKEV_H2(v4u32, __VA_ARGS__) + +/* Description : Pack even word elements of vector pairs + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even word elements of 'in0' are copied to the left half of + * 'out0' & even word elements of 'in1' are copied to the + * right half of 'out0'. + */ +#define PCKEV_W2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_pckev_w((v4i32)in0, (v4i32)in1); \ + out1 = (RTYPE)__msa_pckev_w((v4i32)in2, (v4i32)in3); \ +} while (0) +#define PCKEV_W2_UH(...) PCKEV_W2(v8u16, __VA_ARGS__) +#define PCKEV_W2_SH(...) PCKEV_W2(v8i16, __VA_ARGS__) +#define PCKEV_W2_SW(...) PCKEV_W2(v4i32, __VA_ARGS__) +#define PCKEV_W2_UW(...) PCKEV_W2(v4u32, __VA_ARGS__) + +/* Description : Pack odd halfword elements of vector pairs + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Odd halfword elements of 'in0' are copied to the left half of + * 'out0' & odd halfword elements of 'in1' are copied to the + * right half of 'out0'. + */ +#define PCKOD_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_pckod_h((v8i16)in0, (v8i16)in1); \ + out1 = (RTYPE)__msa_pckod_h((v8i16)in2, (v8i16)in3); \ +} while (0) +#define PCKOD_H2_UH(...) PCKOD_H2(v8u16, __VA_ARGS__) +#define PCKOD_H2_SH(...) PCKOD_H2(v8i16, __VA_ARGS__) +#define PCKOD_H2_SW(...) PCKOD_H2(v4i32, __VA_ARGS__) +#define PCKOD_H2_UW(...) PCKOD_H2(v4u32, __VA_ARGS__) + +/* Description : Arithmetic immediate shift right all elements of word vector + * Arguments : Inputs - in0, in1, shift + * Outputs - in place operation + * Return Type - as per input vector RTYPE + * Details : Each element of vector 'in0' is right shifted by 'shift' and + * the result is written in-place. 'shift' is a GP variable. + */ +#define SRAI_W2(RTYPE, in0, in1, shift_val) do { \ + in0 = (RTYPE)SRAI_W(in0, shift_val); \ + in1 = (RTYPE)SRAI_W(in1, shift_val); \ +} while (0) +#define SRAI_W2_SW(...) SRAI_W2(v4i32, __VA_ARGS__) +#define SRAI_W2_UW(...) SRAI_W2(v4u32, __VA_ARGS__) + +#define SRAI_W4(RTYPE, in0, in1, in2, in3, shift_val) do { \ + SRAI_W2(RTYPE, in0, in1, shift_val); \ + SRAI_W2(RTYPE, in2, in3, shift_val); \ +} while (0) +#define SRAI_W4_SW(...) SRAI_W4(v4i32, __VA_ARGS__) +#define SRAI_W4_UW(...) SRAI_W4(v4u32, __VA_ARGS__) + +/* Description : Arithmetic shift right all elements of half-word vector + * Arguments : Inputs - in0, in1, shift + * Outputs - in place operation + * Return Type - as per input vector RTYPE + * Details : Each element of vector 'in0' is right shifted by 'shift' and + * the result is written in-place. 'shift' is a GP variable. + */ +#define SRAI_H2(RTYPE, in0, in1, shift_val) do { \ + in0 = (RTYPE)SRAI_H(in0, shift_val); \ + in1 = (RTYPE)SRAI_H(in1, shift_val); \ +} while (0) +#define SRAI_H2_SH(...) SRAI_H2(v8i16, __VA_ARGS__) +#define SRAI_H2_UH(...) SRAI_H2(v8u16, __VA_ARGS__) + +/* Description : Arithmetic rounded shift right all elements of word vector + * Arguments : Inputs - in0, in1, shift + * Outputs - in place operation + * Return Type - as per input vector RTYPE + * Details : Each element of vector 'in0' is right shifted by 'shift' and + * the result is written in-place. 'shift' is a GP variable. + */ +#define SRARI_W2(RTYPE, in0, in1, shift) do { \ + in0 = (RTYPE)__msa_srari_w((v4i32)in0, shift); \ + in1 = (RTYPE)__msa_srari_w((v4i32)in1, shift); \ +} while (0) +#define SRARI_W2_SW(...) SRARI_W2(v4i32, __VA_ARGS__) + +#define SRARI_W4(RTYPE, in0, in1, in2, in3, shift) do { \ + SRARI_W2(RTYPE, in0, in1, shift); \ + SRARI_W2(RTYPE, in2, in3, shift); \ +} while (0) +#define SRARI_W4_SH(...) SRARI_W4(v8i16, __VA_ARGS__) +#define SRARI_W4_UW(...) SRARI_W4(v4u32, __VA_ARGS__) +#define SRARI_W4_SW(...) SRARI_W4(v4i32, __VA_ARGS__) + +/* Description : Shift right arithmetic rounded double words + * Arguments : Inputs - in0, in1, shift + * Outputs - in place operation + * Return Type - as per RTYPE + * Details : Each element of vector 'in0' is shifted right arithmetically by + * the number of bits in the corresponding element in the vector + * 'shift'. The last discarded bit is added to shifted value for + * rounding and the result is written in-place. + * 'shift' is a vector. + */ +#define SRAR_D2(RTYPE, in0, in1, shift) do { \ + in0 = (RTYPE)__msa_srar_d((v2i64)in0, (v2i64)shift); \ + in1 = (RTYPE)__msa_srar_d((v2i64)in1, (v2i64)shift); \ +} while (0) +#define SRAR_D2_SW(...) SRAR_D2(v4i32, __VA_ARGS__) +#define SRAR_D2_SD(...) SRAR_D2(v2i64, __VA_ARGS__) +#define SRAR_D2_UD(...) SRAR_D2(v2u64, __VA_ARGS__) + +#define SRAR_D4(RTYPE, in0, in1, in2, in3, shift) do { \ + SRAR_D2(RTYPE, in0, in1, shift); \ + SRAR_D2(RTYPE, in2, in3, shift); \ +} while (0) +#define SRAR_D4_SD(...) SRAR_D4(v2i64, __VA_ARGS__) +#define SRAR_D4_UD(...) SRAR_D4(v2u64, __VA_ARGS__) + +/* Description : Addition of 2 pairs of half-word vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element in 'in0' is added to 'in1' and result is written + * to 'out0'. + */ +#define ADDVI_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)ADDVI_H(in0, in1); \ + out1 = (RTYPE)ADDVI_H(in2, in3); \ +} while (0) +#define ADDVI_H2_SH(...) ADDVI_H2(v8i16, __VA_ARGS__) +#define ADDVI_H2_UH(...) ADDVI_H2(v8u16, __VA_ARGS__) + +/* Description : Addition of 2 pairs of word vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element in 'in0' is added to 'in1' and result is written + * to 'out0'. + */ +#define ADDVI_W2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)ADDVI_W(in0, in1); \ + out1 = (RTYPE)ADDVI_W(in2, in3); \ +} while (0) +#define ADDVI_W2_SW(...) ADDVI_W2(v4i32, __VA_ARGS__) + +/* Description : Fill 2 pairs of word vectors with GP registers + * Arguments : Inputs - in0, in1 + * Outputs - out0, out1 + * Details : GP register in0 is replicated in each word element of out0 + * GP register in1 is replicated in each word element of out1 + */ +#define FILL_W2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_fill_w(in0); \ + out1 = (RTYPE)__msa_fill_w(in1); \ +} while (0) +#define FILL_W2_SW(...) FILL_W2(v4i32, __VA_ARGS__) + +/* Description : Addition of 2 pairs of vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element in 'in0' is added to 'in1' and result is written + * to 'out0'. + */ +#define ADD2(in0, in1, in2, in3, out0, out1) do { \ + out0 = in0 + in1; \ + out1 = in2 + in3; \ +} while (0) + +#define ADD4(in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + ADD2(in0, in1, in2, in3, out0, out1); \ + ADD2(in4, in5, in6, in7, out2, out3); \ +} while (0) + +/* Description : Subtraction of 2 pairs of vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element in 'in1' is subtracted from 'in0' and result is + * written to 'out0'. + */ +#define SUB2(in0, in1, in2, in3, out0, out1) do { \ + out0 = in0 - in1; \ + out1 = in2 - in3; \ +} while (0) + +#define SUB3(in0, in1, in2, in3, in4, in5, out0, out1, out2) do { \ + out0 = in0 - in1; \ + out1 = in2 - in3; \ + out2 = in4 - in5; \ +} while (0) + +#define SUB4(in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + out0 = in0 - in1; \ + out1 = in2 - in3; \ + out2 = in4 - in5; \ + out3 = in6 - in7; \ +} while (0) + +/* Description : Addition - Subtraction of input vectors + * Arguments : Inputs - in0, in1 + * Outputs - out0, out1 + * Details : Each element in 'in1' is added to 'in0' and result is + * written to 'out0'. + * Each element in 'in1' is subtracted from 'in0' and result is + * written to 'out1'. + */ +#define ADDSUB2(in0, in1, out0, out1) do { \ + out0 = in0 + in1; \ + out1 = in0 - in1; \ +} while (0) + +/* Description : Multiplication of pairs of vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element from 'in0' is multiplied with elements from 'in1' + * and the result is written to 'out0' + */ +#define MUL2(in0, in1, in2, in3, out0, out1) do { \ + out0 = in0 * in1; \ + out1 = in2 * in3; \ +} while (0) + +#define MUL4(in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + MUL2(in0, in1, in2, in3, out0, out1); \ + MUL2(in4, in5, in6, in7, out2, out3); \ +} while (0) + +/* Description : Sign extend halfword elements from right half of the vector + * Arguments : Input - in (halfword vector) + * Output - out (sign extended word vector) + * Return Type - signed word + * Details : Sign bit of halfword elements from input vector 'in' is + * extracted and interleaved with same vector 'in0' to generate + * 4 word elements keeping sign intact + */ +#define UNPCK_R_SH_SW(in, out) do { \ + const v8i16 sign_m = __msa_clti_s_h((v8i16)in, 0); \ + out = (v4i32)__msa_ilvr_h(sign_m, (v8i16)in); \ +} while (0) + +/* Description : Sign extend halfword elements from input vector and return + * the result in pair of vectors + * Arguments : Input - in (halfword vector) + * Outputs - out0, out1 (sign extended word vectors) + * Return Type - signed word + * Details : Sign bit of halfword elements from input vector 'in' is + * extracted and interleaved right with same vector 'in0' to + * generate 4 signed word elements in 'out0' + * Then interleaved left with same vector 'in0' to + * generate 4 signed word elements in 'out1' + */ +#define UNPCK_SH_SW(in, out0, out1) do { \ + const v8i16 tmp_m = __msa_clti_s_h((v8i16)in, 0); \ + ILVRL_H2_SW(tmp_m, in, out0, out1); \ +} while (0) + +/* Description : Butterfly of 4 input vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1, out2, out3 + * Details : Butterfly operation + */ +#define BUTTERFLY_4(in0, in1, in2, in3, out0, out1, out2, out3) do { \ + out0 = in0 + in3; \ + out1 = in1 + in2; \ + out2 = in1 - in2; \ + out3 = in0 - in3; \ +} while (0) + +/* Description : Transpose 16x4 block into 4x16 with byte elements in vectors + * Arguments : Inputs - in0, in1, in2, in3, in4, in5, in6, in7, + * in8, in9, in10, in11, in12, in13, in14, in15 + * Outputs - out0, out1, out2, out3 + * Return Type - unsigned byte + */ +#define TRANSPOSE16x4_UB_UB(in0, in1, in2, in3, in4, in5, in6, in7, \ + in8, in9, in10, in11, in12, in13, in14, in15, \ + out0, out1, out2, out3) do { \ + v2i64 tmp0_m, tmp1_m, tmp2_m, tmp3_m, tmp4_m, tmp5_m; \ + ILVEV_W2_SD(in0, in4, in8, in12, tmp2_m, tmp3_m); \ + ILVEV_W2_SD(in1, in5, in9, in13, tmp0_m, tmp1_m); \ + ILVEV_D2_UB(tmp2_m, tmp3_m, tmp0_m, tmp1_m, out1, out3); \ + ILVEV_W2_SD(in2, in6, in10, in14, tmp4_m, tmp5_m); \ + ILVEV_W2_SD(in3, in7, in11, in15, tmp0_m, tmp1_m); \ + ILVEV_D2_SD(tmp4_m, tmp5_m, tmp0_m, tmp1_m, tmp2_m, tmp3_m); \ + ILVEV_B2_SD(out1, out3, tmp2_m, tmp3_m, tmp0_m, tmp1_m); \ + ILVEVOD_H2_UB(tmp0_m, tmp1_m, tmp0_m, tmp1_m, out0, out2); \ + ILVOD_B2_SD(out1, out3, tmp2_m, tmp3_m, tmp0_m, tmp1_m); \ + ILVEVOD_H2_UB(tmp0_m, tmp1_m, tmp0_m, tmp1_m, out1, out3); \ +} while (0) + +/* Description : Transpose 16x8 block into 8x16 with byte elements in vectors + * Arguments : Inputs - in0, in1, in2, in3, in4, in5, in6, in7, + * in8, in9, in10, in11, in12, in13, in14, in15 + * Outputs - out0, out1, out2, out3, out4, out5, out6, out7 + * Return Type - unsigned byte + */ +#define TRANSPOSE16x8_UB_UB(in0, in1, in2, in3, in4, in5, in6, in7, \ + in8, in9, in10, in11, in12, in13, in14, in15, \ + out0, out1, out2, out3, out4, out5, \ + out6, out7) do { \ + v8i16 tmp0_m, tmp1_m, tmp4_m, tmp5_m, tmp6_m, tmp7_m; \ + v4i32 tmp2_m, tmp3_m; \ + ILVEV_D2_UB(in0, in8, in1, in9, out7, out6); \ + ILVEV_D2_UB(in2, in10, in3, in11, out5, out4); \ + ILVEV_D2_UB(in4, in12, in5, in13, out3, out2); \ + ILVEV_D2_UB(in6, in14, in7, in15, out1, out0); \ + ILVEV_B2_SH(out7, out6, out5, out4, tmp0_m, tmp1_m); \ + ILVOD_B2_SH(out7, out6, out5, out4, tmp4_m, tmp5_m); \ + ILVEV_B2_UB(out3, out2, out1, out0, out5, out7); \ + ILVOD_B2_SH(out3, out2, out1, out0, tmp6_m, tmp7_m); \ + ILVEV_H2_SW(tmp0_m, tmp1_m, out5, out7, tmp2_m, tmp3_m); \ + ILVEVOD_W2_UB(tmp2_m, tmp3_m, tmp2_m, tmp3_m, out0, out4); \ + ILVOD_H2_SW(tmp0_m, tmp1_m, out5, out7, tmp2_m, tmp3_m); \ + ILVEVOD_W2_UB(tmp2_m, tmp3_m, tmp2_m, tmp3_m, out2, out6); \ + ILVEV_H2_SW(tmp4_m, tmp5_m, tmp6_m, tmp7_m, tmp2_m, tmp3_m); \ + ILVEVOD_W2_UB(tmp2_m, tmp3_m, tmp2_m, tmp3_m, out1, out5); \ + ILVOD_H2_SW(tmp4_m, tmp5_m, tmp6_m, tmp7_m, tmp2_m, tmp3_m); \ + ILVEVOD_W2_UB(tmp2_m, tmp3_m, tmp2_m, tmp3_m, out3, out7); \ +} while (0) + +/* Description : Transpose 4x4 block with word elements in vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1, out2, out3 + * Return Type - as per RTYPE + */ +#define TRANSPOSE4x4_W(RTYPE, in0, in1, in2, in3, \ + out0, out1, out2, out3) do { \ + v4i32 s0_m, s1_m, s2_m, s3_m; \ + ILVRL_W2_SW(in1, in0, s0_m, s1_m); \ + ILVRL_W2_SW(in3, in2, s2_m, s3_m); \ + out0 = (RTYPE)__msa_ilvr_d((v2i64)s2_m, (v2i64)s0_m); \ + out1 = (RTYPE)__msa_ilvl_d((v2i64)s2_m, (v2i64)s0_m); \ + out2 = (RTYPE)__msa_ilvr_d((v2i64)s3_m, (v2i64)s1_m); \ + out3 = (RTYPE)__msa_ilvl_d((v2i64)s3_m, (v2i64)s1_m); \ +} while (0) +#define TRANSPOSE4x4_SW_SW(...) TRANSPOSE4x4_W(v4i32, __VA_ARGS__) + +/* Description : Add block 4x4 + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + * Details : Least significant 4 bytes from each input vector are added to + * the destination bytes, clipped between 0-255 and stored. + */ +#define ADDBLK_ST4x4_UB(in0, in1, in2, in3, pdst, stride) do { \ + uint32_t src0_m, src1_m, src2_m, src3_m; \ + v8i16 inp0_m, inp1_m, res0_m, res1_m; \ + v16i8 dst0_m = { 0 }; \ + v16i8 dst1_m = { 0 }; \ + const v16i8 zero_m = { 0 }; \ + ILVR_D2_SH(in1, in0, in3, in2, inp0_m, inp1_m); \ + LW4(pdst, stride, src0_m, src1_m, src2_m, src3_m); \ + INSERT_W2_SB(src0_m, src1_m, dst0_m); \ + INSERT_W2_SB(src2_m, src3_m, dst1_m); \ + ILVR_B2_SH(zero_m, dst0_m, zero_m, dst1_m, res0_m, res1_m); \ + ADD2(res0_m, inp0_m, res1_m, inp1_m, res0_m, res1_m); \ + CLIP_SH2_0_255(res0_m, res1_m); \ + PCKEV_B2_SB(res0_m, res0_m, res1_m, res1_m, dst0_m, dst1_m); \ + ST4x4_UB(dst0_m, dst1_m, 0, 1, 0, 1, pdst, stride); \ +} while (0) + +/* Description : Pack even byte elements, extract 0 & 2 index words from pair + * of results and store 4 words in destination memory as per + * stride + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + */ +#define PCKEV_ST4x4_UB(in0, in1, in2, in3, pdst, stride) do { \ + v16i8 tmp0_m, tmp1_m; \ + PCKEV_B2_SB(in1, in0, in3, in2, tmp0_m, tmp1_m); \ + ST4x4_UB(tmp0_m, tmp1_m, 0, 2, 0, 2, pdst, stride); \ +} while (0) + +/* Description : average with rounding (in0 + in1 + 1) / 2. + * Arguments : Inputs - in0, in1, in2, in3, + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Each unsigned byte element from 'in0' vector is added with + * each unsigned byte element from 'in1' vector. Then the average + * with rounding is calculated and written to 'out0' + */ +#define AVER_UB2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_aver_u_b((v16u8)in0, (v16u8)in1); \ + out1 = (RTYPE)__msa_aver_u_b((v16u8)in2, (v16u8)in3); \ +} while (0) +#define AVER_UB2_UB(...) AVER_UB2(v16u8, __VA_ARGS__) + +#endif /* WEBP_DSP_MSA_MACRO_H_ */ diff --git a/3rdparty/libwebp/dsp/neon.h b/3rdparty/libwebp/dsp/neon.h new file mode 100644 index 0000000000..3b548a6855 --- /dev/null +++ b/3rdparty/libwebp/dsp/neon.h @@ -0,0 +1,100 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON common code. + +#ifndef WEBP_DSP_NEON_H_ +#define WEBP_DSP_NEON_H_ + +#include + +#include "./dsp.h" + +// Right now, some intrinsics functions seem slower, so we disable them +// everywhere except aarch64 where the inline assembly is incompatible. +#if defined(__aarch64__) +#define WEBP_USE_INTRINSICS // use intrinsics when possible +#endif + +#define INIT_VECTOR2(v, a, b) do { \ + v.val[0] = a; \ + v.val[1] = b; \ +} while (0) + +#define INIT_VECTOR3(v, a, b, c) do { \ + v.val[0] = a; \ + v.val[1] = b; \ + v.val[2] = c; \ +} while (0) + +#define INIT_VECTOR4(v, a, b, c, d) do { \ + v.val[0] = a; \ + v.val[1] = b; \ + v.val[2] = c; \ + v.val[3] = d; \ +} while (0) + +// if using intrinsics, this flag avoids some functions that make gcc-4.6.3 +// crash ("internal compiler error: in immed_double_const, at emit-rtl."). +// (probably similar to gcc.gnu.org/bugzilla/show_bug.cgi?id=48183) +#if !(LOCAL_GCC_PREREQ(4,8) || defined(__aarch64__)) +#define WORK_AROUND_GCC +#endif + +static WEBP_INLINE int32x4x4_t Transpose4x4(const int32x4x4_t rows) { + uint64x2x2_t row01, row23; + + row01.val[0] = vreinterpretq_u64_s32(rows.val[0]); + row01.val[1] = vreinterpretq_u64_s32(rows.val[1]); + row23.val[0] = vreinterpretq_u64_s32(rows.val[2]); + row23.val[1] = vreinterpretq_u64_s32(rows.val[3]); + // Transpose 64-bit values (there's no vswp equivalent) + { + const uint64x1_t row0h = vget_high_u64(row01.val[0]); + const uint64x1_t row2l = vget_low_u64(row23.val[0]); + const uint64x1_t row1h = vget_high_u64(row01.val[1]); + const uint64x1_t row3l = vget_low_u64(row23.val[1]); + row01.val[0] = vcombine_u64(vget_low_u64(row01.val[0]), row2l); + row23.val[0] = vcombine_u64(row0h, vget_high_u64(row23.val[0])); + row01.val[1] = vcombine_u64(vget_low_u64(row01.val[1]), row3l); + row23.val[1] = vcombine_u64(row1h, vget_high_u64(row23.val[1])); + } + { + const int32x4x2_t out01 = vtrnq_s32(vreinterpretq_s32_u64(row01.val[0]), + vreinterpretq_s32_u64(row01.val[1])); + const int32x4x2_t out23 = vtrnq_s32(vreinterpretq_s32_u64(row23.val[0]), + vreinterpretq_s32_u64(row23.val[1])); + int32x4x4_t out; + out.val[0] = out01.val[0]; + out.val[1] = out01.val[1]; + out.val[2] = out23.val[0]; + out.val[3] = out23.val[1]; + return out; + } +} + +#if 0 // Useful debug macro. +#include +#define PRINT_REG(REG, SIZE) do { \ + int i; \ + printf("%s \t[%d]: 0x", #REG, SIZE); \ + if (SIZE == 8) { \ + uint8_t _tmp[8]; \ + vst1_u8(_tmp, (REG)); \ + for (i = 0; i < 8; ++i) printf("%.2x ", _tmp[i]); \ + } else if (SIZE == 16) { \ + uint16_t _tmp[4]; \ + vst1_u16(_tmp, (REG)); \ + for (i = 0; i < 4; ++i) printf("%.4x ", _tmp[i]); \ + } \ + printf("\n"); \ +} while (0) +#endif + +#endif // WEBP_DSP_NEON_H_ diff --git a/3rdparty/libwebp/dsp/rescaler.c b/3rdparty/libwebp/dsp/rescaler.c new file mode 100644 index 0000000000..0f54502352 --- /dev/null +++ b/3rdparty/libwebp/dsp/rescaler.c @@ -0,0 +1,244 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "./dsp.h" +#include "../utils/rescaler_utils.h" + +//------------------------------------------------------------------------------ +// Implementations of critical functions ImportRow / ExportRow + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) + +//------------------------------------------------------------------------------ +// Row import + +void WebPRescalerImportRowExpandC(WebPRescaler* const wrk, const uint8_t* src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + int channel; + assert(!WebPRescalerInputDone(wrk)); + assert(wrk->x_expand); + for (channel = 0; channel < x_stride; ++channel) { + int x_in = channel; + int x_out = channel; + // simple bilinear interpolation + int accum = wrk->x_add; + int left = src[x_in]; + int right = (wrk->src_width > 1) ? src[x_in + x_stride] : left; + x_in += x_stride; + while (1) { + wrk->frow[x_out] = right * wrk->x_add + (left - right) * accum; + x_out += x_stride; + if (x_out >= x_out_max) break; + accum -= wrk->x_sub; + if (accum < 0) { + left = right; + x_in += x_stride; + assert(x_in < wrk->src_width * x_stride); + right = src[x_in]; + accum += wrk->x_add; + } + } + assert(wrk->x_sub == 0 /* <- special case for src_width=1 */ || accum == 0); + } +} + +void WebPRescalerImportRowShrinkC(WebPRescaler* const wrk, const uint8_t* src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + int channel; + assert(!WebPRescalerInputDone(wrk)); + assert(!wrk->x_expand); + for (channel = 0; channel < x_stride; ++channel) { + int x_in = channel; + int x_out = channel; + uint32_t sum = 0; + int accum = 0; + while (x_out < x_out_max) { + uint32_t base = 0; + accum += wrk->x_add; + while (accum > 0) { + accum -= wrk->x_sub; + assert(x_in < wrk->src_width * x_stride); + base = src[x_in]; + sum += base; + x_in += x_stride; + } + { // Emit next horizontal pixel. + const rescaler_t frac = base * (-accum); + wrk->frow[x_out] = sum * wrk->x_sub - frac; + // fresh fractional start for next pixel + sum = (int)MULT_FIX(frac, wrk->fx_scale); + } + x_out += x_stride; + } + assert(accum == 0); + } +} + +//------------------------------------------------------------------------------ +// Row export + +void WebPRescalerExportRowExpandC(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + for (x_out = 0; x_out < x_out_max; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + for (x_out = 0; x_out < x_out_max; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + } + } +} + +void WebPRescalerExportRowShrinkC(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + for (x_out = 0; x_out < x_out_max; ++x_out) { + const uint32_t frac = (uint32_t)MULT_FIX(frow[x_out], yscale); + const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + irow[x_out] = frac; // new fractional start + } + } else { + for (x_out = 0; x_out < x_out_max; ++x_out) { + const int v = (int)MULT_FIX(irow[x_out], wrk->fxy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + irow[x_out] = 0; + } + } +} + +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ +// Main entry calls + +void WebPRescalerImportRow(WebPRescaler* const wrk, const uint8_t* src) { + assert(!WebPRescalerInputDone(wrk)); + if (!wrk->x_expand) { + WebPRescalerImportRowShrink(wrk, src); + } else { + WebPRescalerImportRowExpand(wrk, src); + } +} + +void WebPRescalerExportRow(WebPRescaler* const wrk) { + if (wrk->y_accum <= 0) { + assert(!WebPRescalerOutputDone(wrk)); + if (wrk->y_expand) { + WebPRescalerExportRowExpand(wrk); + } else if (wrk->fxy_scale) { + WebPRescalerExportRowShrink(wrk); + } else { // special case + int i; + assert(wrk->src_height == wrk->dst_height && wrk->x_add == 1); + assert(wrk->src_width == 1 && wrk->dst_width <= 2); + for (i = 0; i < wrk->num_channels * wrk->dst_width; ++i) { + wrk->dst[i] = wrk->irow[i]; + wrk->irow[i] = 0; + } + } + wrk->y_accum += wrk->y_add; + wrk->dst += wrk->dst_stride; + ++wrk->dst_y; + } +} + +//------------------------------------------------------------------------------ + +WebPRescalerImportRowFunc WebPRescalerImportRowExpand; +WebPRescalerImportRowFunc WebPRescalerImportRowShrink; + +WebPRescalerExportRowFunc WebPRescalerExportRowExpand; +WebPRescalerExportRowFunc WebPRescalerExportRowShrink; + +extern void WebPRescalerDspInitSSE2(void); +extern void WebPRescalerDspInitMIPS32(void); +extern void WebPRescalerDspInitMIPSdspR2(void); +extern void WebPRescalerDspInitMSA(void); +extern void WebPRescalerDspInitNEON(void); + +static volatile VP8CPUInfo rescaler_last_cpuinfo_used = + (VP8CPUInfo)&rescaler_last_cpuinfo_used; + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInit(void) { + if (rescaler_last_cpuinfo_used == VP8GetCPUInfo) return; + + WebPRescalerImportRowExpand = WebPRescalerImportRowExpandC; + WebPRescalerImportRowShrink = WebPRescalerImportRowShrinkC; + WebPRescalerExportRowExpand = WebPRescalerExportRowExpandC; + WebPRescalerExportRowShrink = WebPRescalerExportRowShrinkC; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPRescalerDspInitSSE2(); + } +#endif +#if defined(WEBP_USE_NEON) + if (VP8GetCPUInfo(kNEON)) { + WebPRescalerDspInitNEON(); + } +#endif +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + WebPRescalerDspInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPRescalerDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + WebPRescalerDspInitMSA(); + } +#endif + } + rescaler_last_cpuinfo_used = VP8GetCPUInfo; +} diff --git a/3rdparty/libwebp/dsp/rescaler_mips32.c b/3rdparty/libwebp/dsp/rescaler_mips32.c new file mode 100644 index 0000000000..e09ad5d19f --- /dev/null +++ b/3rdparty/libwebp/dsp/rescaler_mips32.c @@ -0,0 +1,291 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of rescaling functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS32) + +#include +#include "../utils/rescaler_utils.h" + +//------------------------------------------------------------------------------ +// Row import + +static void ImportRowShrink(WebPRescaler* const wrk, const uint8_t* src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int fx_scale = wrk->fx_scale; + const int x_add = wrk->x_add; + const int x_sub = wrk->x_sub; + const int x_stride1 = x_stride << 2; + int channel; + assert(!wrk->x_expand); + assert(!WebPRescalerInputDone(wrk)); + + for (channel = 0; channel < x_stride; ++channel) { + const uint8_t* src1 = src + channel; + rescaler_t* frow = wrk->frow + channel; + int temp1, temp2, temp3; + int base, frac, sum; + int accum, accum1; + int loop_c = x_out_max - channel; + + __asm__ volatile ( + "li %[temp1], 0x8000 \n\t" + "li %[temp2], 0x10000 \n\t" + "li %[sum], 0 \n\t" + "li %[accum], 0 \n\t" + "1: \n\t" + "addu %[accum], %[accum], %[x_add] \n\t" + "li %[base], 0 \n\t" + "blez %[accum], 3f \n\t" + "2: \n\t" + "lbu %[base], 0(%[src1]) \n\t" + "subu %[accum], %[accum], %[x_sub] \n\t" + "addu %[src1], %[src1], %[x_stride] \n\t" + "addu %[sum], %[sum], %[base] \n\t" + "bgtz %[accum], 2b \n\t" + "3: \n\t" + "negu %[accum1], %[accum] \n\t" + "mul %[frac], %[base], %[accum1] \n\t" + "mul %[temp3], %[sum], %[x_sub] \n\t" + "subu %[loop_c], %[loop_c], %[x_stride] \n\t" + "mult %[temp1], %[temp2] \n\t" + "maddu %[frac], %[fx_scale] \n\t" + "mfhi %[sum] \n\t" + "subu %[temp3], %[temp3], %[frac] \n\t" + "sw %[temp3], 0(%[frow]) \n\t" + "addu %[frow], %[frow], %[x_stride1] \n\t" + "bgtz %[loop_c], 1b \n\t" + : [accum]"=&r"(accum), [src1]"+r"(src1), [temp3]"=&r"(temp3), + [sum]"=&r"(sum), [base]"=&r"(base), [frac]"=&r"(frac), + [frow]"+r"(frow), [accum1]"=&r"(accum1), + [temp2]"=&r"(temp2), [temp1]"=&r"(temp1) + : [x_stride]"r"(x_stride), [fx_scale]"r"(fx_scale), + [x_sub]"r"(x_sub), [x_add]"r"(x_add), + [loop_c]"r"(loop_c), [x_stride1]"r"(x_stride1) + : "memory", "hi", "lo" + ); + assert(accum == 0); + } +} + +static void ImportRowExpand(WebPRescaler* const wrk, const uint8_t* src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int x_add = wrk->x_add; + const int x_sub = wrk->x_sub; + const int src_width = wrk->src_width; + const int x_stride1 = x_stride << 2; + int channel; + assert(wrk->x_expand); + assert(!WebPRescalerInputDone(wrk)); + + for (channel = 0; channel < x_stride; ++channel) { + const uint8_t* src1 = src + channel; + rescaler_t* frow = wrk->frow + channel; + int temp1, temp2, temp3, temp4; + int frac; + int accum; + int x_out = channel; + + __asm__ volatile ( + "addiu %[temp3], %[src_width], -1 \n\t" + "lbu %[temp2], 0(%[src1]) \n\t" + "addu %[src1], %[src1], %[x_stride] \n\t" + "bgtz %[temp3], 0f \n\t" + "addiu %[temp1], %[temp2], 0 \n\t" + "b 3f \n\t" + "0: \n\t" + "lbu %[temp1], 0(%[src1]) \n\t" + "3: \n\t" + "addiu %[accum], %[x_add], 0 \n\t" + "1: \n\t" + "subu %[temp3], %[temp2], %[temp1] \n\t" + "mul %[temp3], %[temp3], %[accum] \n\t" + "mul %[temp4], %[temp1], %[x_add] \n\t" + "addu %[temp3], %[temp4], %[temp3] \n\t" + "sw %[temp3], 0(%[frow]) \n\t" + "addu %[frow], %[frow], %[x_stride1] \n\t" + "addu %[x_out], %[x_out], %[x_stride] \n\t" + "subu %[temp3], %[x_out], %[x_out_max] \n\t" + "bgez %[temp3], 2f \n\t" + "subu %[accum], %[accum], %[x_sub] \n\t" + "bgez %[accum], 4f \n\t" + "addiu %[temp2], %[temp1], 0 \n\t" + "addu %[src1], %[src1], %[x_stride] \n\t" + "lbu %[temp1], 0(%[src1]) \n\t" + "addu %[accum], %[accum], %[x_add] \n\t" + "4: \n\t" + "b 1b \n\t" + "2: \n\t" + : [src1]"+r"(src1), [accum]"=&r"(accum), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), + [x_out]"+r"(x_out), [frac]"=&r"(frac), [frow]"+r"(frow) + : [x_stride]"r"(x_stride), [x_add]"r"(x_add), [x_sub]"r"(x_sub), + [x_stride1]"r"(x_stride1), [src_width]"r"(src_width), + [x_out_max]"r"(x_out_max) + : "memory", "hi", "lo" + ); + assert(wrk->x_sub == 0 /* <- special case for src_width=1 */ || accum == 0); + } +} + +//------------------------------------------------------------------------------ +// Row export + +static void ExportRowExpand(WebPRescaler* const wrk) { + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* frow = wrk->frow; + int temp0, temp1, temp3, temp4, temp5, loop_end; + const int temp2 = (int)wrk->fy_scale; + const int temp6 = x_out_max << 2; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "addiu %[dst], %[dst], 1 \n\t" + "addiu %[frow], %[frow], 4 \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[temp0], %[temp2] \n\t" + "mfhi %[temp5] \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [dst]"+r"(dst), [loop_end]"=&r"(loop_end) + : [temp2]"r"(temp2), [temp6]"r"(temp6) + : "memory", "hi", "lo" + ); + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "lw %[temp1], 0(%[irow]) \n\t" + "addiu %[dst], %[dst], 1 \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[A], %[temp0] \n\t" + "maddu %[B], %[temp1] \n\t" + "addiu %[frow], %[frow], 4 \n\t" + "addiu %[irow], %[irow], 4 \n\t" + "mfhi %[temp5] \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[temp5], %[temp2] \n\t" + "mfhi %[temp5] \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [irow]"+r"(irow), [dst]"+r"(dst), [loop_end]"=&r"(loop_end) + : [temp2]"r"(temp2), [temp6]"r"(temp6), [A]"r"(A), [B]"r"(B) + : "memory", "hi", "lo" + ); + } +} + +static void ExportRowShrink(WebPRescaler* const wrk) { + const int x_out_max = wrk->dst_width * wrk->num_channels; + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const rescaler_t* frow = wrk->frow; + const int yscale = wrk->fy_scale * (-wrk->y_accum); + int temp0, temp1, temp3, temp4, temp5, loop_end; + const int temp2 = (int)wrk->fxy_scale; + const int temp6 = x_out_max << 2; + + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + assert(wrk->fxy_scale != 0); + if (yscale) { + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "mult %[temp3], %[temp4] \n\t" + "addiu %[frow], %[frow], 4 \n\t" + "maddu %[temp0], %[yscale] \n\t" + "mfhi %[temp1] \n\t" + "lw %[temp0], 0(%[irow]) \n\t" + "addiu %[dst], %[dst], 1 \n\t" + "addiu %[irow], %[irow], 4 \n\t" + "subu %[temp0], %[temp0], %[temp1] \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[temp0], %[temp2] \n\t" + "mfhi %[temp5] \n\t" + "sw %[temp1], -4(%[irow]) \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [irow]"+r"(irow), [dst]"+r"(dst), [loop_end]"=&r"(loop_end) + : [temp2]"r"(temp2), [yscale]"r"(yscale), [temp6]"r"(temp6) + : "memory", "hi", "lo" + ); + } else { + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[irow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[irow]) \n\t" + "addiu %[dst], %[dst], 1 \n\t" + "addiu %[irow], %[irow], 4 \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[temp0], %[temp2] \n\t" + "mfhi %[temp5] \n\t" + "sw $zero, -4(%[irow]) \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[irow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [irow]"+r"(irow), + [dst]"+r"(dst), [loop_end]"=&r"(loop_end) + : [temp2]"r"(temp2), [temp6]"r"(temp6) + : "memory", "hi", "lo" + ); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPRescalerDspInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitMIPS32(void) { + WebPRescalerImportRowExpand = ImportRowExpand; + WebPRescalerImportRowShrink = ImportRowShrink; + WebPRescalerExportRowExpand = ExportRowExpand; + WebPRescalerExportRowShrink = ExportRowShrink; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/3rdparty/libwebp/dsp/rescaler_mips_dsp_r2.c b/3rdparty/libwebp/dsp/rescaler_mips_dsp_r2.c new file mode 100644 index 0000000000..2308d64544 --- /dev/null +++ b/3rdparty/libwebp/dsp/rescaler_mips_dsp_r2.c @@ -0,0 +1,314 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of rescaling functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include +#include "../utils/rescaler_utils.h" + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) + +//------------------------------------------------------------------------------ +// Row export + +static void ExportRowShrink(WebPRescaler* const wrk) { + int i; + const int x_out_max = wrk->dst_width * wrk->num_channels; + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const rescaler_t* frow = wrk->frow; + const int yscale = wrk->fy_scale * (-wrk->y_accum); + int temp0, temp1, temp2, temp3, temp4, temp5, loop_end; + const int temp7 = (int)wrk->fxy_scale; + const int temp6 = (x_out_max & ~0x3) << 2; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + assert(wrk->fxy_scale != 0); + if (yscale) { + if (x_out_max >= 4) { + int temp8, temp9, temp10, temp11; + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "lw %[temp1], 4(%[frow]) \n\t" + "lw %[temp2], 8(%[frow]) \n\t" + "lw %[temp5], 12(%[frow]) \n\t" + "mult $ac0, %[temp3], %[temp4] \n\t" + "maddu $ac0, %[temp0], %[yscale] \n\t" + "mult $ac1, %[temp3], %[temp4] \n\t" + "maddu $ac1, %[temp1], %[yscale] \n\t" + "mult $ac2, %[temp3], %[temp4] \n\t" + "maddu $ac2, %[temp2], %[yscale] \n\t" + "mult $ac3, %[temp3], %[temp4] \n\t" + "maddu $ac3, %[temp5], %[yscale] \n\t" + "addiu %[frow], %[frow], 16 \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp5], $ac3 \n\t" + "lw %[temp8], 0(%[irow]) \n\t" + "lw %[temp9], 4(%[irow]) \n\t" + "lw %[temp10], 8(%[irow]) \n\t" + "lw %[temp11], 12(%[irow]) \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "addiu %[irow], %[irow], 16 \n\t" + "subu %[temp8], %[temp8], %[temp0] \n\t" + "subu %[temp9], %[temp9], %[temp1] \n\t" + "subu %[temp10], %[temp10], %[temp2] \n\t" + "subu %[temp11], %[temp11], %[temp5] \n\t" + "mult $ac0, %[temp3], %[temp4] \n\t" + "maddu $ac0, %[temp8], %[temp7] \n\t" + "mult $ac1, %[temp3], %[temp4] \n\t" + "maddu $ac1, %[temp9], %[temp7] \n\t" + "mult $ac2, %[temp3], %[temp4] \n\t" + "maddu $ac2, %[temp10], %[temp7] \n\t" + "mult $ac3, %[temp3], %[temp4] \n\t" + "maddu $ac3, %[temp11], %[temp7] \n\t" + "mfhi %[temp8], $ac0 \n\t" + "mfhi %[temp9], $ac1 \n\t" + "mfhi %[temp10], $ac2 \n\t" + "mfhi %[temp11], $ac3 \n\t" + "sw %[temp0], -16(%[irow]) \n\t" + "sw %[temp1], -12(%[irow]) \n\t" + "sw %[temp2], -8(%[irow]) \n\t" + "sw %[temp5], -4(%[irow]) \n\t" + "sb %[temp8], -4(%[dst]) \n\t" + "sb %[temp9], -3(%[dst]) \n\t" + "sb %[temp10], -2(%[dst]) \n\t" + "sb %[temp11], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [irow]"+r"(irow), [dst]"+r"(dst), [loop_end]"=&r"(loop_end), + [temp8]"=&r"(temp8), [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), + [temp11]"=&r"(temp11), [temp2]"=&r"(temp2) + : [temp7]"r"(temp7), [yscale]"r"(yscale), [temp6]"r"(temp6) + : "memory", "hi", "lo", "$ac1hi", "$ac1lo", + "$ac2hi", "$ac2lo", "$ac3hi", "$ac3lo" + ); + } + for (i = 0; i < (x_out_max & 0x3); ++i) { + const uint32_t frac = (uint32_t)MULT_FIX(*frow++, yscale); + const int v = (int)MULT_FIX(*irow - frac, wrk->fxy_scale); + assert(v >= 0 && v <= 255); + *dst++ = v; + *irow++ = frac; // new fractional start + } + } else { + if (x_out_max >= 4) { + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[irow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[irow]) \n\t" + "lw %[temp1], 4(%[irow]) \n\t" + "lw %[temp2], 8(%[irow]) \n\t" + "lw %[temp5], 12(%[irow]) \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "addiu %[irow], %[irow], 16 \n\t" + "mult $ac0, %[temp3], %[temp4] \n\t" + "maddu $ac0, %[temp0], %[temp7] \n\t" + "mult $ac1, %[temp3], %[temp4] \n\t" + "maddu $ac1, %[temp1], %[temp7] \n\t" + "mult $ac2, %[temp3], %[temp4] \n\t" + "maddu $ac2, %[temp2], %[temp7] \n\t" + "mult $ac3, %[temp3], %[temp4] \n\t" + "maddu $ac3, %[temp5], %[temp7] \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp5], $ac3 \n\t" + "sw $zero, -16(%[irow]) \n\t" + "sw $zero, -12(%[irow]) \n\t" + "sw $zero, -8(%[irow]) \n\t" + "sw $zero, -4(%[irow]) \n\t" + "sb %[temp0], -4(%[dst]) \n\t" + "sb %[temp1], -3(%[dst]) \n\t" + "sb %[temp2], -2(%[dst]) \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[irow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [irow]"+r"(irow), + [dst]"+r"(dst), [loop_end]"=&r"(loop_end), [temp2]"=&r"(temp2) + : [temp7]"r"(temp7), [temp6]"r"(temp6) + : "memory", "hi", "lo", "$ac1hi", "$ac1lo", + "$ac2hi", "$ac2lo", "$ac3hi", "$ac3lo" + ); + } + for (i = 0; i < (x_out_max & 0x3); ++i) { + const int v = (int)MULT_FIX(*irow, wrk->fxy_scale); + assert(v >= 0 && v <= 255); + *dst++ = v; + *irow++ = 0; + } + } +} + +static void ExportRowExpand(WebPRescaler* const wrk) { + int i; + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* frow = wrk->frow; + int temp0, temp1, temp2, temp3, temp4, temp5, loop_end; + const int temp6 = (x_out_max & ~0x3) << 2; + const int temp7 = (int)wrk->fy_scale; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + if (x_out_max >= 4) { + __asm__ volatile ( + "li %[temp4], 0x10000 \n\t" + "li %[temp5], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "lw %[temp1], 4(%[frow]) \n\t" + "lw %[temp2], 8(%[frow]) \n\t" + "lw %[temp3], 12(%[frow]) \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "addiu %[frow], %[frow], 16 \n\t" + "mult $ac0, %[temp4], %[temp5] \n\t" + "maddu $ac0, %[temp0], %[temp7] \n\t" + "mult $ac1, %[temp4], %[temp5] \n\t" + "maddu $ac1, %[temp1], %[temp7] \n\t" + "mult $ac2, %[temp4], %[temp5] \n\t" + "maddu $ac2, %[temp2], %[temp7] \n\t" + "mult $ac3, %[temp4], %[temp5] \n\t" + "maddu $ac3, %[temp3], %[temp7] \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp3], $ac3 \n\t" + "sb %[temp0], -4(%[dst]) \n\t" + "sb %[temp1], -3(%[dst]) \n\t" + "sb %[temp2], -2(%[dst]) \n\t" + "sb %[temp3], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [dst]"+r"(dst), [loop_end]"=&r"(loop_end), [temp2]"=&r"(temp2) + : [temp7]"r"(temp7), [temp6]"r"(temp6) + : "memory", "hi", "lo", "$ac1hi", "$ac1lo", + "$ac2hi", "$ac2lo", "$ac3hi", "$ac3lo" + ); + } + for (i = 0; i < (x_out_max & 0x3); ++i) { + const uint32_t J = *frow++; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + assert(v >= 0 && v <= 255); + *dst++ = v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + if (x_out_max >= 4) { + int temp8, temp9, temp10, temp11; + __asm__ volatile ( + "li %[temp8], 0x10000 \n\t" + "li %[temp9], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "lw %[temp1], 4(%[frow]) \n\t" + "lw %[temp2], 8(%[frow]) \n\t" + "lw %[temp3], 12(%[frow]) \n\t" + "lw %[temp4], 0(%[irow]) \n\t" + "lw %[temp5], 4(%[irow]) \n\t" + "lw %[temp10], 8(%[irow]) \n\t" + "lw %[temp11], 12(%[irow]) \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "mult $ac0, %[temp8], %[temp9] \n\t" + "maddu $ac0, %[A], %[temp0] \n\t" + "maddu $ac0, %[B], %[temp4] \n\t" + "mult $ac1, %[temp8], %[temp9] \n\t" + "maddu $ac1, %[A], %[temp1] \n\t" + "maddu $ac1, %[B], %[temp5] \n\t" + "mult $ac2, %[temp8], %[temp9] \n\t" + "maddu $ac2, %[A], %[temp2] \n\t" + "maddu $ac2, %[B], %[temp10] \n\t" + "mult $ac3, %[temp8], %[temp9] \n\t" + "maddu $ac3, %[A], %[temp3] \n\t" + "maddu $ac3, %[B], %[temp11] \n\t" + "addiu %[frow], %[frow], 16 \n\t" + "addiu %[irow], %[irow], 16 \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp3], $ac3 \n\t" + "mult $ac0, %[temp8], %[temp9] \n\t" + "maddu $ac0, %[temp0], %[temp7] \n\t" + "mult $ac1, %[temp8], %[temp9] \n\t" + "maddu $ac1, %[temp1], %[temp7] \n\t" + "mult $ac2, %[temp8], %[temp9] \n\t" + "maddu $ac2, %[temp2], %[temp7] \n\t" + "mult $ac3, %[temp8], %[temp9] \n\t" + "maddu $ac3, %[temp3], %[temp7] \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp3], $ac3 \n\t" + "sb %[temp0], -4(%[dst]) \n\t" + "sb %[temp1], -3(%[dst]) \n\t" + "sb %[temp2], -2(%[dst]) \n\t" + "sb %[temp3], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [irow]"+r"(irow), [dst]"+r"(dst), [loop_end]"=&r"(loop_end), + [temp8]"=&r"(temp8), [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), + [temp11]"=&r"(temp11), [temp2]"=&r"(temp2) + : [temp7]"r"(temp7), [temp6]"r"(temp6), [A]"r"(A), [B]"r"(B) + : "memory", "hi", "lo", "$ac1hi", "$ac1lo", + "$ac2hi", "$ac2lo", "$ac3hi", "$ac3lo" + ); + } + for (i = 0; i < (x_out_max & 0x3); ++i) { + const uint64_t I = (uint64_t)A * *frow++ + + (uint64_t)B * *irow++; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + assert(v >= 0 && v <= 255); + *dst++ = v; + } + } +} + +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPRescalerDspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitMIPSdspR2(void) { + WebPRescalerExportRowExpand = ExportRowExpand; + WebPRescalerExportRowShrink = ExportRowShrink; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/rescaler_msa.c b/3rdparty/libwebp/dsp/rescaler_msa.c new file mode 100644 index 0000000000..2c10e55d8c --- /dev/null +++ b/3rdparty/libwebp/dsp/rescaler_msa.c @@ -0,0 +1,444 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA version of rescaling functions +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MSA) + +#include + +#include "../utils/rescaler_utils.h" +#include "./msa_macro.h" + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) + +#define CALC_MULT_FIX_16(in0, in1, in2, in3, scale, shift, dst) do { \ + v4u32 tmp0, tmp1, tmp2, tmp3; \ + v16u8 t0, t1, t2, t3, t4, t5; \ + v2u64 out0, out1, out2, out3; \ + ILVRL_W2_UW(zero, in0, tmp0, tmp1); \ + ILVRL_W2_UW(zero, in1, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, scale, scale, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, scale, scale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_B2_UB(out1, out0, out3, out2, t0, t1); \ + ILVRL_W2_UW(zero, in2, tmp0, tmp1); \ + ILVRL_W2_UW(zero, in3, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, scale, scale, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, scale, scale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_B2_UB(out1, out0, out3, out2, t2, t3); \ + PCKEV_B2_UB(t1, t0, t3, t2, t4, t5); \ + dst = (v16u8)__msa_pckev_b((v16i8)t5, (v16i8)t4); \ +} while (0) + +#define CALC_MULT_FIX_4(in0, scale, shift, dst) do { \ + v4u32 tmp0, tmp1; \ + v16i8 t0, t1; \ + v2u64 out0, out1; \ + ILVRL_W2_UW(zero, in0, tmp0, tmp1); \ + DOTP_UW2_UD(tmp0, tmp1, scale, scale, out0, out1); \ + SRAR_D2_UD(out0, out1, shift); \ + t0 = __msa_pckev_b((v16i8)out1, (v16i8)out0); \ + t1 = __msa_pckev_b(t0, t0); \ + t0 = __msa_pckev_b(t1, t1); \ + dst = __msa_copy_s_w((v4i32)t0, 0); \ +} while (0) + +#define CALC_MULT_FIX1_16(in0, in1, in2, in3, fyscale, shift, \ + dst0, dst1, dst2, dst3) do { \ + v4u32 tmp0, tmp1, tmp2, tmp3; \ + v2u64 out0, out1, out2, out3; \ + ILVRL_W2_UW(zero, in0, tmp0, tmp1); \ + ILVRL_W2_UW(zero, in1, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, fyscale, fyscale, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, fyscale, fyscale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_W2_UW(out1, out0, out3, out2, dst0, dst1); \ + ILVRL_W2_UW(zero, in2, tmp0, tmp1); \ + ILVRL_W2_UW(zero, in3, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, fyscale, fyscale, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, fyscale, fyscale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_W2_UW(out1, out0, out3, out2, dst2, dst3); \ +} while (0) + +#define CALC_MULT_FIX1_4(in0, scale, shift, dst) do { \ + v4u32 tmp0, tmp1; \ + v2u64 out0, out1; \ + ILVRL_W2_UW(zero, in0, tmp0, tmp1); \ + DOTP_UW2_UD(tmp0, tmp1, scale, scale, out0, out1); \ + SRAR_D2_UD(out0, out1, shift); \ + dst = (v4u32)__msa_pckev_w((v4i32)out1, (v4i32)out0); \ +} while (0) + +#define CALC_MULT_FIX2_16(in0, in1, in2, in3, mult, scale, shift, \ + dst0, dst1) do { \ + v4u32 tmp0, tmp1, tmp2, tmp3; \ + v2u64 out0, out1, out2, out3; \ + ILVRL_W2_UW(in0, in2, tmp0, tmp1); \ + ILVRL_W2_UW(in1, in3, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, mult, mult, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, mult, mult, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + DOTP_UW2_UD(out0, out1, scale, scale, out0, out1); \ + DOTP_UW2_UD(out2, out3, scale, scale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_B2_UB(out1, out0, out3, out2, dst0, dst1); \ +} while (0) + +#define CALC_MULT_FIX2_4(in0, in1, mult, scale, shift, dst) do { \ + v4u32 tmp0, tmp1; \ + v2u64 out0, out1; \ + v16i8 t0, t1; \ + ILVRL_W2_UW(in0, in1, tmp0, tmp1); \ + DOTP_UW2_UD(tmp0, tmp1, mult, mult, out0, out1); \ + SRAR_D2_UD(out0, out1, shift); \ + DOTP_UW2_UD(out0, out1, scale, scale, out0, out1); \ + SRAR_D2_UD(out0, out1, shift); \ + t0 = __msa_pckev_b((v16i8)out1, (v16i8)out0); \ + t1 = __msa_pckev_b(t0, t0); \ + t0 = __msa_pckev_b(t1, t1); \ + dst = __msa_copy_s_w((v4i32)t0, 0); \ +} while (0) + +static WEBP_INLINE void ExportRowExpand_0(const uint32_t* frow, uint8_t* dst, + int length, + WebPRescaler* const wrk) { + const v4u32 scale = (v4u32)__msa_fill_w(wrk->fy_scale); + const v4u32 shift = (v4u32)__msa_fill_w(WEBP_RESCALER_RFIX); + const v4i32 zero = { 0 }; + + while (length >= 16) { + v4u32 src0, src1, src2, src3; + v16u8 out; + LD_UW4(frow, 4, src0, src1, src2, src3); + CALC_MULT_FIX_16(src0, src1, src2, src3, scale, shift, out); + ST_UB(out, dst); + length -= 16; + frow += 16; + dst += 16; + } + if (length > 0) { + int x_out; + if (length >= 12) { + uint32_t val0_m, val1_m, val2_m; + v4u32 src0, src1, src2; + LD_UW3(frow, 4, src0, src1, src2); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + CALC_MULT_FIX_4(src1, scale, shift, val1_m); + CALC_MULT_FIX_4(src2, scale, shift, val2_m); + SW3(val0_m, val1_m, val2_m, dst, 4); + length -= 12; + frow += 12; + dst += 12; + } else if (length >= 8) { + uint32_t val0_m, val1_m; + v4u32 src0, src1; + LD_UW2(frow, 4, src0, src1); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + CALC_MULT_FIX_4(src1, scale, shift, val1_m); + SW2(val0_m, val1_m, dst, 4); + length -= 8; + frow += 8; + dst += 8; + } else if (length >= 4) { + uint32_t val0_m; + const v4u32 src0 = LD_UW(frow); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + SW(val0_m, dst); + length -= 4; + frow += 4; + dst += 4; + } + for (x_out = 0; x_out < length; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + } + } +} + +static WEBP_INLINE void ExportRowExpand_1(const uint32_t* frow, uint32_t* irow, + uint8_t* dst, int length, + WebPRescaler* const wrk) { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + const v4i32 B1 = __msa_fill_w(B); + const v4i32 A1 = __msa_fill_w(A); + const v4i32 AB = __msa_ilvr_w(A1, B1); + const v4u32 scale = (v4u32)__msa_fill_w(wrk->fy_scale); + const v4u32 shift = (v4u32)__msa_fill_w(WEBP_RESCALER_RFIX); + + while (length >= 16) { + v4u32 frow0, frow1, frow2, frow3, irow0, irow1, irow2, irow3; + v16u8 t0, t1, t2, t3, t4, t5; + LD_UW4(frow, 4, frow0, frow1, frow2, frow3); + LD_UW4(irow, 4, irow0, irow1, irow2, irow3); + CALC_MULT_FIX2_16(frow0, frow1, irow0, irow1, AB, scale, shift, t0, t1); + CALC_MULT_FIX2_16(frow2, frow3, irow2, irow3, AB, scale, shift, t2, t3); + PCKEV_B2_UB(t1, t0, t3, t2, t4, t5); + t0 = (v16u8)__msa_pckev_b((v16i8)t5, (v16i8)t4); + ST_UB(t0, dst); + frow += 16; + irow += 16; + dst += 16; + length -= 16; + } + if (length > 0) { + int x_out; + if (length >= 12) { + uint32_t val0_m, val1_m, val2_m; + v4u32 frow0, frow1, frow2, irow0, irow1, irow2; + LD_UW3(frow, 4, frow0, frow1, frow2); + LD_UW3(irow, 4, irow0, irow1, irow2); + CALC_MULT_FIX2_4(frow0, irow0, AB, scale, shift, val0_m); + CALC_MULT_FIX2_4(frow1, irow1, AB, scale, shift, val1_m); + CALC_MULT_FIX2_4(frow2, irow2, AB, scale, shift, val2_m); + SW3(val0_m, val1_m, val2_m, dst, 4); + frow += 12; + irow += 12; + dst += 12; + length -= 12; + } else if (length >= 8) { + uint32_t val0_m, val1_m; + v4u32 frow0, frow1, irow0, irow1; + LD_UW2(frow, 4, frow0, frow1); + LD_UW2(irow, 4, irow0, irow1); + CALC_MULT_FIX2_4(frow0, irow0, AB, scale, shift, val0_m); + CALC_MULT_FIX2_4(frow1, irow1, AB, scale, shift, val1_m); + SW2(val0_m, val1_m, dst, 4); + frow += 4; + irow += 4; + dst += 4; + length -= 4; + } else if (length >= 4) { + uint32_t val0_m; + const v4u32 frow0 = LD_UW(frow + 0); + const v4u32 irow0 = LD_UW(irow + 0); + CALC_MULT_FIX2_4(frow0, irow0, AB, scale, shift, val0_m); + SW(val0_m, dst); + frow += 4; + irow += 4; + dst += 4; + length -= 4; + } + for (x_out = 0; x_out < length; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + } + } +} + +static void RescalerExportRowExpand(WebPRescaler* const wrk) { + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* frow = wrk->frow; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + ExportRowExpand_0(frow, dst, x_out_max, wrk); + } else { + ExportRowExpand_1(frow, irow, dst, x_out_max, wrk); + } +} + +static WEBP_INLINE void ExportRowShrink_0(const uint32_t* frow, uint32_t* irow, + uint8_t* dst, int length, + const uint32_t yscale, + WebPRescaler* const wrk) { + const v4u32 y_scale = (v4u32)__msa_fill_w(yscale); + const v4u32 fxyscale = (v4u32)__msa_fill_w(wrk->fxy_scale); + const v4u32 shiftval = (v4u32)__msa_fill_w(WEBP_RESCALER_RFIX); + const v4i32 zero = { 0 }; + + while (length >= 16) { + v4u32 src0, src1, src2, src3, frac0, frac1, frac2, frac3; + v16u8 out; + LD_UW4(frow, 4, src0, src1, src2, src3); + CALC_MULT_FIX1_16(src0, src1, src2, src3, y_scale, shiftval, + frac0, frac1, frac2, frac3); + LD_UW4(irow, 4, src0, src1, src2, src3); + SUB4(src0, frac0, src1, frac1, src2, frac2, src3, frac3, + src0, src1, src2, src3); + CALC_MULT_FIX_16(src0, src1, src2, src3, fxyscale, shiftval, out); + ST_UB(out, dst); + ST_UW4(frac0, frac1, frac2, frac3, irow, 4); + frow += 16; + irow += 16; + dst += 16; + length -= 16; + } + if (length > 0) { + int x_out; + if (length >= 12) { + uint32_t val0_m, val1_m, val2_m; + v4u32 src0, src1, src2, frac0, frac1, frac2; + LD_UW3(frow, 4, src0, src1, src2); + CALC_MULT_FIX1_4(src0, y_scale, shiftval, frac0); + CALC_MULT_FIX1_4(src1, y_scale, shiftval, frac1); + CALC_MULT_FIX1_4(src2, y_scale, shiftval, frac2); + LD_UW3(irow, 4, src0, src1, src2); + SUB3(src0, frac0, src1, frac1, src2, frac2, src0, src1, src2); + CALC_MULT_FIX_4(src0, fxyscale, shiftval, val0_m); + CALC_MULT_FIX_4(src1, fxyscale, shiftval, val1_m); + CALC_MULT_FIX_4(src2, fxyscale, shiftval, val2_m); + SW3(val0_m, val1_m, val2_m, dst, 4); + ST_UW3(frac0, frac1, frac2, irow, 4); + frow += 12; + irow += 12; + dst += 12; + length -= 12; + } else if (length >= 8) { + uint32_t val0_m, val1_m; + v4u32 src0, src1, frac0, frac1; + LD_UW2(frow, 4, src0, src1); + CALC_MULT_FIX1_4(src0, y_scale, shiftval, frac0); + CALC_MULT_FIX1_4(src1, y_scale, shiftval, frac1); + LD_UW2(irow, 4, src0, src1); + SUB2(src0, frac0, src1, frac1, src0, src1); + CALC_MULT_FIX_4(src0, fxyscale, shiftval, val0_m); + CALC_MULT_FIX_4(src1, fxyscale, shiftval, val1_m); + SW2(val0_m, val1_m, dst, 4); + ST_UW2(frac0, frac1, irow, 4); + frow += 8; + irow += 8; + dst += 8; + length -= 8; + } else if (length >= 4) { + uint32_t val0_m; + v4u32 frac0; + v4u32 src0 = LD_UW(frow); + CALC_MULT_FIX1_4(src0, y_scale, shiftval, frac0); + src0 = LD_UW(irow); + src0 = src0 - frac0; + CALC_MULT_FIX_4(src0, fxyscale, shiftval, val0_m); + SW(val0_m, dst); + ST_UW(frac0, irow); + frow += 4; + irow += 4; + dst += 4; + length -= 4; + } + for (x_out = 0; x_out < length; ++x_out) { + const uint32_t frac = (uint32_t)MULT_FIX(frow[x_out], yscale); + const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + irow[x_out] = frac; + } + } +} + +static WEBP_INLINE void ExportRowShrink_1(uint32_t* irow, uint8_t* dst, + int length, + WebPRescaler* const wrk) { + const v4u32 scale = (v4u32)__msa_fill_w(wrk->fxy_scale); + const v4u32 shift = (v4u32)__msa_fill_w(WEBP_RESCALER_RFIX); + const v4i32 zero = { 0 }; + + while (length >= 16) { + v4u32 src0, src1, src2, src3; + v16u8 dst0; + LD_UW4(irow, 4, src0, src1, src2, src3); + CALC_MULT_FIX_16(src0, src1, src2, src3, scale, shift, dst0); + ST_UB(dst0, dst); + ST_SW4(zero, zero, zero, zero, irow, 4); + length -= 16; + irow += 16; + dst += 16; + } + if (length > 0) { + int x_out; + if (length >= 12) { + uint32_t val0_m, val1_m, val2_m; + v4u32 src0, src1, src2; + LD_UW3(irow, 4, src0, src1, src2); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + CALC_MULT_FIX_4(src1, scale, shift, val1_m); + CALC_MULT_FIX_4(src2, scale, shift, val2_m); + SW3(val0_m, val1_m, val2_m, dst, 4); + ST_SW3(zero, zero, zero, irow, 4); + length -= 12; + irow += 12; + dst += 12; + } else if (length >= 8) { + uint32_t val0_m, val1_m; + v4u32 src0, src1; + LD_UW2(irow, 4, src0, src1); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + CALC_MULT_FIX_4(src1, scale, shift, val1_m); + SW2(val0_m, val1_m, dst, 4); + ST_SW2(zero, zero, irow, 4); + length -= 8; + irow += 8; + dst += 8; + } else if (length >= 4) { + uint32_t val0_m; + const v4u32 src0 = LD_UW(irow + 0); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + SW(val0_m, dst); + ST_SW(zero, irow); + length -= 4; + irow += 4; + dst += 4; + } + for (x_out = 0; x_out < length; ++x_out) { + const int v = (int)MULT_FIX(irow[x_out], wrk->fxy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + irow[x_out] = 0; + } + } +} + +static void RescalerExportRowShrink(WebPRescaler* const wrk) { + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + ExportRowShrink_0(frow, irow, dst, x_out_max, yscale, wrk); + } else { + ExportRowShrink_1(irow, dst, x_out_max, wrk); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPRescalerDspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitMSA(void) { + WebPRescalerExportRowExpand = RescalerExportRowExpand; + WebPRescalerExportRowShrink = RescalerExportRowShrink; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/3rdparty/libwebp/dsp/rescaler_neon.c b/3rdparty/libwebp/dsp/rescaler_neon.c new file mode 100644 index 0000000000..b2dd8f30cc --- /dev/null +++ b/3rdparty/libwebp/dsp/rescaler_neon.c @@ -0,0 +1,186 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON version of rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_NEON) + +#include +#include +#include "./neon.h" +#include "../utils/rescaler_utils.h" + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX_C(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) + +#define LOAD_32x4(SRC, DST) const uint32x4_t DST = vld1q_u32((SRC)) +#define LOAD_32x8(SRC, DST0, DST1) \ + LOAD_32x4(SRC + 0, DST0); \ + LOAD_32x4(SRC + 4, DST1) + +#define STORE_32x8(SRC0, SRC1, DST) do { \ + vst1q_u32((DST) + 0, SRC0); \ + vst1q_u32((DST) + 4, SRC1); \ +} while (0); + +#if (WEBP_RESCALER_RFIX == 32) +#define MAKE_HALF_CST(C) vdupq_n_s32((int32_t)((C) >> 1)) +#define MULT_FIX(A, B) /* note: B is actualy scale>>1. See MAKE_HALF_CST */ \ + vreinterpretq_u32_s32(vqrdmulhq_s32(vreinterpretq_s32_u32((A)), (B))) +#else +#error "MULT_FIX/WEBP_RESCALER_RFIX need some more work" +#endif + +static uint32x4_t Interpolate(const rescaler_t* const frow, + const rescaler_t* const irow, + uint32_t A, uint32_t B) { + LOAD_32x4(frow, A0); + LOAD_32x4(irow, B0); + const uint64x2_t C0 = vmull_n_u32(vget_low_u32(A0), A); + const uint64x2_t C1 = vmull_n_u32(vget_high_u32(A0), A); + const uint64x2_t D0 = vmlal_n_u32(C0, vget_low_u32(B0), B); + const uint64x2_t D1 = vmlal_n_u32(C1, vget_high_u32(B0), B); + const uint32x4_t E = vcombine_u32( + vrshrn_n_u64(D0, WEBP_RESCALER_RFIX), + vrshrn_n_u64(D1, WEBP_RESCALER_RFIX)); + return E; +} + +static void RescalerExportRowExpand(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int max_span = x_out_max & ~7; + const rescaler_t* const frow = wrk->frow; + const uint32_t fy_scale = wrk->fy_scale; + const int32x4_t fy_scale_half = MAKE_HALF_CST(fy_scale); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + for (x_out = 0; x_out < max_span; x_out += 8) { + LOAD_32x4(frow + x_out + 0, A0); + LOAD_32x4(frow + x_out + 4, A1); + const uint32x4_t B0 = MULT_FIX(A0, fy_scale_half); + const uint32x4_t B1 = MULT_FIX(A1, fy_scale_half); + const uint16x4_t C0 = vmovn_u32(B0); + const uint16x4_t C1 = vmovn_u32(B1); + const uint8x8_t D = vmovn_u16(vcombine_u16(C0, C1)); + vst1_u8(dst + x_out, D); + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX_C(J, fy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + for (x_out = 0; x_out < max_span; x_out += 8) { + const uint32x4_t C0 = + Interpolate(frow + x_out + 0, irow + x_out + 0, A, B); + const uint32x4_t C1 = + Interpolate(frow + x_out + 4, irow + x_out + 4, A, B); + const uint32x4_t D0 = MULT_FIX(C0, fy_scale_half); + const uint32x4_t D1 = MULT_FIX(C1, fy_scale_half); + const uint16x4_t E0 = vmovn_u32(D0); + const uint16x4_t E1 = vmovn_u32(D1); + const uint8x8_t F = vmovn_u16(vcombine_u16(E0, E1)); + vst1_u8(dst + x_out, F); + } + for (; x_out < x_out_max; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX_C(J, fy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + } + } +} + +static void RescalerExportRowShrink(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int max_span = x_out_max & ~7; + const rescaler_t* const frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + const uint32_t fxy_scale = wrk->fxy_scale; + const uint32x4_t zero = vdupq_n_u32(0); + const int32x4_t yscale_half = MAKE_HALF_CST(yscale); + const int32x4_t fxy_scale_half = MAKE_HALF_CST(fxy_scale); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + for (x_out = 0; x_out < max_span; x_out += 8) { + LOAD_32x8(frow + x_out, in0, in1); + LOAD_32x8(irow + x_out, in2, in3); + const uint32x4_t A0 = MULT_FIX(in0, yscale_half); + const uint32x4_t A1 = MULT_FIX(in1, yscale_half); + const uint32x4_t B0 = vqsubq_u32(in2, A0); + const uint32x4_t B1 = vqsubq_u32(in3, A1); + const uint32x4_t C0 = MULT_FIX(B0, fxy_scale_half); + const uint32x4_t C1 = MULT_FIX(B1, fxy_scale_half); + const uint16x4_t D0 = vmovn_u32(C0); + const uint16x4_t D1 = vmovn_u32(C1); + const uint8x8_t E = vmovn_u16(vcombine_u16(D0, D1)); + vst1_u8(dst + x_out, E); + STORE_32x8(A0, A1, irow + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t frac = (uint32_t)MULT_FIX_C(frow[x_out], yscale); + const int v = (int)MULT_FIX_C(irow[x_out] - frac, wrk->fxy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + irow[x_out] = frac; // new fractional start + } + } else { + for (x_out = 0; x_out < max_span; x_out += 8) { + LOAD_32x8(irow + x_out, in0, in1); + const uint32x4_t A0 = MULT_FIX(in0, fxy_scale_half); + const uint32x4_t A1 = MULT_FIX(in1, fxy_scale_half); + const uint16x4_t B0 = vmovn_u32(A0); + const uint16x4_t B1 = vmovn_u32(A1); + const uint8x8_t C = vmovn_u16(vcombine_u16(B0, B1)); + vst1_u8(dst + x_out, C); + STORE_32x8(zero, zero, irow + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const int v = (int)MULT_FIX_C(irow[x_out], fxy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + irow[x_out] = 0; + } + } +} + +//------------------------------------------------------------------------------ + +extern void WebPRescalerDspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitNEON(void) { + WebPRescalerExportRowExpand = RescalerExportRowExpand; + WebPRescalerExportRowShrink = RescalerExportRowShrink; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/3rdparty/libwebp/dsp/rescaler_sse2.c b/3rdparty/libwebp/dsp/rescaler_sse2.c new file mode 100644 index 0000000000..1ea1139660 --- /dev/null +++ b/3rdparty/libwebp/dsp/rescaler_sse2.c @@ -0,0 +1,375 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_SSE2) +#include + +#include +#include "../utils/rescaler_utils.h" +#include "../utils/utils.h" + +//------------------------------------------------------------------------------ +// Implementations of critical functions ImportRow / ExportRow + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) + +// input: 8 bytes ABCDEFGH -> output: A0E0B0F0C0G0D0H0 +static void LoadTwoPixels(const uint8_t* const src, __m128i* out) { + const __m128i zero = _mm_setzero_si128(); + const __m128i A = _mm_loadl_epi64((const __m128i*)(src)); // ABCDEFGH + const __m128i B = _mm_unpacklo_epi8(A, zero); // A0B0C0D0E0F0G0H0 + const __m128i C = _mm_srli_si128(B, 8); // E0F0G0H0 + *out = _mm_unpacklo_epi16(B, C); +} + +// input: 8 bytes ABCDEFGH -> output: A0B0C0D0E0F0G0H0 +static void LoadHeightPixels(const uint8_t* const src, __m128i* out) { + const __m128i zero = _mm_setzero_si128(); + const __m128i A = _mm_loadl_epi64((const __m128i*)(src)); // ABCDEFGH + *out = _mm_unpacklo_epi8(A, zero); +} + +static void RescalerImportRowExpandSSE2(WebPRescaler* const wrk, + const uint8_t* src) { + rescaler_t* frow = wrk->frow; + const rescaler_t* const frow_end = frow + wrk->dst_width * wrk->num_channels; + const int x_add = wrk->x_add; + int accum = x_add; + __m128i cur_pixels; + + assert(!WebPRescalerInputDone(wrk)); + assert(wrk->x_expand); + if (wrk->num_channels == 4) { + if (wrk->src_width < 2) { + WebPRescalerImportRowExpandC(wrk, src); + return; + } + LoadTwoPixels(src, &cur_pixels); + src += 4; + while (1) { + const __m128i mult = _mm_set1_epi32(((x_add - accum) << 16) | accum); + const __m128i out = _mm_madd_epi16(cur_pixels, mult); + _mm_storeu_si128((__m128i*)frow, out); + frow += 4; + if (frow >= frow_end) break; + accum -= wrk->x_sub; + if (accum < 0) { + LoadTwoPixels(src, &cur_pixels); + src += 4; + accum += x_add; + } + } + } else { + int left; + const uint8_t* const src_limit = src + wrk->src_width - 8; + if (wrk->src_width < 8) { + WebPRescalerImportRowExpandC(wrk, src); + return; + } + LoadHeightPixels(src, &cur_pixels); + src += 7; + left = 7; + while (1) { + const __m128i mult = _mm_cvtsi32_si128(((x_add - accum) << 16) | accum); + const __m128i out = _mm_madd_epi16(cur_pixels, mult); + assert(sizeof(*frow) == sizeof(uint32_t)); + WebPUint32ToMem((uint8_t*)frow, _mm_cvtsi128_si32(out)); + frow += 1; + if (frow >= frow_end) break; + accum -= wrk->x_sub; + if (accum < 0) { + if (--left) { + cur_pixels = _mm_srli_si128(cur_pixels, 2); + } else if (src <= src_limit) { + LoadHeightPixels(src, &cur_pixels); + src += 7; + left = 7; + } else { // tail + cur_pixels = _mm_srli_si128(cur_pixels, 2); + cur_pixels = _mm_insert_epi16(cur_pixels, src[1], 1); + src += 1; + left = 1; + } + accum += x_add; + } + } + } + assert(accum == 0); +} + +static void RescalerImportRowShrinkSSE2(WebPRescaler* const wrk, + const uint8_t* src) { + const int x_sub = wrk->x_sub; + int accum = 0; + const __m128i zero = _mm_setzero_si128(); + const __m128i mult0 = _mm_set1_epi16(x_sub); + const __m128i mult1 = _mm_set1_epi32(wrk->fx_scale); + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + __m128i sum = zero; + rescaler_t* frow = wrk->frow; + const rescaler_t* const frow_end = wrk->frow + 4 * wrk->dst_width; + + if (wrk->num_channels != 4 || wrk->x_add > (x_sub << 7)) { + WebPRescalerImportRowShrinkC(wrk, src); + return; + } + assert(!WebPRescalerInputDone(wrk)); + assert(!wrk->x_expand); + + for (; frow < frow_end; frow += 4) { + __m128i base = zero; + accum += wrk->x_add; + while (accum > 0) { + const __m128i A = _mm_cvtsi32_si128(WebPMemToUint32(src)); + src += 4; + base = _mm_unpacklo_epi8(A, zero); + // To avoid overflow, we need: base * x_add / x_sub < 32768 + // => x_add < x_sub << 7. That's a 1/128 reduction ratio limit. + sum = _mm_add_epi16(sum, base); + accum -= x_sub; + } + { // Emit next horizontal pixel. + const __m128i mult = _mm_set1_epi16(-accum); + const __m128i frac0 = _mm_mullo_epi16(base, mult); // 16b x 16b -> 32b + const __m128i frac1 = _mm_mulhi_epu16(base, mult); + const __m128i frac = _mm_unpacklo_epi16(frac0, frac1); // frac is 32b + const __m128i A0 = _mm_mullo_epi16(sum, mult0); + const __m128i A1 = _mm_mulhi_epu16(sum, mult0); + const __m128i B0 = _mm_unpacklo_epi16(A0, A1); // sum * x_sub + const __m128i frow_out = _mm_sub_epi32(B0, frac); // sum * x_sub - frac + const __m128i D0 = _mm_srli_epi64(frac, 32); + const __m128i D1 = _mm_mul_epu32(frac, mult1); // 32b x 16b -> 64b + const __m128i D2 = _mm_mul_epu32(D0, mult1); + const __m128i E1 = _mm_add_epi64(D1, rounder); + const __m128i E2 = _mm_add_epi64(D2, rounder); + const __m128i F1 = _mm_shuffle_epi32(E1, 1 | (3 << 2)); + const __m128i F2 = _mm_shuffle_epi32(E2, 1 | (3 << 2)); + const __m128i G = _mm_unpacklo_epi32(F1, F2); + sum = _mm_packs_epi32(G, zero); + _mm_storeu_si128((__m128i*)frow, frow_out); + } + } + assert(accum == 0); +} + +//------------------------------------------------------------------------------ +// Row export + +// load *src as epi64, multiply by mult and store result in [out0 ... out3] +static WEBP_INLINE void LoadDispatchAndMult(const rescaler_t* const src, + const __m128i* const mult, + __m128i* const out0, + __m128i* const out1, + __m128i* const out2, + __m128i* const out3) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(src + 0)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(src + 4)); + const __m128i A2 = _mm_srli_epi64(A0, 32); + const __m128i A3 = _mm_srli_epi64(A1, 32); + if (mult != NULL) { + *out0 = _mm_mul_epu32(A0, *mult); + *out1 = _mm_mul_epu32(A1, *mult); + *out2 = _mm_mul_epu32(A2, *mult); + *out3 = _mm_mul_epu32(A3, *mult); + } else { + *out0 = A0; + *out1 = A1; + *out2 = A2; + *out3 = A3; + } +} + +static WEBP_INLINE void ProcessRow(const __m128i* const A0, + const __m128i* const A1, + const __m128i* const A2, + const __m128i* const A3, + const __m128i* const mult, + uint8_t* const dst) { + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + const __m128i mask = _mm_set_epi32(0xffffffffu, 0, 0xffffffffu, 0); + const __m128i B0 = _mm_mul_epu32(*A0, *mult); + const __m128i B1 = _mm_mul_epu32(*A1, *mult); + const __m128i B2 = _mm_mul_epu32(*A2, *mult); + const __m128i B3 = _mm_mul_epu32(*A3, *mult); + const __m128i C0 = _mm_add_epi64(B0, rounder); + const __m128i C1 = _mm_add_epi64(B1, rounder); + const __m128i C2 = _mm_add_epi64(B2, rounder); + const __m128i C3 = _mm_add_epi64(B3, rounder); + const __m128i D0 = _mm_srli_epi64(C0, WEBP_RESCALER_RFIX); + const __m128i D1 = _mm_srli_epi64(C1, WEBP_RESCALER_RFIX); +#if (WEBP_RESCALER_RFIX < 32) + const __m128i D2 = + _mm_and_si128(_mm_slli_epi64(C2, 32 - WEBP_RESCALER_RFIX), mask); + const __m128i D3 = + _mm_and_si128(_mm_slli_epi64(C3, 32 - WEBP_RESCALER_RFIX), mask); +#else + const __m128i D2 = _mm_and_si128(C2, mask); + const __m128i D3 = _mm_and_si128(C3, mask); +#endif + const __m128i E0 = _mm_or_si128(D0, D2); + const __m128i E1 = _mm_or_si128(D1, D3); + const __m128i F = _mm_packs_epi32(E0, E1); + const __m128i G = _mm_packus_epi16(F, F); + _mm_storel_epi64((__m128i*)dst, G); +} + +static void RescalerExportRowExpandSSE2(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + const __m128i mult = _mm_set_epi32(0, wrk->fy_scale, 0, wrk->fy_scale); + + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0 && wrk->y_sub + wrk->y_accum >= 0); + assert(wrk->y_expand); + if (wrk->y_accum == 0) { + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3; + LoadDispatchAndMult(frow + x_out, NULL, &A0, &A1, &A2, &A3); + ProcessRow(&A0, &A1, &A2, &A3, &mult, dst + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + const __m128i mA = _mm_set_epi32(0, A, 0, A); + const __m128i mB = _mm_set_epi32(0, B, 0, B); + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3, B0, B1, B2, B3; + LoadDispatchAndMult(frow + x_out, &mA, &A0, &A1, &A2, &A3); + LoadDispatchAndMult(irow + x_out, &mB, &B0, &B1, &B2, &B3); + { + const __m128i C0 = _mm_add_epi64(A0, B0); + const __m128i C1 = _mm_add_epi64(A1, B1); + const __m128i C2 = _mm_add_epi64(A2, B2); + const __m128i C3 = _mm_add_epi64(A3, B3); + const __m128i D0 = _mm_add_epi64(C0, rounder); + const __m128i D1 = _mm_add_epi64(C1, rounder); + const __m128i D2 = _mm_add_epi64(C2, rounder); + const __m128i D3 = _mm_add_epi64(C3, rounder); + const __m128i E0 = _mm_srli_epi64(D0, WEBP_RESCALER_RFIX); + const __m128i E1 = _mm_srli_epi64(D1, WEBP_RESCALER_RFIX); + const __m128i E2 = _mm_srli_epi64(D2, WEBP_RESCALER_RFIX); + const __m128i E3 = _mm_srli_epi64(D3, WEBP_RESCALER_RFIX); + ProcessRow(&E0, &E1, &E2, &E3, &mult, dst + x_out); + } + } + for (; x_out < x_out_max; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + } + } +} + +static void RescalerExportRowShrinkSSE2(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + const int scale_xy = wrk->fxy_scale; + const __m128i mult_xy = _mm_set_epi32(0, scale_xy, 0, scale_xy); + const __m128i mult_y = _mm_set_epi32(0, yscale, 0, yscale); + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3, B0, B1, B2, B3; + LoadDispatchAndMult(irow + x_out, NULL, &A0, &A1, &A2, &A3); + LoadDispatchAndMult(frow + x_out, &mult_y, &B0, &B1, &B2, &B3); + { + const __m128i C0 = _mm_add_epi64(B0, rounder); + const __m128i C1 = _mm_add_epi64(B1, rounder); + const __m128i C2 = _mm_add_epi64(B2, rounder); + const __m128i C3 = _mm_add_epi64(B3, rounder); + const __m128i D0 = _mm_srli_epi64(C0, WEBP_RESCALER_RFIX); // = frac + const __m128i D1 = _mm_srli_epi64(C1, WEBP_RESCALER_RFIX); + const __m128i D2 = _mm_srli_epi64(C2, WEBP_RESCALER_RFIX); + const __m128i D3 = _mm_srli_epi64(C3, WEBP_RESCALER_RFIX); + const __m128i E0 = _mm_sub_epi64(A0, D0); // irow[x] - frac + const __m128i E1 = _mm_sub_epi64(A1, D1); + const __m128i E2 = _mm_sub_epi64(A2, D2); + const __m128i E3 = _mm_sub_epi64(A3, D3); + const __m128i F2 = _mm_slli_epi64(D2, 32); + const __m128i F3 = _mm_slli_epi64(D3, 32); + const __m128i G0 = _mm_or_si128(D0, F2); + const __m128i G1 = _mm_or_si128(D1, F3); + _mm_storeu_si128((__m128i*)(irow + x_out + 0), G0); + _mm_storeu_si128((__m128i*)(irow + x_out + 4), G1); + ProcessRow(&E0, &E1, &E2, &E3, &mult_xy, dst + x_out); + } + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t frac = (int)MULT_FIX(frow[x_out], yscale); + const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + irow[x_out] = frac; // new fractional start + } + } else { + const uint32_t scale = wrk->fxy_scale; + const __m128i mult = _mm_set_epi32(0, scale, 0, scale); + const __m128i zero = _mm_setzero_si128(); + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3; + LoadDispatchAndMult(irow + x_out, NULL, &A0, &A1, &A2, &A3); + _mm_storeu_si128((__m128i*)(irow + x_out + 0), zero); + _mm_storeu_si128((__m128i*)(irow + x_out + 4), zero); + ProcessRow(&A0, &A1, &A2, &A3, &mult, dst + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const int v = (int)MULT_FIX(irow[x_out], scale); + assert(v >= 0 && v <= 255); + dst[x_out] = v; + irow[x_out] = 0; + } + } +} + +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ + +extern void WebPRescalerDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitSSE2(void) { + WebPRescalerImportRowExpand = RescalerImportRowExpandSSE2; + WebPRescalerImportRowShrink = RescalerImportRowShrinkSSE2; + WebPRescalerExportRowExpand = RescalerExportRowExpandSSE2; + WebPRescalerExportRowShrink = RescalerExportRowShrinkSSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/dsp/upsampling.c b/3rdparty/libwebp/dsp/upsampling.c index 80ba4f8a5e..265e722c10 100644 --- a/3rdparty/libwebp/dsp/upsampling.c +++ b/3rdparty/libwebp/dsp/upsampling.c @@ -14,9 +14,7 @@ #include "./dsp.h" #include "./yuv.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include //------------------------------------------------------------------------------ // Fancy upsampler @@ -45,11 +43,12 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ const int last_pixel_pair = (len - 1) >> 1; \ uint32_t tl_uv = LOAD_UV(top_u[0], top_v[0]); /* top-left sample */ \ uint32_t l_uv = LOAD_UV(cur_u[0], cur_v[0]); /* left-sample */ \ - if (top_y) { \ + assert(top_y != NULL); \ + { \ const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ FUNC(top_y[0], uv0 & 0xff, (uv0 >> 16), top_dst); \ } \ - if (bottom_y) { \ + if (bottom_y != NULL) { \ const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ FUNC(bottom_y[0], uv0 & 0xff, (uv0 >> 16), bottom_dst); \ } \ @@ -60,7 +59,7 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ const uint32_t avg = tl_uv + t_uv + l_uv + uv + 0x00080008u; \ const uint32_t diag_12 = (avg + 2 * (t_uv + l_uv)) >> 3; \ const uint32_t diag_03 = (avg + 2 * (tl_uv + uv)) >> 3; \ - if (top_y) { \ + { \ const uint32_t uv0 = (diag_12 + tl_uv) >> 1; \ const uint32_t uv1 = (diag_03 + t_uv) >> 1; \ FUNC(top_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ @@ -68,7 +67,7 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ FUNC(top_y[2 * x - 0], uv1 & 0xff, (uv1 >> 16), \ top_dst + (2 * x - 0) * XSTEP); \ } \ - if (bottom_y) { \ + if (bottom_y != NULL) { \ const uint32_t uv0 = (diag_03 + l_uv) >> 1; \ const uint32_t uv1 = (diag_12 + uv) >> 1; \ FUNC(bottom_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ @@ -80,12 +79,12 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ l_uv = uv; \ } \ if (!(len & 1)) { \ - if (top_y) { \ + { \ const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ FUNC(top_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ top_dst + (len - 1) * XSTEP); \ } \ - if (bottom_y) { \ + if (bottom_y != NULL) { \ const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ FUNC(bottom_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ bottom_dst + (len - 1) * XSTEP); \ @@ -107,57 +106,6 @@ UPSAMPLE_FUNC(UpsampleRgb565LinePair, VP8YuvToRgb565, 2) #endif // FANCY_UPSAMPLING -//------------------------------------------------------------------------------ -// simple point-sampling - -#define SAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ -static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ - const uint8_t* u, const uint8_t* v, \ - uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ - int i; \ - for (i = 0; i < len - 1; i += 2) { \ - FUNC(top_y[0], u[0], v[0], top_dst); \ - FUNC(top_y[1], u[0], v[0], top_dst + XSTEP); \ - FUNC(bottom_y[0], u[0], v[0], bottom_dst); \ - FUNC(bottom_y[1], u[0], v[0], bottom_dst + XSTEP); \ - top_y += 2; \ - bottom_y += 2; \ - u++; \ - v++; \ - top_dst += 2 * XSTEP; \ - bottom_dst += 2 * XSTEP; \ - } \ - if (i == len - 1) { /* last one */ \ - FUNC(top_y[0], u[0], v[0], top_dst); \ - FUNC(bottom_y[0], u[0], v[0], bottom_dst); \ - } \ -} - -// All variants implemented. -SAMPLE_FUNC(SampleRgbLinePair, VP8YuvToRgb, 3) -SAMPLE_FUNC(SampleBgrLinePair, VP8YuvToBgr, 3) -SAMPLE_FUNC(SampleRgbaLinePair, VP8YuvToRgba, 4) -SAMPLE_FUNC(SampleBgraLinePair, VP8YuvToBgra, 4) -SAMPLE_FUNC(SampleArgbLinePair, VP8YuvToArgb, 4) -SAMPLE_FUNC(SampleRgba4444LinePair, VP8YuvToRgba4444, 2) -SAMPLE_FUNC(SampleRgb565LinePair, VP8YuvToRgb565, 2) - -#undef SAMPLE_FUNC - -const WebPSampleLinePairFunc WebPSamplers[MODE_LAST] = { - SampleRgbLinePair, // MODE_RGB - SampleRgbaLinePair, // MODE_RGBA - SampleBgrLinePair, // MODE_BGR - SampleBgraLinePair, // MODE_BGRA - SampleArgbLinePair, // MODE_ARGB - SampleRgba4444LinePair, // MODE_RGBA_4444 - SampleRgb565LinePair, // MODE_RGB_565 - SampleRgbaLinePair, // MODE_rgbA - SampleBgraLinePair, // MODE_bgrA - SampleArgbLinePair, // MODE_Argb - SampleRgba4444LinePair // MODE_rgbA_4444 -}; - //------------------------------------------------------------------------------ #if !defined(FANCY_UPSAMPLING) @@ -168,7 +116,8 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bot_y, \ uint8_t* top_dst, uint8_t* bot_dst, int len) { \ const int half_len = len >> 1; \ int x; \ - if (top_dst != NULL) { \ + assert(top_dst != NULL); \ + { \ for (x = 0; x < half_len; ++x) { \ FUNC(top_y[2 * x + 0], top_u[x], top_v[x], top_dst + 8 * x + 0); \ FUNC(top_y[2 * x + 1], top_u[x], top_v[x], top_dst + 8 * x + 4); \ @@ -204,116 +153,76 @@ WebPUpsampleLinePairFunc WebPGetLinePairConverter(int alpha_is_last) { // YUV444 converter #define YUV444_FUNC(FUNC_NAME, FUNC, XSTEP) \ -static void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ - uint8_t* dst, int len) { \ +extern void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len); \ +void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ int i; \ for (i = 0; i < len; ++i) FUNC(y[i], u[i], v[i], &dst[i * XSTEP]); \ } -YUV444_FUNC(Yuv444ToRgb, VP8YuvToRgb, 3) -YUV444_FUNC(Yuv444ToBgr, VP8YuvToBgr, 3) -YUV444_FUNC(Yuv444ToRgba, VP8YuvToRgba, 4) -YUV444_FUNC(Yuv444ToBgra, VP8YuvToBgra, 4) -YUV444_FUNC(Yuv444ToArgb, VP8YuvToArgb, 4) -YUV444_FUNC(Yuv444ToRgba4444, VP8YuvToRgba4444, 2) -YUV444_FUNC(Yuv444ToRgb565, VP8YuvToRgb565, 2) +YUV444_FUNC(WebPYuv444ToRgbC, VP8YuvToRgb, 3) +YUV444_FUNC(WebPYuv444ToBgrC, VP8YuvToBgr, 3) +YUV444_FUNC(WebPYuv444ToRgbaC, VP8YuvToRgba, 4) +YUV444_FUNC(WebPYuv444ToBgraC, VP8YuvToBgra, 4) +YUV444_FUNC(WebPYuv444ToArgbC, VP8YuvToArgb, 4) +YUV444_FUNC(WebPYuv444ToRgba4444C, VP8YuvToRgba4444, 2) +YUV444_FUNC(WebPYuv444ToRgb565C, VP8YuvToRgb565, 2) #undef YUV444_FUNC -const WebPYUV444Converter WebPYUV444Converters[MODE_LAST] = { - Yuv444ToRgb, // MODE_RGB - Yuv444ToRgba, // MODE_RGBA - Yuv444ToBgr, // MODE_BGR - Yuv444ToBgra, // MODE_BGRA - Yuv444ToArgb, // MODE_ARGB - Yuv444ToRgba4444, // MODE_RGBA_4444 - Yuv444ToRgb565, // MODE_RGB_565 - Yuv444ToRgba, // MODE_rgbA - Yuv444ToBgra, // MODE_bgrA - Yuv444ToArgb, // MODE_Argb - Yuv444ToRgba4444 // MODE_rgbA_4444 -}; +WebPYUV444Converter WebPYUV444Converters[MODE_LAST]; -//------------------------------------------------------------------------------ -// Premultiplied modes +extern void WebPInitYUV444ConvertersMIPSdspR2(void); +extern void WebPInitYUV444ConvertersSSE2(void); -// non dithered-modes +static volatile VP8CPUInfo upsampling_last_cpuinfo_used1 = + (VP8CPUInfo)&upsampling_last_cpuinfo_used1; -// (x * a * 32897) >> 23 is bit-wise equivalent to (int)(x * a / 255.) -// for all 8bit x or a. For bit-wise equivalence to (int)(x * a / 255. + .5), -// one can use instead: (x * a * 65793 + (1 << 23)) >> 24 -#if 1 // (int)(x * a / 255.) -#define MULTIPLIER(a) ((a) * 32897UL) -#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) -#else // (int)(x * a / 255. + .5) -#define MULTIPLIER(a) ((a) * 65793UL) -#define PREMULTIPLY(x, m) (((x) * (m) + (1UL << 23)) >> 24) +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444Converters(void) { + if (upsampling_last_cpuinfo_used1 == VP8GetCPUInfo) return; + + WebPYUV444Converters[MODE_RGB] = WebPYuv444ToRgbC; + WebPYUV444Converters[MODE_RGBA] = WebPYuv444ToRgbaC; + WebPYUV444Converters[MODE_BGR] = WebPYuv444ToBgrC; + WebPYUV444Converters[MODE_BGRA] = WebPYuv444ToBgraC; + WebPYUV444Converters[MODE_ARGB] = WebPYuv444ToArgbC; + WebPYUV444Converters[MODE_RGBA_4444] = WebPYuv444ToRgba4444C; + WebPYUV444Converters[MODE_RGB_565] = WebPYuv444ToRgb565C; + WebPYUV444Converters[MODE_rgbA] = WebPYuv444ToRgbaC; + WebPYUV444Converters[MODE_bgrA] = WebPYuv444ToBgraC; + WebPYUV444Converters[MODE_Argb] = WebPYuv444ToArgbC; + WebPYUV444Converters[MODE_rgbA_4444] = WebPYuv444ToRgba4444C; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitYUV444ConvertersSSE2(); + } #endif - -static void ApplyAlphaMultiply(uint8_t* rgba, int alpha_first, - int w, int h, int stride) { - while (h-- > 0) { - uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); - const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); - int i; - for (i = 0; i < w; ++i) { - const uint32_t a = alpha[4 * i]; - if (a != 0xff) { - const uint32_t mult = MULTIPLIER(a); - rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); - rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); - rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); - } +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitYUV444ConvertersMIPSdspR2(); } - rgba += stride; +#endif } + upsampling_last_cpuinfo_used1 = VP8GetCPUInfo; } -#undef MULTIPLIER -#undef PREMULTIPLY - -// rgbA4444 - -#define MULTIPLIER(a) ((a) * 0x1111) // 0x1111 ~= (1 << 16) / 15 - -static WEBP_INLINE uint8_t dither_hi(uint8_t x) { - return (x & 0xf0) | (x >> 4); -} - -static WEBP_INLINE uint8_t dither_lo(uint8_t x) { - return (x & 0x0f) | (x << 4); -} - -static WEBP_INLINE uint8_t multiply(uint8_t x, uint32_t m) { - return (x * m) >> 16; -} - -static void ApplyAlphaMultiply4444(uint8_t* rgba4444, - int w, int h, int stride) { - while (h-- > 0) { - int i; - for (i = 0; i < w; ++i) { - const uint8_t a = (rgba4444[2 * i + 1] & 0x0f); - const uint32_t mult = MULTIPLIER(a); - const uint8_t r = multiply(dither_hi(rgba4444[2 * i + 0]), mult); - const uint8_t g = multiply(dither_lo(rgba4444[2 * i + 0]), mult); - const uint8_t b = multiply(dither_hi(rgba4444[2 * i + 1]), mult); - rgba4444[2 * i + 0] = (r & 0xf0) | ((g >> 4) & 0x0f); - rgba4444[2 * i + 1] = (b & 0xf0) | a; - } - rgba4444 += stride; - } -} -#undef MULTIPLIER - -void (*WebPApplyAlphaMultiply)(uint8_t*, int, int, int, int) - = ApplyAlphaMultiply; -void (*WebPApplyAlphaMultiply4444)(uint8_t*, int, int, int) - = ApplyAlphaMultiply4444; //------------------------------------------------------------------------------ -// Main call +// Main calls + +extern void WebPInitUpsamplersSSE2(void); +extern void WebPInitUpsamplersNEON(void); +extern void WebPInitUpsamplersMIPSdspR2(void); +extern void WebPInitUpsamplersMSA(void); + +static volatile VP8CPUInfo upsampling_last_cpuinfo_used2 = + (VP8CPUInfo)&upsampling_last_cpuinfo_used2; + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplers(void) { + if (upsampling_last_cpuinfo_used2 == VP8GetCPUInfo) return; -void WebPInitUpsamplers(void) { #ifdef FANCY_UPSAMPLING WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair; WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair; @@ -322,6 +231,10 @@ void WebPInitUpsamplers(void) { WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair; WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair; WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair; // If defined, use CPUInfo() to overwrite some pointers with faster versions. if (VP8GetCPUInfo != NULL) { @@ -335,35 +248,19 @@ void WebPInitUpsamplers(void) { WebPInitUpsamplersNEON(); } #endif - } -#endif // FANCY_UPSAMPLING -} - -void WebPInitPremultiply(void) { - WebPApplyAlphaMultiply = ApplyAlphaMultiply; - WebPApplyAlphaMultiply4444 = ApplyAlphaMultiply4444; - -#ifdef FANCY_UPSAMPLING - WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair; - WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair; - WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair; - WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair; - - if (VP8GetCPUInfo != NULL) { -#if defined(WEBP_USE_SSE2) - if (VP8GetCPUInfo(kSSE2)) { - WebPInitPremultiplySSE2(); +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitUpsamplersMIPSdspR2(); } #endif -#if defined(WEBP_USE_NEON) - if (VP8GetCPUInfo(kNEON)) { - WebPInitPremultiplyNEON(); +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + WebPInitUpsamplersMSA(); } #endif } #endif // FANCY_UPSAMPLING + upsampling_last_cpuinfo_used2 = VP8GetCPUInfo; } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/dsp/upsampling_mips_dsp_r2.c b/3rdparty/libwebp/dsp/upsampling_mips_dsp_r2.c new file mode 100644 index 0000000000..ed2eb74825 --- /dev/null +++ b/3rdparty/libwebp/dsp/upsampling_mips_dsp_r2.c @@ -0,0 +1,282 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV to RGB upsampling functions. +// +// Author(s): Branimir Vasic (branimir.vasic@imgtec.com) +// Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include +#include "./yuv.h" + +#if !defined(WEBP_YUV_USE_TABLE) + +#define YUV_TO_RGB(Y, U, V, R, G, B) do { \ + const int t1 = MultHi(Y, 19077); \ + const int t2 = MultHi(V, 13320); \ + R = MultHi(V, 26149); \ + G = MultHi(U, 6419); \ + B = MultHi(U, 33050); \ + R = t1 + R; \ + G = t1 - G; \ + B = t1 + B; \ + R = R - 14234; \ + G = G - t2 + 8708; \ + B = B - 17685; \ + __asm__ volatile ( \ + "shll_s.w %[" #R "], %[" #R "], 17 \n\t" \ + "shll_s.w %[" #G "], %[" #G "], 17 \n\t" \ + "shll_s.w %[" #B "], %[" #B "], 17 \n\t" \ + "precrqu_s.qb.ph %[" #R "], %[" #R "], $zero \n\t" \ + "precrqu_s.qb.ph %[" #G "], %[" #G "], $zero \n\t" \ + "precrqu_s.qb.ph %[" #B "], %[" #B "], $zero \n\t" \ + "srl %[" #R "], %[" #R "], 24 \n\t" \ + "srl %[" #G "], %[" #G "], 24 \n\t" \ + "srl %[" #B "], %[" #B "], 24 \n\t" \ + : [R]"+r"(R), [G]"+r"(G), [B]"+r"(B) \ + : \ + ); \ + } while (0) + +static WEBP_INLINE void YuvToRgb(int y, int u, int v, uint8_t* const rgb) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; +} +static WEBP_INLINE void YuvToBgr(int y, int u, int v, uint8_t* const bgr) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + bgr[0] = b; + bgr[1] = g; + bgr[2] = r; +} +static WEBP_INLINE void YuvToRgb565(int y, int u, int v, uint8_t* const rgb) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + { + const int rg = (r & 0xf8) | (g >> 5); + const int gb = ((g << 3) & 0xe0) | (b >> 3); +#ifdef WEBP_SWAP_16BIT_CSP + rgb[0] = gb; + rgb[1] = rg; +#else + rgb[0] = rg; + rgb[1] = gb; +#endif + } +} +static WEBP_INLINE void YuvToRgba4444(int y, int u, int v, + uint8_t* const argb) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + { + const int rg = (r & 0xf0) | (g >> 4); + const int ba = (b & 0xf0) | 0x0f; // overwrite the lower 4 bits +#ifdef WEBP_SWAP_16BIT_CSP + argb[0] = ba; + argb[1] = rg; +#else + argb[0] = rg; + argb[1] = ba; +#endif + } +} +#endif // WEBP_YUV_USE_TABLE + +//----------------------------------------------------------------------------- +// Alpha handling variants + +static WEBP_INLINE void YuvToArgb(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const argb) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + argb[0] = 0xff; + argb[1] = r; + argb[2] = g; + argb[3] = b; +} +static WEBP_INLINE void YuvToBgra(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const bgra) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + bgra[0] = b; + bgra[1] = g; + bgra[2] = r; + bgra[3] = 0xff; +} +static WEBP_INLINE void YuvToRgba(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const rgba) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + rgba[0] = r; + rgba[1] = g; + rgba[2] = b; + rgba[3] = 0xff; +} + +//------------------------------------------------------------------------------ +// Fancy upsampler + +#ifdef FANCY_UPSAMPLING + +// Given samples laid out in a square as: +// [a b] +// [c d] +// we interpolate u/v as: +// ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16 +// ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16 + +// We process u and v together stashed into 32bit (16bit each). +#define LOAD_UV(u, v) ((u) | ((v) << 16)) + +#define UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ + int x; \ + const int last_pixel_pair = (len - 1) >> 1; \ + uint32_t tl_uv = LOAD_UV(top_u[0], top_v[0]); /* top-left sample */ \ + uint32_t l_uv = LOAD_UV(cur_u[0], cur_v[0]); /* left-sample */ \ + assert(top_y != NULL); \ + { \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + FUNC(top_y[0], uv0 & 0xff, (uv0 >> 16), top_dst); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bottom_y[0], uv0 & 0xff, (uv0 >> 16), bottom_dst); \ + } \ + for (x = 1; x <= last_pixel_pair; ++x) { \ + const uint32_t t_uv = LOAD_UV(top_u[x], top_v[x]); /* top sample */ \ + const uint32_t uv = LOAD_UV(cur_u[x], cur_v[x]); /* sample */ \ + /* precompute invariant values associated with first and second diagonals*/\ + const uint32_t avg = tl_uv + t_uv + l_uv + uv + 0x00080008u; \ + const uint32_t diag_12 = (avg + 2 * (t_uv + l_uv)) >> 3; \ + const uint32_t diag_03 = (avg + 2 * (tl_uv + uv)) >> 3; \ + { \ + const uint32_t uv0 = (diag_12 + tl_uv) >> 1; \ + const uint32_t uv1 = (diag_03 + t_uv) >> 1; \ + FUNC(top_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ + top_dst + (2 * x - 1) * XSTEP); \ + FUNC(top_y[2 * x - 0], uv1 & 0xff, (uv1 >> 16), \ + top_dst + (2 * x - 0) * XSTEP); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (diag_03 + l_uv) >> 1; \ + const uint32_t uv1 = (diag_12 + uv) >> 1; \ + FUNC(bottom_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ + bottom_dst + (2 * x - 1) * XSTEP); \ + FUNC(bottom_y[2 * x + 0], uv1 & 0xff, (uv1 >> 16), \ + bottom_dst + (2 * x + 0) * XSTEP); \ + } \ + tl_uv = t_uv; \ + l_uv = uv; \ + } \ + if (!(len & 1)) { \ + { \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + FUNC(top_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ + top_dst + (len - 1) * XSTEP); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bottom_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ + bottom_dst + (len - 1) * XSTEP); \ + } \ + } \ +} + +// All variants implemented. +UPSAMPLE_FUNC(UpsampleRgbLinePair, YuvToRgb, 3) +UPSAMPLE_FUNC(UpsampleBgrLinePair, YuvToBgr, 3) +UPSAMPLE_FUNC(UpsampleRgbaLinePair, YuvToRgba, 4) +UPSAMPLE_FUNC(UpsampleBgraLinePair, YuvToBgra, 4) +UPSAMPLE_FUNC(UpsampleArgbLinePair, YuvToArgb, 4) +UPSAMPLE_FUNC(UpsampleRgba4444LinePair, YuvToRgba4444, 2) +UPSAMPLE_FUNC(UpsampleRgb565LinePair, YuvToRgb565, 2) + +#undef LOAD_UV +#undef UPSAMPLE_FUNC + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitUpsamplersMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersMIPSdspR2(void) { + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair; + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair; +} + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ +// YUV444 converter + +#define YUV444_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + for (i = 0; i < len; ++i) FUNC(y[i], u[i], v[i], &dst[i * XSTEP]); \ +} + +YUV444_FUNC(Yuv444ToRgb, YuvToRgb, 3) +YUV444_FUNC(Yuv444ToBgr, YuvToBgr, 3) +YUV444_FUNC(Yuv444ToRgba, YuvToRgba, 4) +YUV444_FUNC(Yuv444ToBgra, YuvToBgra, 4) +YUV444_FUNC(Yuv444ToArgb, YuvToArgb, 4) +YUV444_FUNC(Yuv444ToRgba4444, YuvToRgba4444, 2) +YUV444_FUNC(Yuv444ToRgb565, YuvToRgb565, 2) + +#undef YUV444_FUNC + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitYUV444ConvertersMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444ConvertersMIPSdspR2(void) { + WebPYUV444Converters[MODE_RGB] = Yuv444ToRgb; + WebPYUV444Converters[MODE_RGBA] = Yuv444ToRgba; + WebPYUV444Converters[MODE_BGR] = Yuv444ToBgr; + WebPYUV444Converters[MODE_BGRA] = Yuv444ToBgra; + WebPYUV444Converters[MODE_ARGB] = Yuv444ToArgb; + WebPYUV444Converters[MODE_RGBA_4444] = Yuv444ToRgba4444; + WebPYUV444Converters[MODE_RGB_565] = Yuv444ToRgb565; + WebPYUV444Converters[MODE_rgbA] = Yuv444ToRgba; + WebPYUV444Converters[MODE_bgrA] = Yuv444ToBgra; + WebPYUV444Converters[MODE_Argb] = Yuv444ToArgb; + WebPYUV444Converters[MODE_rgbA_4444] = Yuv444ToRgba4444; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(WebPInitYUV444ConvertersMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_MIPS_DSP_R2)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersMIPSdspR2) +#endif diff --git a/3rdparty/libwebp/dsp/upsampling_msa.c b/3rdparty/libwebp/dsp/upsampling_msa.c new file mode 100644 index 0000000000..f24926fa94 --- /dev/null +++ b/3rdparty/libwebp/dsp/upsampling_msa.c @@ -0,0 +1,678 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA version of YUV to RGB upsampling functions. +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include +#include "./dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "./msa_macro.h" +#include "./yuv.h" + +#ifdef FANCY_UPSAMPLING + +#define ILVR_UW2(in, out0, out1) do { \ + const v8i16 t0 = (v8i16)__msa_ilvr_b((v16i8)zero, (v16i8)in); \ + out0 = (v4u32)__msa_ilvr_h((v8i16)zero, t0); \ + out1 = (v4u32)__msa_ilvl_h((v8i16)zero, t0); \ +} while (0) + +#define ILVRL_UW4(in, out0, out1, out2, out3) do { \ + v16u8 t0, t1; \ + ILVRL_B2_UB(zero, in, t0, t1); \ + ILVRL_H2_UW(zero, t0, out0, out1); \ + ILVRL_H2_UW(zero, t1, out2, out3); \ +} while (0) + +#define MULTHI_16(in0, in1, in2, in3, cnst, out0, out1) do { \ + const v4i32 const0 = (v4i32)__msa_fill_w(cnst * 256); \ + v4u32 temp0, temp1, temp2, temp3; \ + MUL4(in0, const0, in1, const0, in2, const0, in3, const0, \ + temp0, temp1, temp2, temp3); \ + PCKOD_H2_UH(temp1, temp0, temp3, temp2, out0, out1); \ +} while (0) + +#define MULTHI_8(in0, in1, cnst, out0) do { \ + const v4i32 const0 = (v4i32)__msa_fill_w(cnst * 256); \ + v4u32 temp0, temp1; \ + MUL2(in0, const0, in1, const0, temp0, temp1); \ + out0 = (v8u16)__msa_pckod_h((v8i16)temp1, (v8i16)temp0); \ +} while (0) + +#define CALC_R16(y0, y1, v0, v1, dst) do { \ + const v8i16 const_a = (v8i16)__msa_fill_h(14234); \ + const v8i16 a0 = __msa_adds_s_h((v8i16)y0, (v8i16)v0); \ + const v8i16 a1 = __msa_adds_s_h((v8i16)y1, (v8i16)v1); \ + v8i16 b0 = __msa_subs_s_h(a0, const_a); \ + v8i16 b1 = __msa_subs_s_h(a1, const_a); \ + SRAI_H2_SH(b0, b1, 6); \ + CLIP_SH2_0_255(b0, b1); \ + dst = (v16u8)__msa_pckev_b((v16i8)b1, (v16i8)b0); \ +} while (0) + +#define CALC_R8(y0, v0, dst) do { \ + const v8i16 const_a = (v8i16)__msa_fill_h(14234); \ + const v8i16 a0 = __msa_adds_s_h((v8i16)y0, (v8i16)v0); \ + v8i16 b0 = __msa_subs_s_h(a0, const_a); \ + b0 = SRAI_H(b0, 6); \ + CLIP_SH_0_255(b0); \ + dst = (v16u8)__msa_pckev_b((v16i8)b0, (v16i8)b0); \ +} while (0) + +#define CALC_G16(y0, y1, u0, u1, v0, v1, dst) do { \ + const v8i16 const_a = (v8i16)__msa_fill_h(8708); \ + v8i16 a0 = __msa_subs_s_h((v8i16)y0, (v8i16)u0); \ + v8i16 a1 = __msa_subs_s_h((v8i16)y1, (v8i16)u1); \ + const v8i16 b0 = __msa_subs_s_h(a0, (v8i16)v0); \ + const v8i16 b1 = __msa_subs_s_h(a1, (v8i16)v1); \ + a0 = __msa_adds_s_h(b0, const_a); \ + a1 = __msa_adds_s_h(b1, const_a); \ + SRAI_H2_SH(a0, a1, 6); \ + CLIP_SH2_0_255(a0, a1); \ + dst = (v16u8)__msa_pckev_b((v16i8)a1, (v16i8)a0); \ +} while (0) + +#define CALC_G8(y0, u0, v0, dst) do { \ + const v8i16 const_a = (v8i16)__msa_fill_h(8708); \ + v8i16 a0 = __msa_subs_s_h((v8i16)y0, (v8i16)u0); \ + const v8i16 b0 = __msa_subs_s_h(a0, (v8i16)v0); \ + a0 = __msa_adds_s_h(b0, const_a); \ + a0 = SRAI_H(a0, 6); \ + CLIP_SH_0_255(a0); \ + dst = (v16u8)__msa_pckev_b((v16i8)a0, (v16i8)a0); \ +} while (0) + +#define CALC_B16(y0, y1, u0, u1, dst) do { \ + const v8u16 const_a = (v8u16)__msa_fill_h(17685); \ + const v8u16 a0 = __msa_adds_u_h((v8u16)y0, u0); \ + const v8u16 a1 = __msa_adds_u_h((v8u16)y1, u1); \ + v8u16 b0 = __msa_subs_u_h(a0, const_a); \ + v8u16 b1 = __msa_subs_u_h(a1, const_a); \ + SRAI_H2_UH(b0, b1, 6); \ + CLIP_UH2_0_255(b0, b1); \ + dst = (v16u8)__msa_pckev_b((v16i8)b1, (v16i8)b0); \ +} while (0) + +#define CALC_B8(y0, u0, dst) do { \ + const v8u16 const_a = (v8u16)__msa_fill_h(17685); \ + const v8u16 a0 = __msa_adds_u_h((v8u16)y0, u0); \ + v8u16 b0 = __msa_subs_u_h(a0, const_a); \ + b0 = SRAI_H(b0, 6); \ + CLIP_UH_0_255(b0); \ + dst = (v16u8)__msa_pckev_b((v16i8)b0, (v16i8)b0); \ +} while (0) + +#define CALC_RGB16(y, u, v, R, G, B) do { \ + const v16u8 zero = { 0 }; \ + v8u16 y0, y1, u0, u1, v0, v1; \ + v4u32 p0, p1, p2, p3; \ + const v16u8 in_y = LD_UB(y); \ + const v16u8 in_u = LD_UB(u); \ + const v16u8 in_v = LD_UB(v); \ + ILVRL_UW4(in_y, p0, p1, p2, p3); \ + MULTHI_16(p0, p1, p2, p3, 19077, y0, y1); \ + ILVRL_UW4(in_v, p0, p1, p2, p3); \ + MULTHI_16(p0, p1, p2, p3, 26149, v0, v1); \ + CALC_R16(y0, y1, v0, v1, R); \ + MULTHI_16(p0, p1, p2, p3, 13320, v0, v1); \ + ILVRL_UW4(in_u, p0, p1, p2, p3); \ + MULTHI_16(p0, p1, p2, p3, 6419, u0, u1); \ + CALC_G16(y0, y1, u0, u1, v0, v1, G); \ + MULTHI_16(p0, p1, p2, p3, 33050, u0, u1); \ + CALC_B16(y0, y1, u0, u1, B); \ +} while (0) + +#define CALC_RGB8(y, u, v, R, G, B) do { \ + const v16u8 zero = { 0 }; \ + v8u16 y0, u0, v0; \ + v4u32 p0, p1; \ + const v16u8 in_y = LD_UB(y); \ + const v16u8 in_u = LD_UB(u); \ + const v16u8 in_v = LD_UB(v); \ + ILVR_UW2(in_y, p0, p1); \ + MULTHI_8(p0, p1, 19077, y0); \ + ILVR_UW2(in_v, p0, p1); \ + MULTHI_8(p0, p1, 26149, v0); \ + CALC_R8(y0, v0, R); \ + MULTHI_8(p0, p1, 13320, v0); \ + ILVR_UW2(in_u, p0, p1); \ + MULTHI_8(p0, p1, 6419, u0); \ + CALC_G8(y0, u0, v0, G); \ + MULTHI_8(p0, p1, 33050, u0); \ + CALC_B8(y0, u0, B); \ +} while (0) + +#define STORE16_3(a0, a1, a2, dst) do { \ + const v16u8 mask0 = { 0, 1, 16, 2, 3, 17, 4, 5, 18, 6, 7, 19, \ + 8, 9, 20, 10 }; \ + const v16u8 mask1 = { 0, 21, 1, 2, 22, 3, 4, 23, 5, 6, 24, 7, \ + 8, 25, 9, 10 }; \ + const v16u8 mask2 = { 26, 0, 1, 27, 2, 3, 28, 4, 5, 29, 6, 7, \ + 30, 8, 9, 31 }; \ + v16u8 out0, out1, out2, tmp0, tmp1, tmp2; \ + ILVRL_B2_UB(a1, a0, tmp0, tmp1); \ + out0 = VSHF_UB(tmp0, a2, mask0); \ + tmp2 = SLDI_UB(tmp1, tmp0, 11); \ + out1 = VSHF_UB(tmp2, a2, mask1); \ + tmp2 = SLDI_UB(tmp1, tmp1, 6); \ + out2 = VSHF_UB(tmp2, a2, mask2); \ + ST_UB(out0, dst + 0); \ + ST_UB(out1, dst + 16); \ + ST_UB(out2, dst + 32); \ +} while (0) + +#define STORE8_3(a0, a1, a2, dst) do { \ + int64_t out_m; \ + const v16u8 mask0 = { 0, 1, 16, 2, 3, 17, 4, 5, 18, 6, 7, 19, \ + 8, 9, 20, 10 }; \ + const v16u8 mask1 = { 11, 21, 12, 13, 22, 14, 15, 23, \ + 255, 255, 255, 255, 255, 255, 255, 255 }; \ + const v16u8 tmp0 = (v16u8)__msa_ilvr_b((v16i8)a1, (v16i8)a0); \ + v16u8 out0, out1; \ + VSHF_B2_UB(tmp0, a2, tmp0, a2, mask0, mask1, out0, out1); \ + ST_UB(out0, dst); \ + out_m = __msa_copy_s_d((v2i64)out1, 0); \ + SD(out_m, dst + 16); \ +} while (0) + +#define STORE16_4(a0, a1, a2, a3, dst) do { \ + v16u8 tmp0, tmp1, tmp2, tmp3; \ + v16u8 out0, out1, out2, out3; \ + ILVRL_B2_UB(a1, a0, tmp0, tmp1); \ + ILVRL_B2_UB(a3, a2, tmp2, tmp3); \ + ILVRL_H2_UB(tmp2, tmp0, out0, out1); \ + ILVRL_H2_UB(tmp3, tmp1, out2, out3); \ + ST_UB(out0, dst + 0); \ + ST_UB(out1, dst + 16); \ + ST_UB(out2, dst + 32); \ + ST_UB(out3, dst + 48); \ +} while (0) + +#define STORE8_4(a0, a1, a2, a3, dst) do { \ + v16u8 tmp0, tmp1, tmp2, tmp3; \ + ILVR_B2_UB(a1, a0, a3, a2, tmp0, tmp1); \ + ILVRL_H2_UB(tmp1, tmp0, tmp2, tmp3); \ + ST_UB(tmp2, dst + 0); \ + ST_UB(tmp3, dst + 16); \ +} while (0) + +#define STORE2_16(a0, a1, dst) do { \ + v16u8 out0, out1; \ + ILVRL_B2_UB(a1, a0, out0, out1); \ + ST_UB(out0, dst + 0); \ + ST_UB(out1, dst + 16); \ +} while (0) + +#define STORE2_8(a0, a1, dst) do { \ + const v16u8 out0 = (v16u8)__msa_ilvr_b((v16i8)a1, (v16i8)a0); \ + ST_UB(out0, dst); \ +} while (0) + +#define CALC_RGBA4444(y, u, v, out0, out1, N, dst) do { \ + CALC_RGB##N(y, u, v, R, G, B); \ + tmp0 = ANDI_B(R, 0xf0); \ + tmp1 = SRAI_B(G, 4); \ + RG = tmp0 | tmp1; \ + tmp0 = ANDI_B(B, 0xf0); \ + BA = ORI_B(tmp0, 0x0f); \ + STORE2_##N(out0, out1, dst); \ +} while (0) + +#define CALC_RGB565(y, u, v, out0, out1, N, dst) do { \ + CALC_RGB##N(y, u, v, R, G, B); \ + tmp0 = ANDI_B(R, 0xf8); \ + tmp1 = SRAI_B(G, 5); \ + RG = tmp0 | tmp1; \ + tmp0 = SLLI_B(G, 3); \ + tmp1 = ANDI_B(tmp0, 0xe0); \ + tmp0 = SRAI_B(B, 3); \ + GB = tmp0 | tmp1; \ + STORE2_##N(out0, out1, dst); \ +} while (0) + +static WEBP_INLINE int Clip8(int v) { + return v < 0 ? 0 : v > 255 ? 255 : v; +} + +static void YuvToRgb(int y, int u, int v, uint8_t* const rgb) { + const int y1 = MultHi(y, 19077); + const int r1 = y1 + MultHi(v, 26149) - 14234; + const int g1 = y1 - MultHi(u, 6419) - MultHi(v, 13320) + 8708; + const int b1 = y1 + MultHi(u, 33050) - 17685; + rgb[0] = Clip8(r1 >> 6); + rgb[1] = Clip8(g1 >> 6); + rgb[2] = Clip8(b1 >> 6); +} + +static void YuvToBgr(int y, int u, int v, uint8_t* const bgr) { + const int y1 = MultHi(y, 19077); + const int r1 = y1 + MultHi(v, 26149) - 14234; + const int g1 = y1 - MultHi(u, 6419) - MultHi(v, 13320) + 8708; + const int b1 = y1 + MultHi(u, 33050) - 17685; + bgr[0] = Clip8(b1 >> 6); + bgr[1] = Clip8(g1 >> 6); + bgr[2] = Clip8(r1 >> 6); +} + +static void YuvToRgb565(int y, int u, int v, uint8_t* const rgb) { + const int y1 = MultHi(y, 19077); + const int r1 = y1 + MultHi(v, 26149) - 14234; + const int g1 = y1 - MultHi(u, 6419) - MultHi(v, 13320) + 8708; + const int b1 = y1 + MultHi(u, 33050) - 17685; + const int r = Clip8(r1 >> 6); + const int g = Clip8(g1 >> 6); + const int b = Clip8(b1 >> 6); + const int rg = (r & 0xf8) | (g >> 5); + const int gb = ((g << 3) & 0xe0) | (b >> 3); +#ifdef WEBP_SWAP_16BIT_CSP + rgb[0] = gb; + rgb[1] = rg; +#else + rgb[0] = rg; + rgb[1] = gb; +#endif +} + +static void YuvToRgba4444(int y, int u, int v, uint8_t* const argb) { + const int y1 = MultHi(y, 19077); + const int r1 = y1 + MultHi(v, 26149) - 14234; + const int g1 = y1 - MultHi(u, 6419) - MultHi(v, 13320) + 8708; + const int b1 = y1 + MultHi(u, 33050) - 17685; + const int r = Clip8(r1 >> 6); + const int g = Clip8(g1 >> 6); + const int b = Clip8(b1 >> 6); + const int rg = (r & 0xf0) | (g >> 4); + const int ba = (b & 0xf0) | 0x0f; // overwrite the lower 4 bits +#ifdef WEBP_SWAP_16BIT_CSP + argb[0] = ba; + argb[1] = rg; +#else + argb[0] = rg; + argb[1] = ba; +#endif +} + +static void YuvToArgb(uint8_t y, uint8_t u, uint8_t v, uint8_t* const argb) { + argb[0] = 0xff; + YuvToRgb(y, u, v, argb + 1); +} + +static void YuvToBgra(uint8_t y, uint8_t u, uint8_t v, uint8_t* const bgra) { + YuvToBgr(y, u, v, bgra); + bgra[3] = 0xff; +} + +static void YuvToRgba(uint8_t y, uint8_t u, uint8_t v, uint8_t* const rgba) { + YuvToRgb(y, u, v, rgba); + rgba[3] = 0xff; +} + +static void YuvToRgbLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_3(R, G, B, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 3; + length -= 16; + } + if (length > 8) { + uint8_t temp[3 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(temp, u, v, R, G, B); + STORE16_3(R, G, B, temp); + memcpy(dst, temp, length * 3 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[3 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_3(R, G, B, temp); + memcpy(dst, temp, length * 3 * sizeof(*dst)); + } +} + +static void YuvToBgrLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_3(B, G, R, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 3; + length -= 16; + } + if (length > 8) { + uint8_t temp[3 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(temp, u, v, R, G, B); + STORE16_3(B, G, R, temp); + memcpy(dst, temp, length * 3 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[3 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_3(B, G, R, temp); + memcpy(dst, temp, length * 3 * sizeof(*dst)); + } +} + +static void YuvToRgbaLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + const v16u8 A = (v16u8)__msa_ldi_b(0xff); + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_4(R, G, B, A, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 4; + length -= 16; + } + if (length > 8) { + uint8_t temp[4 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(&temp[0], u, v, R, G, B); + STORE16_4(R, G, B, A, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[4 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_4(R, G, B, A, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } +} + +static void YuvToBgraLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + const v16u8 A = (v16u8)__msa_ldi_b(0xff); + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_4(B, G, R, A, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 4; + length -= 16; + } + if (length > 8) { + uint8_t temp[4 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(temp, u, v, R, G, B); + STORE16_4(B, G, R, A, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[4 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_4(B, G, R, A, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } +} + +static void YuvToArgbLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + const v16u8 A = (v16u8)__msa_ldi_b(0xff); + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_4(A, R, G, B, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 4; + length -= 16; + } + if (length > 8) { + uint8_t temp[4 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(temp, u, v, R, G, B); + STORE16_4(A, R, G, B, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[4 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_4(A, R, G, B, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } +} + +static void YuvToRgba4444Line(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B, RG, BA, tmp0, tmp1; + while (length >= 16) { + #ifdef WEBP_SWAP_16BIT_CSP + CALC_RGBA4444(y, u, v, BA, RG, 16, dst); + #else + CALC_RGBA4444(y, u, v, RG, BA, 16, dst); + #endif + y += 16; + u += 16; + v += 16; + dst += 16 * 2; + length -= 16; + } + if (length > 8) { + uint8_t temp[2 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); +#ifdef WEBP_SWAP_16BIT_CSP + CALC_RGBA4444(temp, u, v, BA, RG, 16, temp); +#else + CALC_RGBA4444(temp, u, v, RG, BA, 16, temp); +#endif + memcpy(dst, temp, length * 2 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[2 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); +#ifdef WEBP_SWAP_16BIT_CSP + CALC_RGBA4444(temp, u, v, BA, RG, 8, temp); +#else + CALC_RGBA4444(temp, u, v, RG, BA, 8, temp); +#endif + memcpy(dst, temp, length * 2 * sizeof(*dst)); + } +} + +static void YuvToRgb565Line(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B, RG, GB, tmp0, tmp1; + while (length >= 16) { + #ifdef WEBP_SWAP_16BIT_CSP + CALC_RGB565(y, u, v, GB, RG, 16, dst); + #else + CALC_RGB565(y, u, v, RG, GB, 16, dst); + #endif + y += 16; + u += 16; + v += 16; + dst += 16 * 2; + length -= 16; + } + if (length > 8) { + uint8_t temp[2 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); +#ifdef WEBP_SWAP_16BIT_CSP + CALC_RGB565(temp, u, v, GB, RG, 16, temp); +#else + CALC_RGB565(temp, u, v, RG, GB, 16, temp); +#endif + memcpy(dst, temp, length * 2 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[2 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); +#ifdef WEBP_SWAP_16BIT_CSP + CALC_RGB565(temp, u, v, GB, RG, 8, temp); +#else + CALC_RGB565(temp, u, v, RG, GB, 8, temp); +#endif + memcpy(dst, temp, length * 2 * sizeof(*dst)); + } +} + +#define UPSAMPLE_32PIXELS(a, b, c, d) do { \ + v16u8 s = __msa_aver_u_b(a, d); \ + v16u8 t = __msa_aver_u_b(b, c); \ + const v16u8 st = s ^ t; \ + v16u8 ad = a ^ d; \ + v16u8 bc = b ^ c; \ + v16u8 t0 = ad | bc; \ + v16u8 t1 = t0 | st; \ + v16u8 t2 = ANDI_B(t1, 1); \ + v16u8 t3 = __msa_aver_u_b(s, t); \ + const v16u8 k = t3 - t2; \ + v16u8 diag1, diag2; \ + AVER_UB2_UB(t, k, s, k, t0, t1); \ + bc = bc & st; \ + ad = ad & st; \ + t = t ^ k; \ + s = s ^ k; \ + t2 = bc | t; \ + t3 = ad | s; \ + t2 = ANDI_B(t2, 1); \ + t3 = ANDI_B(t3, 1); \ + SUB2(t0, t2, t1, t3, diag1, diag2); \ + AVER_UB2_UB(a, diag1, b, diag2, t0, t1); \ + ILVRL_B2_UB(t1, t0, a, b); \ + if (pbot_y != NULL) { \ + AVER_UB2_UB(c, diag2, d, diag1, t0, t1); \ + ILVRL_B2_UB(t1, t0, c, d); \ + } \ +} while (0) + +#define UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bot_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bot_dst, int len) \ +{ \ + int size = (len - 1) >> 1; \ + uint8_t temp_u[64]; \ + uint8_t temp_v[64]; \ + const uint32_t tl_uv = ((top_u[0]) | ((top_v[0]) << 16)); \ + const uint32_t l_uv = ((cur_u[0]) | ((cur_v[0]) << 16)); \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + const uint8_t* ptop_y = &top_y[1]; \ + uint8_t *ptop_dst = top_dst + XSTEP; \ + const uint8_t* pbot_y = &bot_y[1]; \ + uint8_t *pbot_dst = bot_dst + XSTEP; \ + \ + FUNC(top_y[0], uv0 & 0xff, (uv0 >> 16), top_dst); \ + if (bot_y != NULL) { \ + const uint32_t uv1 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bot_y[0], uv1 & 0xff, (uv1 >> 16), bot_dst); \ + } \ + while (size >= 16) { \ + v16u8 tu0, tu1, tv0, tv1, cu0, cu1, cv0, cv1; \ + LD_UB2(top_u, 1, tu0, tu1); \ + LD_UB2(cur_u, 1, cu0, cu1); \ + LD_UB2(top_v, 1, tv0, tv1); \ + LD_UB2(cur_v, 1, cv0, cv1); \ + UPSAMPLE_32PIXELS(tu0, tu1, cu0, cu1); \ + UPSAMPLE_32PIXELS(tv0, tv1, cv0, cv1); \ + ST_UB4(tu0, tu1, cu0, cu1, &temp_u[0], 16); \ + ST_UB4(tv0, tv1, cv0, cv1, &temp_v[0], 16); \ + FUNC##Line(ptop_y, &temp_u[ 0], &temp_v[0], ptop_dst, 32); \ + if (bot_y != NULL) { \ + FUNC##Line(pbot_y, &temp_u[32], &temp_v[32], pbot_dst, 32); \ + } \ + ptop_y += 32; \ + pbot_y += 32; \ + ptop_dst += XSTEP * 32; \ + pbot_dst += XSTEP * 32; \ + top_u += 16; \ + top_v += 16; \ + cur_u += 16; \ + cur_v += 16; \ + size -= 16; \ + } \ + if (size > 0) { \ + v16u8 tu0, tu1, tv0, tv1, cu0, cu1, cv0, cv1; \ + memcpy(&temp_u[ 0], top_u, 17 * sizeof(uint8_t)); \ + memcpy(&temp_u[32], cur_u, 17 * sizeof(uint8_t)); \ + memcpy(&temp_v[ 0], top_v, 17 * sizeof(uint8_t)); \ + memcpy(&temp_v[32], cur_v, 17 * sizeof(uint8_t)); \ + LD_UB2(&temp_u[ 0], 1, tu0, tu1); \ + LD_UB2(&temp_u[32], 1, cu0, cu1); \ + LD_UB2(&temp_v[ 0], 1, tv0, tv1); \ + LD_UB2(&temp_v[32], 1, cv0, cv1); \ + UPSAMPLE_32PIXELS(tu0, tu1, cu0, cu1); \ + UPSAMPLE_32PIXELS(tv0, tv1, cv0, cv1); \ + ST_UB4(tu0, tu1, cu0, cu1, &temp_u[0], 16); \ + ST_UB4(tv0, tv1, cv0, cv1, &temp_v[0], 16); \ + FUNC##Line(ptop_y, &temp_u[ 0], &temp_v[0], ptop_dst, size * 2); \ + if (bot_y != NULL) { \ + FUNC##Line(pbot_y, &temp_u[32], &temp_v[32], pbot_dst, size * 2); \ + } \ + top_u += size; \ + top_v += size; \ + cur_u += size; \ + cur_v += size; \ + } \ + if (!(len & 1)) { \ + const uint32_t t0 = ((top_u[0]) | ((top_v[0]) << 16)); \ + const uint32_t c0 = ((cur_u[0]) | ((cur_v[0]) << 16)); \ + const uint32_t tmp0 = (3 * t0 + c0 + 0x00020002u) >> 2; \ + FUNC(top_y[len - 1], tmp0 & 0xff, (tmp0 >> 16), \ + top_dst + (len - 1) * XSTEP); \ + if (bot_y != NULL) { \ + const uint32_t tmp1 = (3 * c0 + t0 + 0x00020002u) >> 2; \ + FUNC(bot_y[len - 1], tmp1 & 0xff, (tmp1 >> 16), \ + bot_dst + (len - 1) * XSTEP); \ + } \ + } \ +} + +UPSAMPLE_FUNC(UpsampleRgbLinePair, YuvToRgb, 3) +UPSAMPLE_FUNC(UpsampleBgrLinePair, YuvToBgr, 3) +UPSAMPLE_FUNC(UpsampleRgbaLinePair, YuvToRgba, 4) +UPSAMPLE_FUNC(UpsampleBgraLinePair, YuvToBgra, 4) +UPSAMPLE_FUNC(UpsampleArgbLinePair, YuvToArgb, 4) +UPSAMPLE_FUNC(UpsampleRgba4444LinePair, YuvToRgba4444, 2) +UPSAMPLE_FUNC(UpsampleRgb565LinePair, YuvToRgb565, 2) + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersMSA(void) { + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair; + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair; +} + +#endif // FANCY_UPSAMPLING + +#endif // WEBP_USE_MSA + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_MSA)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersMSA) +#endif diff --git a/3rdparty/libwebp/dsp/upsampling_neon.c b/3rdparty/libwebp/dsp/upsampling_neon.c index d118895909..d371a834ff 100644 --- a/3rdparty/libwebp/dsp/upsampling_neon.c +++ b/3rdparty/libwebp/dsp/upsampling_neon.c @@ -14,60 +14,48 @@ #include "./dsp.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - #if defined(WEBP_USE_NEON) #include #include #include +#include "./neon.h" #include "./yuv.h" #ifdef FANCY_UPSAMPLING +//----------------------------------------------------------------------------- +// U/V upsampling + // Loads 9 pixels each from rows r1 and r2 and generates 16 pixels. -#define UPSAMPLE_16PIXELS(r1, r2, out) { \ - uint8x8_t a = vld1_u8(r1); \ - uint8x8_t b = vld1_u8(r1 + 1); \ - uint8x8_t c = vld1_u8(r2); \ - uint8x8_t d = vld1_u8(r2 + 1); \ - \ - uint16x8_t al = vshll_n_u8(a, 1); \ - uint16x8_t bl = vshll_n_u8(b, 1); \ - uint16x8_t cl = vshll_n_u8(c, 1); \ - uint16x8_t dl = vshll_n_u8(d, 1); \ - \ - uint8x8_t diag1, diag2; \ - uint16x8_t sl; \ - \ +#define UPSAMPLE_16PIXELS(r1, r2, out) do { \ + const uint8x8_t a = vld1_u8(r1 + 0); \ + const uint8x8_t b = vld1_u8(r1 + 1); \ + const uint8x8_t c = vld1_u8(r2 + 0); \ + const uint8x8_t d = vld1_u8(r2 + 1); \ /* a + b + c + d */ \ - sl = vaddl_u8(a, b); \ - sl = vaddw_u8(sl, c); \ - sl = vaddw_u8(sl, d); \ + const uint16x8_t ad = vaddl_u8(a, d); \ + const uint16x8_t bc = vaddl_u8(b, c); \ + const uint16x8_t abcd = vaddq_u16(ad, bc); \ + /* 3a + b + c + 3d */ \ + const uint16x8_t al = vaddq_u16(abcd, vshlq_n_u16(ad, 1)); \ + /* a + 3b + 3c + d */ \ + const uint16x8_t bl = vaddq_u16(abcd, vshlq_n_u16(bc, 1)); \ \ - al = vaddq_u16(sl, al); /* 3a + b + c + d */ \ - bl = vaddq_u16(sl, bl); /* a + 3b + c + d */ \ + const uint8x8_t diag2 = vshrn_n_u16(al, 3); \ + const uint8x8_t diag1 = vshrn_n_u16(bl, 3); \ \ - al = vaddq_u16(al, dl); /* 3a + b + c + 3d */ \ - bl = vaddq_u16(bl, cl); /* a + 3b + 3c + d */ \ + const uint8x8_t A = vrhadd_u8(a, diag1); \ + const uint8x8_t B = vrhadd_u8(b, diag2); \ + const uint8x8_t C = vrhadd_u8(c, diag2); \ + const uint8x8_t D = vrhadd_u8(d, diag1); \ \ - diag2 = vshrn_n_u16(al, 3); \ - diag1 = vshrn_n_u16(bl, 3); \ - \ - a = vrhadd_u8(a, diag1); \ - b = vrhadd_u8(b, diag2); \ - c = vrhadd_u8(c, diag2); \ - d = vrhadd_u8(d, diag1); \ - \ - { \ - const uint8x8x2_t a_b = {{ a, b }}; \ - const uint8x8x2_t c_d = {{ c, d }}; \ - vst2_u8(out, a_b); \ - vst2_u8(out + 32, c_d); \ - } \ -} + uint8x8x2_t A_B, C_D; \ + INIT_VECTOR2(A_B, A, B); \ + INIT_VECTOR2(C_D, C, D); \ + vst2_u8(out + 0, A_B); \ + vst2_u8(out + 32, C_D); \ +} while (0) // Turn the macro into a function for reducing code-size when non-critical static void Upsample16Pixels(const uint8_t *r1, const uint8_t *r2, @@ -85,125 +73,119 @@ static void Upsample16Pixels(const uint8_t *r1, const uint8_t *r2, Upsample16Pixels(r1, r2, out); \ } -#define CY 76283 -#define CVR 89858 -#define CUG 22014 -#define CVG 45773 -#define CUB 113618 +//----------------------------------------------------------------------------- +// YUV->RGB conversion -static const int16_t coef[4] = { CVR / 4, CUG, CVG / 2, CUB / 4 }; +// note: we represent the 33050 large constant as 32768 + 282 +static const int16_t kCoeffs1[4] = { 19077, 26149, 6419, 13320 }; -#define CONVERT8(FMT, XSTEP, N, src_y, src_uv, out, cur_x) { \ - int i; \ - for (i = 0; i < N; i += 8) { \ - int off = ((cur_x) + i) * XSTEP; \ - uint8x8_t y = vld1_u8(src_y + (cur_x) + i); \ - uint8x8_t u = vld1_u8((src_uv) + i); \ - uint8x8_t v = vld1_u8((src_uv) + i + 16); \ - int16x8_t yy = vreinterpretq_s16_u16(vsubl_u8(y, u16)); \ - int16x8_t uu = vreinterpretq_s16_u16(vsubl_u8(u, u128)); \ - int16x8_t vv = vreinterpretq_s16_u16(vsubl_u8(v, u128)); \ - \ - int16x8_t ud = vshlq_n_s16(uu, 1); \ - int16x8_t vd = vshlq_n_s16(vv, 1); \ - \ - int32x4_t vrl = vqdmlal_lane_s16(vshll_n_s16(vget_low_s16(vv), 1), \ - vget_low_s16(vd), cf16, 0); \ - int32x4_t vrh = vqdmlal_lane_s16(vshll_n_s16(vget_high_s16(vv), 1), \ - vget_high_s16(vd), cf16, 0); \ - int16x8_t vr = vcombine_s16(vrshrn_n_s32(vrl, 16), \ - vrshrn_n_s32(vrh, 16)); \ - \ - int32x4_t vl = vmovl_s16(vget_low_s16(vv)); \ - int32x4_t vh = vmovl_s16(vget_high_s16(vv)); \ - int32x4_t ugl = vmlal_lane_s16(vl, vget_low_s16(uu), cf16, 1); \ - int32x4_t ugh = vmlal_lane_s16(vh, vget_high_s16(uu), cf16, 1); \ - int32x4_t gcl = vqdmlal_lane_s16(ugl, vget_low_s16(vv), cf16, 2); \ - int32x4_t gch = vqdmlal_lane_s16(ugh, vget_high_s16(vv), cf16, 2); \ - int16x8_t gc = vcombine_s16(vrshrn_n_s32(gcl, 16), \ - vrshrn_n_s32(gch, 16)); \ - \ - int32x4_t ubl = vqdmlal_lane_s16(vshll_n_s16(vget_low_s16(uu), 1), \ - vget_low_s16(ud), cf16, 3); \ - int32x4_t ubh = vqdmlal_lane_s16(vshll_n_s16(vget_high_s16(uu), 1), \ - vget_high_s16(ud), cf16, 3); \ - int16x8_t ub = vcombine_s16(vrshrn_n_s32(ubl, 16), \ - vrshrn_n_s32(ubh, 16)); \ - \ - int32x4_t rl = vaddl_s16(vget_low_s16(yy), vget_low_s16(vr)); \ - int32x4_t rh = vaddl_s16(vget_high_s16(yy), vget_high_s16(vr)); \ - int32x4_t gl = vsubl_s16(vget_low_s16(yy), vget_low_s16(gc)); \ - int32x4_t gh = vsubl_s16(vget_high_s16(yy), vget_high_s16(gc)); \ - int32x4_t bl = vaddl_s16(vget_low_s16(yy), vget_low_s16(ub)); \ - int32x4_t bh = vaddl_s16(vget_high_s16(yy), vget_high_s16(ub)); \ - \ - rl = vmulq_lane_s32(rl, cf32, 0); \ - rh = vmulq_lane_s32(rh, cf32, 0); \ - gl = vmulq_lane_s32(gl, cf32, 0); \ - gh = vmulq_lane_s32(gh, cf32, 0); \ - bl = vmulq_lane_s32(bl, cf32, 0); \ - bh = vmulq_lane_s32(bh, cf32, 0); \ - \ - y = vqmovun_s16(vcombine_s16(vrshrn_n_s32(rl, 16), \ - vrshrn_n_s32(rh, 16))); \ - u = vqmovun_s16(vcombine_s16(vrshrn_n_s32(gl, 16), \ - vrshrn_n_s32(gh, 16))); \ - v = vqmovun_s16(vcombine_s16(vrshrn_n_s32(bl, 16), \ - vrshrn_n_s32(bh, 16))); \ - STR_ ## FMT(out + off, y, u, v); \ - } \ -} +#define v255 vdup_n_u8(255) -#define v255 vmov_n_u8(255) - -#define STR_Rgb(out, r, g, b) do { \ - const uint8x8x3_t r_g_b = {{ r, g, b }}; \ +#define STORE_Rgb(out, r, g, b) do { \ + uint8x8x3_t r_g_b; \ + INIT_VECTOR3(r_g_b, r, g, b); \ vst3_u8(out, r_g_b); \ } while (0) -#define STR_Bgr(out, r, g, b) do { \ - const uint8x8x3_t b_g_r = {{ b, g, r }}; \ +#define STORE_Bgr(out, r, g, b) do { \ + uint8x8x3_t b_g_r; \ + INIT_VECTOR3(b_g_r, b, g, r); \ vst3_u8(out, b_g_r); \ } while (0) -#define STR_Rgba(out, r, g, b) do { \ - const uint8x8x4_t r_g_b_v255 = {{ r, g, b, v255 }}; \ +#define STORE_Rgba(out, r, g, b) do { \ + uint8x8x4_t r_g_b_v255; \ + INIT_VECTOR4(r_g_b_v255, r, g, b, v255); \ vst4_u8(out, r_g_b_v255); \ } while (0) -#define STR_Bgra(out, r, g, b) do { \ - const uint8x8x4_t b_g_r_v255 = {{ b, g, r, v255 }}; \ +#define STORE_Bgra(out, r, g, b) do { \ + uint8x8x4_t b_g_r_v255; \ + INIT_VECTOR4(b_g_r_v255, b, g, r, v255); \ vst4_u8(out, b_g_r_v255); \ } while (0) -#define CONVERT1(FMT, XSTEP, N, src_y, src_uv, rgb, cur_x) { \ +#define STORE_Argb(out, r, g, b) do { \ + uint8x8x4_t v255_r_g_b; \ + INIT_VECTOR4(v255_r_g_b, v255, r, g, b); \ + vst4_u8(out, v255_r_g_b); \ +} while (0) + +#if !defined(WEBP_SWAP_16BIT_CSP) +#define ZIP_U8(lo, hi) vzip_u8((lo), (hi)) +#else +#define ZIP_U8(lo, hi) vzip_u8((hi), (lo)) +#endif + +#define STORE_Rgba4444(out, r, g, b) do { \ + const uint8x8_t rg = vsri_n_u8(r, g, 4); /* shift g, insert r */ \ + const uint8x8_t ba = vsri_n_u8(b, v255, 4); /* shift a, insert b */ \ + const uint8x8x2_t rgba4444 = ZIP_U8(rg, ba); \ + vst1q_u8(out, vcombine_u8(rgba4444.val[0], rgba4444.val[1])); \ +} while (0) + +#define STORE_Rgb565(out, r, g, b) do { \ + const uint8x8_t rg = vsri_n_u8(r, g, 5); /* shift g and insert r */ \ + const uint8x8_t g1 = vshl_n_u8(g, 3); /* pre-shift g: 3bits */ \ + const uint8x8_t gb = vsri_n_u8(g1, b, 3); /* shift b and insert g */ \ + const uint8x8x2_t rgb565 = ZIP_U8(rg, gb); \ + vst1q_u8(out, vcombine_u8(rgb565.val[0], rgb565.val[1])); \ +} while (0) + +#define CONVERT8(FMT, XSTEP, N, src_y, src_uv, out, cur_x) do { \ + int i; \ + for (i = 0; i < N; i += 8) { \ + const int off = ((cur_x) + i) * XSTEP; \ + const uint8x8_t y = vld1_u8((src_y) + (cur_x) + i); \ + const uint8x8_t u = vld1_u8((src_uv) + i + 0); \ + const uint8x8_t v = vld1_u8((src_uv) + i + 16); \ + const int16x8_t Y0 = vreinterpretq_s16_u16(vshll_n_u8(y, 7)); \ + const int16x8_t U0 = vreinterpretq_s16_u16(vshll_n_u8(u, 7)); \ + const int16x8_t V0 = vreinterpretq_s16_u16(vshll_n_u8(v, 7)); \ + const int16x8_t Y1 = vqdmulhq_lane_s16(Y0, coeff1, 0); \ + const int16x8_t R0 = vqdmulhq_lane_s16(V0, coeff1, 1); \ + const int16x8_t G0 = vqdmulhq_lane_s16(U0, coeff1, 2); \ + const int16x8_t G1 = vqdmulhq_lane_s16(V0, coeff1, 3); \ + const int16x8_t B0 = vqdmulhq_n_s16(U0, 282); \ + const int16x8_t R1 = vqaddq_s16(Y1, R_Rounder); \ + const int16x8_t G2 = vqaddq_s16(Y1, G_Rounder); \ + const int16x8_t B1 = vqaddq_s16(Y1, B_Rounder); \ + const int16x8_t R2 = vqaddq_s16(R0, R1); \ + const int16x8_t G3 = vqaddq_s16(G0, G1); \ + const int16x8_t B2 = vqaddq_s16(B0, B1); \ + const int16x8_t G4 = vqsubq_s16(G2, G3); \ + const int16x8_t B3 = vqaddq_s16(B2, U0); \ + const uint8x8_t R = vqshrun_n_s16(R2, YUV_FIX2); \ + const uint8x8_t G = vqshrun_n_s16(G4, YUV_FIX2); \ + const uint8x8_t B = vqshrun_n_s16(B3, YUV_FIX2); \ + STORE_ ## FMT(out + off, R, G, B); \ + } \ +} while (0) + +#define CONVERT1(FUNC, XSTEP, N, src_y, src_uv, rgb, cur_x) { \ int i; \ for (i = 0; i < N; i++) { \ - int off = ((cur_x) + i) * XSTEP; \ - int y = src_y[(cur_x) + i]; \ - int u = (src_uv)[i]; \ - int v = (src_uv)[i + 16]; \ - VP8YuvTo ## FMT(y, u, v, rgb + off); \ + const int off = ((cur_x) + i) * XSTEP; \ + const int y = src_y[(cur_x) + i]; \ + const int u = (src_uv)[i]; \ + const int v = (src_uv)[i + 16]; \ + FUNC(y, u, v, rgb + off); \ } \ } #define CONVERT2RGB_8(FMT, XSTEP, top_y, bottom_y, uv, \ top_dst, bottom_dst, cur_x, len) { \ - if (top_y) { \ - CONVERT8(FMT, XSTEP, len, top_y, uv, top_dst, cur_x) \ - } \ - if (bottom_y) { \ - CONVERT8(FMT, XSTEP, len, bottom_y, (uv) + 32, bottom_dst, cur_x) \ + CONVERT8(FMT, XSTEP, len, top_y, uv, top_dst, cur_x); \ + if (bottom_y != NULL) { \ + CONVERT8(FMT, XSTEP, len, bottom_y, (uv) + 32, bottom_dst, cur_x); \ } \ } -#define CONVERT2RGB_1(FMT, XSTEP, top_y, bottom_y, uv, \ +#define CONVERT2RGB_1(FUNC, XSTEP, top_y, bottom_y, uv, \ top_dst, bottom_dst, cur_x, len) { \ - if (top_y) { \ - CONVERT1(FMT, XSTEP, len, top_y, uv, top_dst, cur_x); \ - } \ - if (bottom_y) { \ - CONVERT1(FMT, XSTEP, len, bottom_y, (uv) + 32, bottom_dst, cur_x); \ + CONVERT1(FUNC, XSTEP, len, top_y, uv, top_dst, cur_x); \ + if (bottom_y != NULL) { \ + CONVERT1(FUNC, XSTEP, len, bottom_y, (uv) + 32, bottom_dst, cur_x); \ } \ } @@ -225,18 +207,19 @@ static void FUNC_NAME(const uint8_t *top_y, const uint8_t *bottom_y, \ const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ \ - const int16x4_t cf16 = vld1_s16(coef); \ - const int32x2_t cf32 = vmov_n_s32(CY); \ - const uint8x8_t u16 = vmov_n_u8(16); \ - const uint8x8_t u128 = vmov_n_u8(128); \ + const int16x4_t coeff1 = vld1_s16(kCoeffs1); \ + const int16x8_t R_Rounder = vdupq_n_s16(-14234); \ + const int16x8_t G_Rounder = vdupq_n_s16(8708); \ + const int16x8_t B_Rounder = vdupq_n_s16(-17685); \ \ /* Treat the first pixel in regular way */ \ - if (top_y) { \ + assert(top_y != NULL); \ + { \ const int u0 = (top_u[0] + u_diag) >> 1; \ const int v0 = (top_v[0] + v_diag) >> 1; \ VP8YuvTo ## FMT(top_y[0], u0, v0, top_dst); \ } \ - if (bottom_y) { \ + if (bottom_y != NULL) { \ const int u0 = (cur_u[0] + u_diag) >> 1; \ const int v0 = (cur_v[0] + v_diag) >> 1; \ VP8YuvTo ## FMT(bottom_y[0], u0, v0, bottom_dst); \ @@ -255,40 +238,44 @@ static void FUNC_NAME(const uint8_t *top_y, const uint8_t *bottom_y, \ \ UPSAMPLE_LAST_BLOCK(top_u, cur_u, leftover, r_uv); \ UPSAMPLE_LAST_BLOCK(top_v, cur_v, leftover, r_uv + 16); \ - CONVERT2RGB_1(FMT, XSTEP, top_y, bottom_y, r_uv, \ + CONVERT2RGB_1(VP8YuvTo ## FMT, XSTEP, top_y, bottom_y, r_uv, \ top_dst, bottom_dst, last_pos, len - last_pos); \ } // NEON variants of the fancy upsampler. -NEON_UPSAMPLE_FUNC(UpsampleRgbLinePairNEON, Rgb, 3) -NEON_UPSAMPLE_FUNC(UpsampleBgrLinePairNEON, Bgr, 3) -NEON_UPSAMPLE_FUNC(UpsampleRgbaLinePairNEON, Rgba, 4) -NEON_UPSAMPLE_FUNC(UpsampleBgraLinePairNEON, Bgra, 4) - -#endif // FANCY_UPSAMPLING - -#endif // WEBP_USE_NEON +NEON_UPSAMPLE_FUNC(UpsampleRgbLinePair, Rgb, 3) +NEON_UPSAMPLE_FUNC(UpsampleBgrLinePair, Bgr, 3) +NEON_UPSAMPLE_FUNC(UpsampleRgbaLinePair, Rgba, 4) +NEON_UPSAMPLE_FUNC(UpsampleBgraLinePair, Bgra, 4) +NEON_UPSAMPLE_FUNC(UpsampleArgbLinePair, Argb, 4) +NEON_UPSAMPLE_FUNC(UpsampleRgba4444LinePair, Rgba4444, 2) +NEON_UPSAMPLE_FUNC(UpsampleRgb565LinePair, Rgb565, 2) //------------------------------------------------------------------------------ +// Entry point extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; -void WebPInitUpsamplersNEON(void) { -#if defined(WEBP_USE_NEON) - WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePairNEON; - WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePairNEON; - WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePairNEON; - WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePairNEON; -#endif // WEBP_USE_NEON +extern void WebPInitUpsamplersNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersNEON(void) { + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair; + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair; } -void WebPInitPremultiplyNEON(void) { -#if defined(WEBP_USE_NEON) - WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePairNEON; - WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePairNEON; -#endif // WEBP_USE_NEON -} +#endif // FANCY_UPSAMPLING -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" +#endif // WEBP_USE_NEON + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_NEON)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersNEON) #endif diff --git a/3rdparty/libwebp/dsp/upsampling_sse2.c b/3rdparty/libwebp/dsp/upsampling_sse2.c index f31d04845e..b5b668900f 100644 --- a/3rdparty/libwebp/dsp/upsampling_sse2.c +++ b/3rdparty/libwebp/dsp/upsampling_sse2.c @@ -13,10 +13,6 @@ #include "./dsp.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - #if defined(WEBP_USE_SSE2) #include @@ -51,7 +47,7 @@ extern "C" { (out) = _mm_sub_epi8(tmp0, tmp4); /* (k + in + 1) / 2 - lsb_correction */ \ } while (0) -// pack and store two alterning pixel rows +// pack and store two alternating pixel rows #define PACK_AND_STORE(a, b, da, db, out) do { \ const __m128i t_a = _mm_avg_epu8(a, da); /* (9a + 3b + 3c + d + 8) / 16 */ \ const __m128i t_b = _mm_avg_epu8(b, db); /* (3a + 9b + c + 3d + 8) / 16 */ \ @@ -64,10 +60,10 @@ extern "C" { // Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. #define UPSAMPLE_32PIXELS(r1, r2, out) { \ const __m128i one = _mm_set1_epi8(1); \ - const __m128i a = _mm_loadu_si128((__m128i*)&(r1)[0]); \ - const __m128i b = _mm_loadu_si128((__m128i*)&(r1)[1]); \ - const __m128i c = _mm_loadu_si128((__m128i*)&(r2)[0]); \ - const __m128i d = _mm_loadu_si128((__m128i*)&(r2)[1]); \ + const __m128i a = _mm_loadu_si128((const __m128i*)&(r1)[0]); \ + const __m128i b = _mm_loadu_si128((const __m128i*)&(r1)[1]); \ + const __m128i c = _mm_loadu_si128((const __m128i*)&(r2)[0]); \ + const __m128i d = _mm_loadu_si128((const __m128i*)&(r2)[1]); \ \ const __m128i s = _mm_avg_epu8(a, d); /* s = (a + d + 1) / 2 */ \ const __m128i t = _mm_avg_epu8(b, c); /* t = (b + c + 1) / 2 */ \ @@ -87,8 +83,8 @@ extern "C" { GET_M(ad, s, diag2); /* diag2 = (3a + b + c + 3d) / 8 */ \ \ /* pack the alternate pixels */ \ - PACK_AND_STORE(a, b, diag1, diag2, &(out)[0 * 32]); \ - PACK_AND_STORE(c, d, diag2, diag1, &(out)[2 * 32]); \ + PACK_AND_STORE(a, b, diag1, diag2, out + 0); /* store top */ \ + PACK_AND_STORE(c, d, diag2, diag1, out + 2 * 32); /* store bottom */ \ } // Turn the macro into a function for reducing code-size when non-critical @@ -108,110 +104,146 @@ static void Upsample32Pixels(const uint8_t r1[], const uint8_t r2[], Upsample32Pixels(r1, r2, out); \ } -#define CONVERT2RGB(FUNC, XSTEP, top_y, bottom_y, uv, \ +#define CONVERT2RGB(FUNC, XSTEP, top_y, bottom_y, \ top_dst, bottom_dst, cur_x, num_pixels) { \ int n; \ - if (top_y) { \ - for (n = 0; n < (num_pixels); ++n) { \ - FUNC(top_y[(cur_x) + n], (uv)[n], (uv)[32 + n], \ - top_dst + ((cur_x) + n) * XSTEP); \ - } \ + for (n = 0; n < (num_pixels); ++n) { \ + FUNC(top_y[(cur_x) + n], r_u[n], r_v[n], \ + top_dst + ((cur_x) + n) * XSTEP); \ } \ - if (bottom_y) { \ + if (bottom_y != NULL) { \ for (n = 0; n < (num_pixels); ++n) { \ - FUNC(bottom_y[(cur_x) + n], (uv)[64 + n], (uv)[64 + 32 + n], \ + FUNC(bottom_y[(cur_x) + n], r_u[64 + n], r_v[64 + n], \ bottom_dst + ((cur_x) + n) * XSTEP); \ } \ } \ } +#define CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, \ + top_dst, bottom_dst, cur_x) do { \ + FUNC##32(top_y + (cur_x), r_u, r_v, top_dst + (cur_x) * XSTEP); \ + if (bottom_y != NULL) { \ + FUNC##32(bottom_y + (cur_x), r_u + 64, r_v + 64, \ + bottom_dst + (cur_x) * XSTEP); \ + } \ +} while (0) + #define SSE2_UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ const uint8_t* top_u, const uint8_t* top_v, \ const uint8_t* cur_u, const uint8_t* cur_v, \ uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ - int block; \ - /* 16 byte aligned array to cache reconstructed u and v */ \ + int uv_pos, pos; \ + /* 16byte-aligned array to cache reconstructed u and v */ \ uint8_t uv_buf[4 * 32 + 15]; \ - uint8_t* const r_uv = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~15); \ - const int uv_len = (len + 1) >> 1; \ - /* 17 pixels must be read-able for each block */ \ - const int num_blocks = (uv_len - 1) >> 4; \ - const int leftover = uv_len - num_blocks * 16; \ - const int last_pos = 1 + 32 * num_blocks; \ + uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~15); \ + uint8_t* const r_v = r_u + 32; \ \ - const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ - const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ - \ - assert(len > 0); \ - /* Treat the first pixel in regular way */ \ - if (top_y) { \ - const int u0 = (top_u[0] + u_diag) >> 1; \ - const int v0 = (top_v[0] + v_diag) >> 1; \ - FUNC(top_y[0], u0, v0, top_dst); \ + assert(top_y != NULL); \ + { /* Treat the first pixel in regular way */ \ + const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ + const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ + const int u0_t = (top_u[0] + u_diag) >> 1; \ + const int v0_t = (top_v[0] + v_diag) >> 1; \ + FUNC(top_y[0], u0_t, v0_t, top_dst); \ + if (bottom_y != NULL) { \ + const int u0_b = (cur_u[0] + u_diag) >> 1; \ + const int v0_b = (cur_v[0] + v_diag) >> 1; \ + FUNC(bottom_y[0], u0_b, v0_b, bottom_dst); \ + } \ } \ - if (bottom_y) { \ - const int u0 = (cur_u[0] + u_diag) >> 1; \ - const int v0 = (cur_v[0] + v_diag) >> 1; \ - FUNC(bottom_y[0], u0, v0, bottom_dst); \ + /* For UPSAMPLE_32PIXELS, 17 u/v values must be read-able for each block */ \ + for (pos = 1, uv_pos = 0; pos + 32 + 1 <= len; pos += 32, uv_pos += 16) { \ + UPSAMPLE_32PIXELS(top_u + uv_pos, cur_u + uv_pos, r_u); \ + UPSAMPLE_32PIXELS(top_v + uv_pos, cur_v + uv_pos, r_v); \ + CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, pos); \ } \ - \ - for (block = 0; block < num_blocks; ++block) { \ - UPSAMPLE_32PIXELS(top_u, cur_u, r_uv + 0 * 32); \ - UPSAMPLE_32PIXELS(top_v, cur_v, r_uv + 1 * 32); \ - CONVERT2RGB(FUNC, XSTEP, top_y, bottom_y, r_uv, top_dst, bottom_dst, \ - 32 * block + 1, 32) \ - top_u += 16; \ - cur_u += 16; \ - top_v += 16; \ - cur_v += 16; \ + if (len > 1) { \ + const int left_over = ((len + 1) >> 1) - (pos >> 1); \ + assert(left_over > 0); \ + UPSAMPLE_LAST_BLOCK(top_u + uv_pos, cur_u + uv_pos, left_over, r_u); \ + UPSAMPLE_LAST_BLOCK(top_v + uv_pos, cur_v + uv_pos, left_over, r_v); \ + CONVERT2RGB(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, \ + pos, len - pos); \ } \ - \ - UPSAMPLE_LAST_BLOCK(top_u, cur_u, leftover, r_uv + 0 * 32); \ - UPSAMPLE_LAST_BLOCK(top_v, cur_v, leftover, r_uv + 1 * 32); \ - CONVERT2RGB(FUNC, XSTEP, top_y, bottom_y, r_uv, top_dst, bottom_dst, \ - last_pos, len - last_pos); \ } // SSE2 variants of the fancy upsampler. -SSE2_UPSAMPLE_FUNC(UpsampleRgbLinePairSSE2, VP8YuvToRgb, 3) -SSE2_UPSAMPLE_FUNC(UpsampleBgrLinePairSSE2, VP8YuvToBgr, 3) -SSE2_UPSAMPLE_FUNC(UpsampleRgbaLinePairSSE2, VP8YuvToRgba, 4) -SSE2_UPSAMPLE_FUNC(UpsampleBgraLinePairSSE2, VP8YuvToBgra, 4) +SSE2_UPSAMPLE_FUNC(UpsampleRgbLinePair, VP8YuvToRgb, 3) +SSE2_UPSAMPLE_FUNC(UpsampleBgrLinePair, VP8YuvToBgr, 3) +SSE2_UPSAMPLE_FUNC(UpsampleRgbaLinePair, VP8YuvToRgba, 4) +SSE2_UPSAMPLE_FUNC(UpsampleBgraLinePair, VP8YuvToBgra, 4) +SSE2_UPSAMPLE_FUNC(UpsampleArgbLinePair, VP8YuvToArgb, 4) +SSE2_UPSAMPLE_FUNC(UpsampleRgba4444LinePair, VP8YuvToRgba4444, 2) +SSE2_UPSAMPLE_FUNC(UpsampleRgb565LinePair, VP8YuvToRgb565, 2) #undef GET_M #undef PACK_AND_STORE #undef UPSAMPLE_32PIXELS #undef UPSAMPLE_LAST_BLOCK #undef CONVERT2RGB +#undef CONVERT2RGB_32 #undef SSE2_UPSAMPLE_FUNC -#endif // FANCY_UPSAMPLING - -#endif // WEBP_USE_SSE2 - //------------------------------------------------------------------------------ +// Entry point extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; -void WebPInitUpsamplersSSE2(void) { -#if defined(WEBP_USE_SSE2) - WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePairSSE2; - WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePairSSE2; - WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePairSSE2; - WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePairSSE2; -#endif // WEBP_USE_SSE2 +extern void WebPInitUpsamplersSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersSSE2(void) { + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair; + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair; } -void WebPInitPremultiplySSE2(void) { -#if defined(WEBP_USE_SSE2) - WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePairSSE2; - WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePairSSE2; -#endif // WEBP_USE_SSE2 +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ + +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; +extern void WebPInitYUV444ConvertersSSE2(void); + +#define YUV444_FUNC(FUNC_NAME, CALL, XSTEP) \ +extern void WebP##FUNC_NAME##C(const uint8_t* y, const uint8_t* u, \ + const uint8_t* v, uint8_t* dst, int len); \ +static void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + const int max_len = len & ~31; \ + for (i = 0; i < max_len; i += 32) CALL(y + i, u + i, v + i, dst + i * XSTEP);\ + if (i < len) { /* C-fallback */ \ + WebP##FUNC_NAME##C(y + i, u + i, v + i, dst + i * XSTEP, len - i); \ + } \ } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" +YUV444_FUNC(Yuv444ToRgba, VP8YuvToRgba32, 4); +YUV444_FUNC(Yuv444ToBgra, VP8YuvToBgra32, 4); +YUV444_FUNC(Yuv444ToRgb, VP8YuvToRgb32, 3); +YUV444_FUNC(Yuv444ToBgr, VP8YuvToBgr32, 3); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444ConvertersSSE2(void) { + WebPYUV444Converters[MODE_RGBA] = Yuv444ToRgba; + WebPYUV444Converters[MODE_BGRA] = Yuv444ToBgra; + WebPYUV444Converters[MODE_RGB] = Yuv444ToRgb; + WebPYUV444Converters[MODE_BGR] = Yuv444ToBgr; +} + +#else + +WEBP_DSP_INIT_STUB(WebPInitYUV444ConvertersSSE2) + +#endif // WEBP_USE_SSE2 + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_SSE2)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersSSE2) #endif - - diff --git a/3rdparty/libwebp/dsp/yuv.c b/3rdparty/libwebp/dsp/yuv.c index 1a59f7449e..dd7d9dedfa 100644 --- a/3rdparty/libwebp/dsp/yuv.c +++ b/3rdparty/libwebp/dsp/yuv.c @@ -7,22 +7,15 @@ // be found in the AUTHORS file in the root of the source tree. // ----------------------------------------------------------------------------- // -// YUV->RGB conversion function +// YUV->RGB conversion functions // // Author: Skal (pascal.massimino@gmail.com) #include "./yuv.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include -#ifdef WEBP_YUV_USE_TABLE - -int16_t VP8kVToR[256], VP8kUToB[256]; -int32_t VP8kVToG[256], VP8kUToG[256]; -uint8_t VP8kClip[YUV_RANGE_MAX - YUV_RANGE_MIN]; -uint8_t VP8kClip4Bits[YUV_RANGE_MAX - YUV_RANGE_MIN]; +#if defined(WEBP_YUV_USE_TABLE) static int done = 0; @@ -30,7 +23,12 @@ static WEBP_INLINE uint8_t clip(int v, int max_value) { return v < 0 ? 0 : v > max_value ? max_value : v; } -void VP8YUVInit(void) { +int16_t VP8kVToR[256], VP8kUToB[256]; +int32_t VP8kVToG[256], VP8kUToG[256]; +uint8_t VP8kClip[YUV_RANGE_MAX - YUV_RANGE_MIN]; +uint8_t VP8kClip4Bits[YUV_RANGE_MAX - YUV_RANGE_MIN]; + +WEBP_TSAN_IGNORE_FUNCTION void VP8YUVInit(void) { int i; if (done) { return; @@ -66,10 +64,274 @@ void VP8YUVInit(void) { #else -void VP8YUVInit(void) {} +WEBP_TSAN_IGNORE_FUNCTION void VP8YUVInit(void) {} #endif // WEBP_YUV_USE_TABLE -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif +//----------------------------------------------------------------------------- +// Plain-C version + +#define ROW_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* y, \ + const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + const uint8_t* const end = dst + (len & ~1) * XSTEP; \ + while (dst != end) { \ + FUNC(y[0], u[0], v[0], dst); \ + FUNC(y[1], u[0], v[0], dst + XSTEP); \ + y += 2; \ + ++u; \ + ++v; \ + dst += 2 * XSTEP; \ + } \ + if (len & 1) { \ + FUNC(y[0], u[0], v[0], dst); \ + } \ +} \ + +// All variants implemented. +ROW_FUNC(YuvToRgbRow, VP8YuvToRgb, 3) +ROW_FUNC(YuvToBgrRow, VP8YuvToBgr, 3) +ROW_FUNC(YuvToRgbaRow, VP8YuvToRgba, 4) +ROW_FUNC(YuvToBgraRow, VP8YuvToBgra, 4) +ROW_FUNC(YuvToArgbRow, VP8YuvToArgb, 4) +ROW_FUNC(YuvToRgba4444Row, VP8YuvToRgba4444, 2) +ROW_FUNC(YuvToRgb565Row, VP8YuvToRgb565, 2) + +#undef ROW_FUNC + +// Main call for processing a plane with a WebPSamplerRowFunc function: +void WebPSamplerProcessPlane(const uint8_t* y, int y_stride, + const uint8_t* u, const uint8_t* v, int uv_stride, + uint8_t* dst, int dst_stride, + int width, int height, WebPSamplerRowFunc func) { + int j; + for (j = 0; j < height; ++j) { + func(y, u, v, dst, width); + y += y_stride; + if (j & 1) { + u += uv_stride; + v += uv_stride; + } + dst += dst_stride; + } +} + +//----------------------------------------------------------------------------- +// Main call + +WebPSamplerRowFunc WebPSamplers[MODE_LAST]; + +extern void WebPInitSamplersSSE2(void); +extern void WebPInitSamplersMIPS32(void); +extern void WebPInitSamplersMIPSdspR2(void); + +static volatile VP8CPUInfo yuv_last_cpuinfo_used = + (VP8CPUInfo)&yuv_last_cpuinfo_used; + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplers(void) { + if (yuv_last_cpuinfo_used == VP8GetCPUInfo) return; + + WebPSamplers[MODE_RGB] = YuvToRgbRow; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow; + WebPSamplers[MODE_BGR] = YuvToBgrRow; + WebPSamplers[MODE_BGRA] = YuvToBgraRow; + WebPSamplers[MODE_ARGB] = YuvToArgbRow; + WebPSamplers[MODE_RGBA_4444] = YuvToRgba4444Row; + WebPSamplers[MODE_RGB_565] = YuvToRgb565Row; + WebPSamplers[MODE_rgbA] = YuvToRgbaRow; + WebPSamplers[MODE_bgrA] = YuvToBgraRow; + WebPSamplers[MODE_Argb] = YuvToArgbRow; + WebPSamplers[MODE_rgbA_4444] = YuvToRgba4444Row; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitSamplersSSE2(); + } +#endif // WEBP_USE_SSE2 +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + WebPInitSamplersMIPS32(); + } +#endif // WEBP_USE_MIPS32 +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitSamplersMIPSdspR2(); + } +#endif // WEBP_USE_MIPS_DSP_R2 + } + yuv_last_cpuinfo_used = VP8GetCPUInfo; +} + +//----------------------------------------------------------------------------- +// ARGB -> YUV converters + +static void ConvertARGBToY(const uint32_t* argb, uint8_t* y, int width) { + int i; + for (i = 0; i < width; ++i) { + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +void WebPConvertARGBToUV_C(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store) { + // No rounding. Last pixel is dealt with separately. + const int uv_width = src_width >> 1; + int i; + for (i = 0; i < uv_width; ++i) { + const uint32_t v0 = argb[2 * i + 0]; + const uint32_t v1 = argb[2 * i + 1]; + // VP8RGBToU/V expects four accumulated pixels. Hence we need to + // scale r/g/b value by a factor 2. We just shift v0/v1 one bit less. + const int r = ((v0 >> 15) & 0x1fe) + ((v1 >> 15) & 0x1fe); + const int g = ((v0 >> 7) & 0x1fe) + ((v1 >> 7) & 0x1fe); + const int b = ((v0 << 1) & 0x1fe) + ((v1 << 1) & 0x1fe); + const int tmp_u = VP8RGBToU(r, g, b, YUV_HALF << 2); + const int tmp_v = VP8RGBToV(r, g, b, YUV_HALF << 2); + if (do_store) { + u[i] = tmp_u; + v[i] = tmp_v; + } else { + // Approximated average-of-four. But it's an acceptable diff. + u[i] = (u[i] + tmp_u + 1) >> 1; + v[i] = (v[i] + tmp_v + 1) >> 1; + } + } + if (src_width & 1) { // last pixel + const uint32_t v0 = argb[2 * i + 0]; + const int r = (v0 >> 14) & 0x3fc; + const int g = (v0 >> 6) & 0x3fc; + const int b = (v0 << 2) & 0x3fc; + const int tmp_u = VP8RGBToU(r, g, b, YUV_HALF << 2); + const int tmp_v = VP8RGBToV(r, g, b, YUV_HALF << 2); + if (do_store) { + u[i] = tmp_u; + v[i] = tmp_v; + } else { + u[i] = (u[i] + tmp_u + 1) >> 1; + v[i] = (v[i] + tmp_v + 1) >> 1; + } + } +} + +//----------------------------------------------------------------------------- + +static void ConvertRGB24ToY(const uint8_t* rgb, uint8_t* y, int width) { + int i; + for (i = 0; i < width; ++i, rgb += 3) { + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY(const uint8_t* bgr, uint8_t* y, int width) { + int i; + for (i = 0; i < width; ++i, bgr += 3) { + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +void WebPConvertRGBA32ToUV_C(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width) { + int i; + for (i = 0; i < width; i += 1, rgb += 4) { + const int r = rgb[0], g = rgb[1], b = rgb[2]; + u[i] = VP8RGBToU(r, g, b, YUV_HALF << 2); + v[i] = VP8RGBToV(r, g, b, YUV_HALF << 2); + } +} + +//----------------------------------------------------------------------------- + +#define MAX_Y ((1 << 10) - 1) // 10b precision over 16b-arithmetic +static uint16_t clip_y(int v) { + return (v < 0) ? 0 : (v > MAX_Y) ? MAX_Y : (uint16_t)v; +} + +static uint64_t SharpYUVUpdateY_C(const uint16_t* ref, const uint16_t* src, + uint16_t* dst, int len) { + uint64_t diff = 0; + int i; + for (i = 0; i < len; ++i) { + const int diff_y = ref[i] - src[i]; + const int new_y = (int)dst[i] + diff_y; + dst[i] = clip_y(new_y); + diff += (uint64_t)abs(diff_y); + } + return diff; +} + +static void SharpYUVUpdateRGB_C(const int16_t* ref, const int16_t* src, + int16_t* dst, int len) { + int i; + for (i = 0; i < len; ++i) { + const int diff_uv = ref[i] - src[i]; + dst[i] += diff_uv; + } +} + +static void SharpYUVFilterRow_C(const int16_t* A, const int16_t* B, int len, + const uint16_t* best_y, uint16_t* out) { + int i; + for (i = 0; i < len; ++i, ++A, ++B) { + const int v0 = (A[0] * 9 + A[1] * 3 + B[0] * 3 + B[1] + 8) >> 4; + const int v1 = (A[1] * 9 + A[0] * 3 + B[1] * 3 + B[0] + 8) >> 4; + out[2 * i + 0] = clip_y(best_y[2 * i + 0] + v0); + out[2 * i + 1] = clip_y(best_y[2 * i + 1] + v1); + } +} + +#undef MAX_Y + +//----------------------------------------------------------------------------- + +void (*WebPConvertRGB24ToY)(const uint8_t* rgb, uint8_t* y, int width); +void (*WebPConvertBGR24ToY)(const uint8_t* bgr, uint8_t* y, int width); +void (*WebPConvertRGBA32ToUV)(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width); + +void (*WebPConvertARGBToY)(const uint32_t* argb, uint8_t* y, int width); +void (*WebPConvertARGBToUV)(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store); + +uint64_t (*WebPSharpYUVUpdateY)(const uint16_t* ref, const uint16_t* src, + uint16_t* dst, int len); +void (*WebPSharpYUVUpdateRGB)(const int16_t* ref, const int16_t* src, + int16_t* dst, int len); +void (*WebPSharpYUVFilterRow)(const int16_t* A, const int16_t* B, int len, + const uint16_t* best_y, uint16_t* out); + +static volatile VP8CPUInfo rgba_to_yuv_last_cpuinfo_used = + (VP8CPUInfo)&rgba_to_yuv_last_cpuinfo_used; + +extern void WebPInitConvertARGBToYUVSSE2(void); +extern void WebPInitSharpYUVSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUV(void) { + if (rgba_to_yuv_last_cpuinfo_used == VP8GetCPUInfo) return; + + WebPConvertARGBToY = ConvertARGBToY; + WebPConvertARGBToUV = WebPConvertARGBToUV_C; + + WebPConvertRGB24ToY = ConvertRGB24ToY; + WebPConvertBGR24ToY = ConvertBGR24ToY; + + WebPConvertRGBA32ToUV = WebPConvertRGBA32ToUV_C; + + WebPSharpYUVUpdateY = SharpYUVUpdateY_C; + WebPSharpYUVUpdateRGB = SharpYUVUpdateRGB_C; + WebPSharpYUVFilterRow = SharpYUVFilterRow_C; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitConvertARGBToYUVSSE2(); + WebPInitSharpYUVSSE2(); + } +#endif // WEBP_USE_SSE2 + } + rgba_to_yuv_last_cpuinfo_used = VP8GetCPUInfo; +} diff --git a/3rdparty/libwebp/dsp/yuv.h b/3rdparty/libwebp/dsp/yuv.h index 3844d8cab3..1d33b5863b 100644 --- a/3rdparty/libwebp/dsp/yuv.h +++ b/3rdparty/libwebp/dsp/yuv.h @@ -14,30 +14,29 @@ // Y = 0.2569 * R + 0.5044 * G + 0.0979 * B + 16 // U = -0.1483 * R - 0.2911 * G + 0.4394 * B + 128 // V = 0.4394 * R - 0.3679 * G - 0.0715 * B + 128 -// We use 16bit fixed point operations for RGB->YUV conversion. +// We use 16bit fixed point operations for RGB->YUV conversion (YUV_FIX). // // For the Y'CbCr to RGB conversion, the BT.601 specification reads: // R = 1.164 * (Y-16) + 1.596 * (V-128) // G = 1.164 * (Y-16) - 0.813 * (V-128) - 0.391 * (U-128) // B = 1.164 * (Y-16) + 2.018 * (U-128) // where Y is in the [16,235] range, and U/V in the [16,240] range. -// In the table-lookup version (WEBP_YUV_USE_TABLE), the common factor -// "1.164 * (Y-16)" can be handled as an offset in the VP8kClip[] table. -// So in this case the formulae should be read as: -// R = 1.164 * [Y + 1.371 * (V-128) ] - 18.624 -// G = 1.164 * [Y - 0.698 * (V-128) - 0.336 * (U-128)] - 18.624 -// B = 1.164 * [Y + 1.733 * (U-128)] - 18.624 -// once factorized. Here too, 16bit fixed precision is used. // +// The fixed-point implementation used here is: +// R = (19077 . y + 26149 . v - 14234) >> 6 +// G = (19077 . y - 6419 . u - 13320 . v + 8708) >> 6 +// B = (19077 . y + 33050 . u - 17685) >> 6 +// where the '.' operator is the mulhi_epu16 variant: +// a . b = ((a << 8) * b) >> 16 +// that preserves 8 bits of fractional precision before final descaling. + // Author: Skal (pascal.massimino@gmail.com) #ifndef WEBP_DSP_YUV_H_ #define WEBP_DSP_YUV_H_ -#include "../dec/decode_vp8.h" - -// Define the following to use the LUT-based code: -#define WEBP_YUV_USE_TABLE +#include "./dsp.h" +#include "../dec/vp8_dec.h" #if defined(WEBP_EXPERIMENTAL_FEATURES) // Do NOT activate this feature for real compression. This is only experimental! @@ -52,134 +51,66 @@ //------------------------------------------------------------------------------ // YUV -> RGB conversion -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -enum { YUV_FIX = 16, // fixed-point precision - YUV_HALF = 1 << (YUV_FIX - 1), - YUV_MASK = (256 << YUV_FIX) - 1, - YUV_RANGE_MIN = -227, // min value of r/g/b output - YUV_RANGE_MAX = 256 + 226 // max value of r/g/b output +enum { + YUV_FIX = 16, // fixed-point precision for RGB->YUV + YUV_HALF = 1 << (YUV_FIX - 1), + YUV_MASK = (256 << YUV_FIX) - 1, + YUV_RANGE_MIN = -227, // min value of r/g/b output + YUV_RANGE_MAX = 256 + 226, // max value of r/g/b output + + YUV_FIX2 = 6, // fixed-point precision for YUV->RGB + YUV_HALF2 = 1 << YUV_FIX2 >> 1, + YUV_MASK2 = (256 << YUV_FIX2) - 1 }; -#ifdef WEBP_YUV_USE_TABLE +//------------------------------------------------------------------------------ +// slower on x86 by ~7-8%, but bit-exact with the SSE2/NEON version -extern int16_t VP8kVToR[256], VP8kUToB[256]; -extern int32_t VP8kVToG[256], VP8kUToG[256]; -extern uint8_t VP8kClip[YUV_RANGE_MAX - YUV_RANGE_MIN]; -extern uint8_t VP8kClip4Bits[YUV_RANGE_MAX - YUV_RANGE_MIN]; - -static WEBP_INLINE void VP8YuvToRgb(uint8_t y, uint8_t u, uint8_t v, - uint8_t* const rgb) { - const int r_off = VP8kVToR[v]; - const int g_off = (VP8kVToG[v] + VP8kUToG[u]) >> YUV_FIX; - const int b_off = VP8kUToB[u]; - rgb[0] = VP8kClip[y + r_off - YUV_RANGE_MIN]; - rgb[1] = VP8kClip[y + g_off - YUV_RANGE_MIN]; - rgb[2] = VP8kClip[y + b_off - YUV_RANGE_MIN]; +static WEBP_INLINE int MultHi(int v, int coeff) { // _mm_mulhi_epu16 emulation + return (v * coeff) >> 8; } -static WEBP_INLINE void VP8YuvToBgr(uint8_t y, uint8_t u, uint8_t v, - uint8_t* const bgr) { - const int r_off = VP8kVToR[v]; - const int g_off = (VP8kVToG[v] + VP8kUToG[u]) >> YUV_FIX; - const int b_off = VP8kUToB[u]; - bgr[0] = VP8kClip[y + b_off - YUV_RANGE_MIN]; - bgr[1] = VP8kClip[y + g_off - YUV_RANGE_MIN]; - bgr[2] = VP8kClip[y + r_off - YUV_RANGE_MIN]; -} - -static WEBP_INLINE void VP8YuvToRgb565(uint8_t y, uint8_t u, uint8_t v, - uint8_t* const rgb) { - const int r_off = VP8kVToR[v]; - const int g_off = (VP8kVToG[v] + VP8kUToG[u]) >> YUV_FIX; - const int b_off = VP8kUToB[u]; - const uint8_t rg = ((VP8kClip[y + r_off - YUV_RANGE_MIN] & 0xf8) | - (VP8kClip[y + g_off - YUV_RANGE_MIN] >> 5)); - const uint8_t gb = (((VP8kClip[y + g_off - YUV_RANGE_MIN] << 3) & 0xe0) | - (VP8kClip[y + b_off - YUV_RANGE_MIN] >> 3)); -#ifdef WEBP_SWAP_16BIT_CSP - rgb[0] = gb; - rgb[1] = rg; -#else - rgb[0] = rg; - rgb[1] = gb; -#endif -} - -static WEBP_INLINE void VP8YuvToRgba4444(uint8_t y, uint8_t u, uint8_t v, - uint8_t* const argb) { - const int r_off = VP8kVToR[v]; - const int g_off = (VP8kVToG[v] + VP8kUToG[u]) >> YUV_FIX; - const int b_off = VP8kUToB[u]; - const uint8_t rg = ((VP8kClip4Bits[y + r_off - YUV_RANGE_MIN] << 4) | - VP8kClip4Bits[y + g_off - YUV_RANGE_MIN]); - const uint8_t ba = (VP8kClip4Bits[y + b_off - YUV_RANGE_MIN] << 4) | 0x0f; -#ifdef WEBP_SWAP_16BIT_CSP - argb[0] = ba; - argb[1] = rg; -#else - argb[0] = rg; - argb[1] = ba; -#endif -} - -#else // Table-free version (slower on x86) - -// These constants are 16b fixed-point version of ITU-R BT.601 constants -#define kYScale 76309 // 1.164 = 255 / 219 -#define kVToR 104597 // 1.596 = 255 / 112 * 0.701 -#define kUToG 25674 // 0.391 = 255 / 112 * 0.886 * 0.114 / 0.587 -#define kVToG 53278 // 0.813 = 255 / 112 * 0.701 * 0.299 / 0.587 -#define kUToB 132201 // 2.018 = 255 / 112 * 0.886 -#define kRCst (-kYScale * 16 - kVToR * 128 + YUV_HALF) -#define kGCst (-kYScale * 16 + kUToG * 128 + kVToG * 128 + YUV_HALF) -#define kBCst (-kYScale * 16 - kUToB * 128 + YUV_HALF) - -static WEBP_INLINE uint8_t VP8Clip8(int v) { - return ((v & ~YUV_MASK) == 0) ? (uint8_t)(v >> YUV_FIX) - : (v < 0) ? 0u : 255u; -} - -static WEBP_INLINE uint8_t VP8ClipN(int v, int N) { // clip to N bits - return ((v & ~YUV_MASK) == 0) ? (uint8_t)(v >> (YUV_FIX + (8 - N))) - : (v < 0) ? 0u : (255u >> (8 - N)); +static WEBP_INLINE int VP8Clip8(int v) { + return ((v & ~YUV_MASK2) == 0) ? (v >> YUV_FIX2) : (v < 0) ? 0 : 255; } static WEBP_INLINE int VP8YUVToR(int y, int v) { - return kYScale * y + kVToR * v + kRCst; + return VP8Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); } static WEBP_INLINE int VP8YUVToG(int y, int u, int v) { - return kYScale * y - kUToG * u - kVToG * v + kGCst; + return VP8Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); } static WEBP_INLINE int VP8YUVToB(int y, int u) { - return kYScale * y + kUToB * u + kBCst; + return VP8Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); } -static WEBP_INLINE void VP8YuvToRgb(uint8_t y, uint8_t u, uint8_t v, +static WEBP_INLINE void VP8YuvToRgb(int y, int u, int v, uint8_t* const rgb) { - rgb[0] = VP8Clip8(VP8YUVToR(y, v)); - rgb[1] = VP8Clip8(VP8YUVToG(y, u, v)); - rgb[2] = VP8Clip8(VP8YUVToB(y, u)); + rgb[0] = VP8YUVToR(y, v); + rgb[1] = VP8YUVToG(y, u, v); + rgb[2] = VP8YUVToB(y, u); } -static WEBP_INLINE void VP8YuvToBgr(uint8_t y, uint8_t u, uint8_t v, +static WEBP_INLINE void VP8YuvToBgr(int y, int u, int v, uint8_t* const bgr) { - bgr[0] = VP8Clip8(VP8YUVToB(y, u)); - bgr[1] = VP8Clip8(VP8YUVToG(y, u, v)); - bgr[2] = VP8Clip8(VP8YUVToR(y, v)); + bgr[0] = VP8YUVToB(y, u); + bgr[1] = VP8YUVToG(y, u, v); + bgr[2] = VP8YUVToR(y, v); } -static WEBP_INLINE void VP8YuvToRgb565(uint8_t y, uint8_t u, uint8_t v, +static WEBP_INLINE void VP8YuvToRgb565(int y, int u, int v, uint8_t* const rgb) { - const int r = VP8Clip8(VP8YUVToR(y, u)); - const int g = VP8ClipN(VP8YUVToG(y, u, v), 6); - const int b = VP8ClipN(VP8YUVToB(y, v), 5); - const uint8_t rg = (r & 0xf8) | (g >> 3); - const uint8_t gb = (g << 5) | b; + const int r = VP8YUVToR(y, v); // 5 usable bits + const int g = VP8YUVToG(y, u, v); // 6 usable bits + const int b = VP8YUVToB(y, u); // 5 usable bits + const int rg = (r & 0xf8) | (g >> 5); + const int gb = ((g << 3) & 0xe0) | (b >> 3); #ifdef WEBP_SWAP_16BIT_CSP rgb[0] = gb; rgb[1] = rg; @@ -189,13 +120,13 @@ static WEBP_INLINE void VP8YuvToRgb565(uint8_t y, uint8_t u, uint8_t v, #endif } -static WEBP_INLINE void VP8YuvToRgba4444(uint8_t y, uint8_t u, uint8_t v, +static WEBP_INLINE void VP8YuvToRgba4444(int y, int u, int v, uint8_t* const argb) { - const int r = VP8Clip8(VP8YUVToR(y, u)); - const int g = VP8ClipN(VP8YUVToG(y, u, v), 4); - const int b = VP8Clip8(VP8YUVToB(y, v)); - const uint8_t rg = (r & 0xf0) | g; - const uint8_t ba = b | 0x0f; // overwrite the lower 4 bits + const int r = VP8YUVToR(y, v); // 4 usable bits + const int g = VP8YUVToG(y, u, v); // 4 usable bits + const int b = VP8YUVToB(y, u); // 4 usable bits + const int rg = (r & 0xf0) | (g >> 4); + const int ba = (b & 0xf0) | 0x0f; // overwrite the lower 4 bits #ifdef WEBP_SWAP_16BIT_CSP argb[0] = ba; argb[1] = rg; @@ -205,7 +136,8 @@ static WEBP_INLINE void VP8YuvToRgba4444(uint8_t y, uint8_t u, uint8_t v, #endif } -#endif // WEBP_YUV_USE_TABLE +//----------------------------------------------------------------------------- +// Alpha handling variants static WEBP_INLINE void VP8YuvToArgb(uint8_t y, uint8_t u, uint8_t v, uint8_t* const argb) { @@ -228,56 +160,78 @@ static WEBP_INLINE void VP8YuvToRgba(uint8_t y, uint8_t u, uint8_t v, // Must be called before everything, to initialize the tables. void VP8YUVInit(void); +//----------------------------------------------------------------------------- +// SSE2 extra functions (mostly for upsampling_sse2.c) + +#if defined(WEBP_USE_SSE2) + +// Process 32 pixels and store the result (16b, 24b or 32b per pixel) in *dst. +void VP8YuvToRgba32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToRgb32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToBgra32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToBgr32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToArgb32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToRgba444432(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToRgb56532(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); + +#endif // WEBP_USE_SSE2 + //------------------------------------------------------------------------------ // RGB -> YUV conversion -static WEBP_INLINE int VP8ClipUV(int v) { - v = (v + (257 << (YUV_FIX + 2 - 1))) >> (YUV_FIX + 2); - return ((v & ~0xff) == 0) ? v : (v < 0) ? 0 : 255; +// Stub functions that can be called with various rounding values: +static WEBP_INLINE int VP8ClipUV(int uv, int rounding) { + uv = (uv + rounding + (128 << (YUV_FIX + 2))) >> (YUV_FIX + 2); + return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; } #ifndef USE_YUVj -static WEBP_INLINE int VP8RGBToY(int r, int g, int b) { - const int kRound = (1 << (YUV_FIX - 1)) + (16 << YUV_FIX); +static WEBP_INLINE int VP8RGBToY(int r, int g, int b, int rounding) { const int luma = 16839 * r + 33059 * g + 6420 * b; - return (luma + kRound) >> YUV_FIX; // no need to clip + return (luma + rounding + (16 << YUV_FIX)) >> YUV_FIX; // no need to clip } -static WEBP_INLINE int VP8RGBToU(int r, int g, int b) { +static WEBP_INLINE int VP8RGBToU(int r, int g, int b, int rounding) { const int u = -9719 * r - 19081 * g + 28800 * b; - return VP8ClipUV(u); + return VP8ClipUV(u, rounding); } -static WEBP_INLINE int VP8RGBToV(int r, int g, int b) { +static WEBP_INLINE int VP8RGBToV(int r, int g, int b, int rounding) { const int v = +28800 * r - 24116 * g - 4684 * b; - return VP8ClipUV(v); + return VP8ClipUV(v, rounding); } #else // This JPEG-YUV colorspace, only for comparison! -// These are also 16-bit precision coefficients from Rec.601, but with full +// These are also 16bit precision coefficients from Rec.601, but with full // [0..255] output range. -static WEBP_INLINE int VP8RGBToY(int r, int g, int b) { - const int kRound = (1 << (YUV_FIX - 1)); +static WEBP_INLINE int VP8RGBToY(int r, int g, int b, int rounding) { const int luma = 19595 * r + 38470 * g + 7471 * b; - return (luma + kRound) >> YUV_FIX; // no need to clip + return (luma + rounding) >> YUV_FIX; // no need to clip } -static WEBP_INLINE int VP8RGBToU(int r, int g, int b) { +static WEBP_INLINE int VP8RGBToU(int r, int g, int b, int rounding) { const int u = -11058 * r - 21710 * g + 32768 * b; - return VP8ClipUV(u); + return VP8ClipUV(u, rounding); } -static WEBP_INLINE int VP8RGBToV(int r, int g, int b) { +static WEBP_INLINE int VP8RGBToV(int r, int g, int b, int rounding) { const int v = 32768 * r - 27439 * g - 5329 * b; - return VP8ClipUV(v); + return VP8ClipUV(v, rounding); } #endif // USE_YUVj -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/dsp/yuv_mips32.c b/3rdparty/libwebp/dsp/yuv_mips32.c new file mode 100644 index 0000000000..e61aac571f --- /dev/null +++ b/3rdparty/libwebp/dsp/yuv_mips32.c @@ -0,0 +1,103 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of YUV to RGB upsampling functions. +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS32) + +#include "./yuv.h" + +//------------------------------------------------------------------------------ +// simple point-sampling + +#define ROW_FUNC(FUNC_NAME, XSTEP, R, G, B, A) \ +static void FUNC_NAME(const uint8_t* y, \ + const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i, r, g, b; \ + int temp0, temp1, temp2, temp3, temp4; \ + for (i = 0; i < (len >> 1); i++) { \ + temp1 = MultHi(v[0], 26149); \ + temp3 = MultHi(v[0], 13320); \ + temp2 = MultHi(u[0], 6419); \ + temp4 = MultHi(u[0], 33050); \ + temp0 = MultHi(y[0], 19077); \ + temp1 -= 14234; \ + temp3 -= 8708; \ + temp2 += temp3; \ + temp4 -= 17685; \ + r = VP8Clip8(temp0 + temp1); \ + g = VP8Clip8(temp0 - temp2); \ + b = VP8Clip8(temp0 + temp4); \ + temp0 = MultHi(y[1], 19077); \ + dst[R] = r; \ + dst[G] = g; \ + dst[B] = b; \ + if (A) dst[A] = 0xff; \ + r = VP8Clip8(temp0 + temp1); \ + g = VP8Clip8(temp0 - temp2); \ + b = VP8Clip8(temp0 + temp4); \ + dst[R + XSTEP] = r; \ + dst[G + XSTEP] = g; \ + dst[B + XSTEP] = b; \ + if (A) dst[A + XSTEP] = 0xff; \ + y += 2; \ + ++u; \ + ++v; \ + dst += 2 * XSTEP; \ + } \ + if (len & 1) { \ + temp1 = MultHi(v[0], 26149); \ + temp3 = MultHi(v[0], 13320); \ + temp2 = MultHi(u[0], 6419); \ + temp4 = MultHi(u[0], 33050); \ + temp0 = MultHi(y[0], 19077); \ + temp1 -= 14234; \ + temp3 -= 8708; \ + temp2 += temp3; \ + temp4 -= 17685; \ + r = VP8Clip8(temp0 + temp1); \ + g = VP8Clip8(temp0 - temp2); \ + b = VP8Clip8(temp0 + temp4); \ + dst[R] = r; \ + dst[G] = g; \ + dst[B] = b; \ + if (A) dst[A] = 0xff; \ + } \ +} + +ROW_FUNC(YuvToRgbRow, 3, 0, 1, 2, 0) +ROW_FUNC(YuvToRgbaRow, 4, 0, 1, 2, 3) +ROW_FUNC(YuvToBgrRow, 3, 2, 1, 0, 0) +ROW_FUNC(YuvToBgraRow, 4, 2, 1, 0, 3) + +#undef ROW_FUNC + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersMIPS32(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow; + WebPSamplers[MODE_BGR] = YuvToBgrRow; + WebPSamplers[MODE_BGRA] = YuvToBgraRow; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(WebPInitSamplersMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/3rdparty/libwebp/dsp/yuv_mips_dsp_r2.c b/3rdparty/libwebp/dsp/yuv_mips_dsp_r2.c new file mode 100644 index 0000000000..1720d4190f --- /dev/null +++ b/3rdparty/libwebp/dsp/yuv_mips_dsp_r2.c @@ -0,0 +1,134 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS DSPr2 version of YUV to RGB upsampling functions. +// +// Author(s): Branimir Vasic (branimir.vasic@imgtec.com) +// Djordje Pesut (djordje.pesut@imgtec.com) + +#include "./dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "./yuv.h" + +//------------------------------------------------------------------------------ +// simple point-sampling + +#define ROW_FUNC_PART_1() \ + "lbu %[temp3], 0(%[v]) \n\t" \ + "lbu %[temp4], 0(%[u]) \n\t" \ + "lbu %[temp0], 0(%[y]) \n\t" \ + "mul %[temp1], %[t_con_1], %[temp3] \n\t" \ + "mul %[temp3], %[t_con_2], %[temp3] \n\t" \ + "mul %[temp2], %[t_con_3], %[temp4] \n\t" \ + "mul %[temp4], %[t_con_4], %[temp4] \n\t" \ + "mul %[temp0], %[t_con_5], %[temp0] \n\t" \ + "subu %[temp1], %[temp1], %[t_con_6] \n\t" \ + "subu %[temp3], %[temp3], %[t_con_7] \n\t" \ + "addu %[temp2], %[temp2], %[temp3] \n\t" \ + "subu %[temp4], %[temp4], %[t_con_8] \n\t" \ + +#define ROW_FUNC_PART_2(R, G, B, K) \ + "addu %[temp5], %[temp0], %[temp1] \n\t" \ + "subu %[temp6], %[temp0], %[temp2] \n\t" \ + "addu %[temp7], %[temp0], %[temp4] \n\t" \ +".if " #K " \n\t" \ + "lbu %[temp0], 1(%[y]) \n\t" \ +".endif \n\t" \ + "shll_s.w %[temp5], %[temp5], 17 \n\t" \ + "shll_s.w %[temp6], %[temp6], 17 \n\t" \ +".if " #K " \n\t" \ + "mul %[temp0], %[t_con_5], %[temp0] \n\t" \ +".endif \n\t" \ + "shll_s.w %[temp7], %[temp7], 17 \n\t" \ + "precrqu_s.qb.ph %[temp5], %[temp5], $zero \n\t" \ + "precrqu_s.qb.ph %[temp6], %[temp6], $zero \n\t" \ + "precrqu_s.qb.ph %[temp7], %[temp7], $zero \n\t" \ + "srl %[temp5], %[temp5], 24 \n\t" \ + "srl %[temp6], %[temp6], 24 \n\t" \ + "srl %[temp7], %[temp7], 24 \n\t" \ + "sb %[temp5], " #R "(%[dst]) \n\t" \ + "sb %[temp6], " #G "(%[dst]) \n\t" \ + "sb %[temp7], " #B "(%[dst]) \n\t" \ + +#define ASM_CLOBBER_LIST() \ + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), \ + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), \ + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7) \ + : [t_con_1]"r"(t_con_1), [t_con_2]"r"(t_con_2), [t_con_3]"r"(t_con_3), \ + [t_con_4]"r"(t_con_4), [t_con_5]"r"(t_con_5), [t_con_6]"r"(t_con_6), \ + [u]"r"(u), [v]"r"(v), [y]"r"(y), [dst]"r"(dst), \ + [t_con_7]"r"(t_con_7), [t_con_8]"r"(t_con_8) \ + : "memory", "hi", "lo" \ + +#define ROW_FUNC(FUNC_NAME, XSTEP, R, G, B, A) \ +static void FUNC_NAME(const uint8_t* y, \ + const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + uint32_t temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; \ + const int t_con_1 = 26149; \ + const int t_con_2 = 13320; \ + const int t_con_3 = 6419; \ + const int t_con_4 = 33050; \ + const int t_con_5 = 19077; \ + const int t_con_6 = 14234; \ + const int t_con_7 = 8708; \ + const int t_con_8 = 17685; \ + for (i = 0; i < (len >> 1); i++) { \ + __asm__ volatile ( \ + ROW_FUNC_PART_1() \ + ROW_FUNC_PART_2(R, G, B, 1) \ + ROW_FUNC_PART_2(R + XSTEP, G + XSTEP, B + XSTEP, 0) \ + ASM_CLOBBER_LIST() \ + ); \ + if (A) dst[A] = dst[A + XSTEP] = 0xff; \ + y += 2; \ + ++u; \ + ++v; \ + dst += 2 * XSTEP; \ + } \ + if (len & 1) { \ + __asm__ volatile ( \ + ROW_FUNC_PART_1() \ + ROW_FUNC_PART_2(R, G, B, 0) \ + ASM_CLOBBER_LIST() \ + ); \ + if (A) dst[A] = 0xff; \ + } \ +} + +ROW_FUNC(YuvToRgbRow, 3, 0, 1, 2, 0) +ROW_FUNC(YuvToRgbaRow, 4, 0, 1, 2, 3) +ROW_FUNC(YuvToBgrRow, 3, 2, 1, 0, 0) +ROW_FUNC(YuvToBgraRow, 4, 2, 1, 0, 3) + +#undef ROW_FUNC +#undef ASM_CLOBBER_LIST +#undef ROW_FUNC_PART_2 +#undef ROW_FUNC_PART_1 + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersMIPSdspR2(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow; + WebPSamplers[MODE_BGR] = YuvToBgrRow; + WebPSamplers[MODE_BGRA] = YuvToBgraRow; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(WebPInitSamplersMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/3rdparty/libwebp/dsp/yuv_sse2.c b/3rdparty/libwebp/dsp/yuv_sse2.c new file mode 100644 index 0000000000..e33c2bbafd --- /dev/null +++ b/3rdparty/libwebp/dsp/yuv_sse2.c @@ -0,0 +1,863 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./yuv.h" + +#if defined(WEBP_USE_SSE2) + +#include "./common_sse2.h" +#include +#include + +//----------------------------------------------------------------------------- +// Convert spans of 32 pixels to various RGB formats for the fancy upsampler. + +// These constants are 14b fixed-point version of ITU-R BT.601 constants. +// R = (19077 * y + 26149 * v - 14234) >> 6 +// G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 +// B = (19077 * y + 33050 * u - 17685) >> 6 +static void ConvertYUV444ToRGB(const __m128i* const Y0, + const __m128i* const U0, + const __m128i* const V0, + __m128i* const R, + __m128i* const G, + __m128i* const B) { + const __m128i k19077 = _mm_set1_epi16(19077); + const __m128i k26149 = _mm_set1_epi16(26149); + const __m128i k14234 = _mm_set1_epi16(14234); + // 33050 doesn't fit in a signed short: only use this with unsigned arithmetic + const __m128i k33050 = _mm_set1_epi16((short)33050); + const __m128i k17685 = _mm_set1_epi16(17685); + const __m128i k6419 = _mm_set1_epi16(6419); + const __m128i k13320 = _mm_set1_epi16(13320); + const __m128i k8708 = _mm_set1_epi16(8708); + + const __m128i Y1 = _mm_mulhi_epu16(*Y0, k19077); + + const __m128i R0 = _mm_mulhi_epu16(*V0, k26149); + const __m128i R1 = _mm_sub_epi16(Y1, k14234); + const __m128i R2 = _mm_add_epi16(R1, R0); + + const __m128i G0 = _mm_mulhi_epu16(*U0, k6419); + const __m128i G1 = _mm_mulhi_epu16(*V0, k13320); + const __m128i G2 = _mm_add_epi16(Y1, k8708); + const __m128i G3 = _mm_add_epi16(G0, G1); + const __m128i G4 = _mm_sub_epi16(G2, G3); + + // be careful with the saturated *unsigned* arithmetic here! + const __m128i B0 = _mm_mulhi_epu16(*U0, k33050); + const __m128i B1 = _mm_adds_epu16(B0, Y1); + const __m128i B2 = _mm_subs_epu16(B1, k17685); + + // use logical shift for B2, which can be larger than 32767 + *R = _mm_srai_epi16(R2, 6); // range: [-14234, 30815] + *G = _mm_srai_epi16(G4, 6); // range: [-10953, 27710] + *B = _mm_srli_epi16(B2, 6); // range: [0, 34238] +} + +// Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. +static WEBP_INLINE __m128i Load_HI_16(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + return _mm_unpacklo_epi8(zero, _mm_loadl_epi64((const __m128i*)src)); +} + +// Load and replicate the U/V samples +static WEBP_INLINE __m128i Load_UV_HI_8(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + const __m128i tmp0 = _mm_cvtsi32_si128(*(const uint32_t*)src); + const __m128i tmp1 = _mm_unpacklo_epi8(zero, tmp0); + return _mm_unpacklo_epi16(tmp1, tmp1); // replicate samples +} + +// Convert 32 samples of YUV444 to R/G/B +static void YUV444ToRGB(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, __m128i* const B) { + const __m128i Y0 = Load_HI_16(y), U0 = Load_HI_16(u), V0 = Load_HI_16(v); + ConvertYUV444ToRGB(&Y0, &U0, &V0, R, G, B); +} + +// Convert 32 samples of YUV420 to R/G/B +static void YUV420ToRGB(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, __m128i* const B) { + const __m128i Y0 = Load_HI_16(y), U0 = Load_UV_HI_8(u), V0 = Load_UV_HI_8(v); + ConvertYUV444ToRGB(&Y0, &U0, &V0, R, G, B); +} + +// Pack R/G/B/A results into 32b output. +static WEBP_INLINE void PackAndStore4(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + const __m128i* const A, + uint8_t* const dst) { + const __m128i rb = _mm_packus_epi16(*R, *B); + const __m128i ga = _mm_packus_epi16(*G, *A); + const __m128i rg = _mm_unpacklo_epi8(rb, ga); + const __m128i ba = _mm_unpackhi_epi8(rb, ga); + const __m128i RGBA_lo = _mm_unpacklo_epi16(rg, ba); + const __m128i RGBA_hi = _mm_unpackhi_epi16(rg, ba); + _mm_storeu_si128((__m128i*)(dst + 0), RGBA_lo); + _mm_storeu_si128((__m128i*)(dst + 16), RGBA_hi); +} + +// Pack R/G/B/A results into 16b output. +static WEBP_INLINE void PackAndStore4444(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + const __m128i* const A, + uint8_t* const dst) { +#if !defined(WEBP_SWAP_16BIT_CSP) + const __m128i rg0 = _mm_packus_epi16(*R, *G); + const __m128i ba0 = _mm_packus_epi16(*B, *A); +#else + const __m128i rg0 = _mm_packus_epi16(*B, *A); + const __m128i ba0 = _mm_packus_epi16(*R, *G); +#endif + const __m128i mask_0xf0 = _mm_set1_epi8(0xf0); + const __m128i rb1 = _mm_unpacklo_epi8(rg0, ba0); // rbrbrbrbrb... + const __m128i ga1 = _mm_unpackhi_epi8(rg0, ba0); // gagagagaga... + const __m128i rb2 = _mm_and_si128(rb1, mask_0xf0); + const __m128i ga2 = _mm_srli_epi16(_mm_and_si128(ga1, mask_0xf0), 4); + const __m128i rgba4444 = _mm_or_si128(rb2, ga2); + _mm_storeu_si128((__m128i*)dst, rgba4444); +} + +// Pack R/G/B results into 16b output. +static WEBP_INLINE void PackAndStore565(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + uint8_t* const dst) { + const __m128i r0 = _mm_packus_epi16(*R, *R); + const __m128i g0 = _mm_packus_epi16(*G, *G); + const __m128i b0 = _mm_packus_epi16(*B, *B); + const __m128i r1 = _mm_and_si128(r0, _mm_set1_epi8(0xf8)); + const __m128i b1 = _mm_and_si128(_mm_srli_epi16(b0, 3), _mm_set1_epi8(0x1f)); + const __m128i g1 = _mm_srli_epi16(_mm_and_si128(g0, _mm_set1_epi8(0xe0)), 5); + const __m128i g2 = _mm_slli_epi16(_mm_and_si128(g0, _mm_set1_epi8(0x1c)), 3); + const __m128i rg = _mm_or_si128(r1, g1); + const __m128i gb = _mm_or_si128(g2, b1); +#if !defined(WEBP_SWAP_16BIT_CSP) + const __m128i rgb565 = _mm_unpacklo_epi8(rg, gb); +#else + const __m128i rgb565 = _mm_unpacklo_epi8(gb, rg); +#endif + _mm_storeu_si128((__m128i*)dst, rgb565); +} + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void PlanarTo24b(__m128i* const in0, __m128i* const in1, + __m128i* const in2, __m128i* const in3, + __m128i* const in4, __m128i* const in5, + uint8_t* const rgb) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + VP8PlanarTo24b(in0, in1, in2, in3, in4, in5); + + _mm_storeu_si128((__m128i*)(rgb + 0), *in0); + _mm_storeu_si128((__m128i*)(rgb + 16), *in1); + _mm_storeu_si128((__m128i*)(rgb + 32), *in2); + _mm_storeu_si128((__m128i*)(rgb + 48), *in3); + _mm_storeu_si128((__m128i*)(rgb + 64), *in4); + _mm_storeu_si128((__m128i*)(rgb + 80), *in5); +} + +void VP8YuvToRgba32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 32) { + __m128i R, G, B; + YUV444ToRGB(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4(&R, &G, &B, &kAlpha, dst); + } +} + +void VP8YuvToBgra32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 32) { + __m128i R, G, B; + YUV444ToRGB(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4(&B, &G, &R, &kAlpha, dst); + } +} + +void VP8YuvToArgb32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 32) { + __m128i R, G, B; + YUV444ToRGB(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4(&kAlpha, &R, &G, &B, dst); + } +} + +void VP8YuvToRgba444432(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 16) { + __m128i R, G, B; + YUV444ToRGB(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4444(&R, &G, &B, &kAlpha, dst); + } +} + +void VP8YuvToRgb56532(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + int n; + for (n = 0; n < 32; n += 8, dst += 16) { + __m128i R, G, B; + YUV444ToRGB(y + n, u + n, v + n, &R, &G, &B); + PackAndStore565(&R, &G, &B, dst); + } +} + +void VP8YuvToRgb32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV444ToRGB(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); +} + +void VP8YuvToBgr32(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV444ToRGB(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5= _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); +} + +//----------------------------------------------------------------------------- +// Arbitrary-length row conversion functions + +static void YuvToRgbaRow(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n + 8 <= len; n += 8, dst += 32) { + __m128i R, G, B; + YUV420ToRGB(y, u, v, &R, &G, &B); + PackAndStore4(&R, &G, &B, &kAlpha, dst); + y += 8; + u += 4; + v += 4; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgba(y[0], u[0], v[0], dst); + dst += 4; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgraRow(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n + 8 <= len; n += 8, dst += 32) { + __m128i R, G, B; + YUV420ToRGB(y, u, v, &R, &G, &B); + PackAndStore4(&B, &G, &R, &kAlpha, dst); + y += 8; + u += 4; + v += 4; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgra(y[0], u[0], v[0], dst); + dst += 4; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToArgbRow(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n + 8 <= len; n += 8, dst += 32) { + __m128i R, G, B; + YUV420ToRGB(y, u, v, &R, &G, &B); + PackAndStore4(&kAlpha, &R, &G, &B, dst); + y += 8; + u += 4; + v += 4; + } + for (; n < len; ++n) { // Finish off + VP8YuvToArgb(y[0], u[0], v[0], dst); + dst += 4; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToRgbRow(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV420ToRGB(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgb(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgrRow(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV420ToRGB(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5 = _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgr(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersSSE2(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow; + WebPSamplers[MODE_BGR] = YuvToBgrRow; + WebPSamplers[MODE_BGRA] = YuvToBgraRow; + WebPSamplers[MODE_ARGB] = YuvToArgbRow; +} + +//------------------------------------------------------------------------------ +// RGB24/32 -> YUV converters + +// Load eight 16b-words from *src. +#define LOAD_16(src) _mm_loadu_si128((const __m128i*)(src)) +// Store either 16b-words into *dst +#define STORE_16(V, dst) _mm_storeu_si128((__m128i*)(dst), (V)) + +// Function that inserts a value of the second half of the in buffer in between +// every two char of the first half. +static WEBP_INLINE void RGB24PackedToPlanarHelper( + const __m128i* const in /*in[6]*/, __m128i* const out /*out[6]*/) { + out[0] = _mm_unpacklo_epi8(in[0], in[3]); + out[1] = _mm_unpackhi_epi8(in[0], in[3]); + out[2] = _mm_unpacklo_epi8(in[1], in[4]); + out[3] = _mm_unpackhi_epi8(in[1], in[4]); + out[4] = _mm_unpacklo_epi8(in[2], in[5]); + out[5] = _mm_unpackhi_epi8(in[2], in[5]); +} + +// Unpack the 8b input rgbrgbrgbrgb ... as contiguous registers: +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// Similar to PlanarTo24bHelper(), but in reverse order. +static WEBP_INLINE void RGB24PackedToPlanar(const uint8_t* const rgb, + __m128i* const out /*out[6]*/) { + __m128i tmp[6]; + tmp[0] = _mm_loadu_si128((const __m128i*)(rgb + 0)); + tmp[1] = _mm_loadu_si128((const __m128i*)(rgb + 16)); + tmp[2] = _mm_loadu_si128((const __m128i*)(rgb + 32)); + tmp[3] = _mm_loadu_si128((const __m128i*)(rgb + 48)); + tmp[4] = _mm_loadu_si128((const __m128i*)(rgb + 64)); + tmp[5] = _mm_loadu_si128((const __m128i*)(rgb + 80)); + + RGB24PackedToPlanarHelper(tmp, out); + RGB24PackedToPlanarHelper(out, tmp); + RGB24PackedToPlanarHelper(tmp, out); + RGB24PackedToPlanarHelper(out, tmp); + RGB24PackedToPlanarHelper(tmp, out); +} + +// Convert 8 packed ARGB to r[], g[], b[] +static WEBP_INLINE void RGB32PackedToPlanar(const uint32_t* const argb, + __m128i* const rgb /*in[6]*/) { + const __m128i zero = _mm_setzero_si128(); + __m128i a0 = LOAD_16(argb + 0); + __m128i a1 = LOAD_16(argb + 4); + __m128i a2 = LOAD_16(argb + 8); + __m128i a3 = LOAD_16(argb + 12); + VP8L32bToPlanar(&a0, &a1, &a2, &a3); + rgb[0] = _mm_unpacklo_epi8(a1, zero); + rgb[1] = _mm_unpackhi_epi8(a1, zero); + rgb[2] = _mm_unpacklo_epi8(a2, zero); + rgb[3] = _mm_unpackhi_epi8(a2, zero); + rgb[4] = _mm_unpacklo_epi8(a3, zero); + rgb[5] = _mm_unpackhi_epi8(a3, zero); +} + +// This macro computes (RG * MULT_RG + GB * MULT_GB + ROUNDER) >> DESCALE_FIX +// It's a macro and not a function because we need to use immediate values with +// srai_epi32, e.g. +#define TRANSFORM(RG_LO, RG_HI, GB_LO, GB_HI, MULT_RG, MULT_GB, \ + ROUNDER, DESCALE_FIX, OUT) do { \ + const __m128i V0_lo = _mm_madd_epi16(RG_LO, MULT_RG); \ + const __m128i V0_hi = _mm_madd_epi16(RG_HI, MULT_RG); \ + const __m128i V1_lo = _mm_madd_epi16(GB_LO, MULT_GB); \ + const __m128i V1_hi = _mm_madd_epi16(GB_HI, MULT_GB); \ + const __m128i V2_lo = _mm_add_epi32(V0_lo, V1_lo); \ + const __m128i V2_hi = _mm_add_epi32(V0_hi, V1_hi); \ + const __m128i V3_lo = _mm_add_epi32(V2_lo, ROUNDER); \ + const __m128i V3_hi = _mm_add_epi32(V2_hi, ROUNDER); \ + const __m128i V5_lo = _mm_srai_epi32(V3_lo, DESCALE_FIX); \ + const __m128i V5_hi = _mm_srai_epi32(V3_hi, DESCALE_FIX); \ + (OUT) = _mm_packs_epi32(V5_lo, V5_hi); \ +} while (0) + +#define MK_CST_16(A, B) _mm_set_epi16((B), (A), (B), (A), (B), (A), (B), (A)) +static WEBP_INLINE void ConvertRGBToY(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const Y) { + const __m128i kRG_y = MK_CST_16(16839, 33059 - 16384); + const __m128i kGB_y = MK_CST_16(16384, 6420); + const __m128i kHALF_Y = _mm_set1_epi32((16 << YUV_FIX) + YUV_HALF); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_y, kGB_y, kHALF_Y, YUV_FIX, *Y); +} + +static WEBP_INLINE void ConvertRGBToUV(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const U, __m128i* const V) { + const __m128i kRG_u = MK_CST_16(-9719, -19081); + const __m128i kGB_u = MK_CST_16(0, 28800); + const __m128i kRG_v = MK_CST_16(28800, 0); + const __m128i kGB_v = MK_CST_16(-24116, -4684); + const __m128i kHALF_UV = _mm_set1_epi32(((128 << YUV_FIX) + YUV_HALF) << 2); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_u, kGB_u, + kHALF_UV, YUV_FIX + 2, *U); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_v, kGB_v, + kHALF_UV, YUV_FIX + 2, *V); +} + +#undef MK_CST_16 +#undef TRANSFORM + +static void ConvertRGB24ToY(const uint8_t* rgb, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; rgb += 3 * 16 * 2) { + __m128i rgb_plane[6]; + int j; + + RGB24PackedToPlanar(rgb, rgb_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + r = _mm_unpacklo_epi8(rgb_plane[0 + j], zero); + g = _mm_unpacklo_epi8(rgb_plane[2 + j], zero); + b = _mm_unpacklo_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + r = _mm_unpackhi_epi8(rgb_plane[0 + j], zero); + g = _mm_unpackhi_epi8(rgb_plane[2 + j], zero); + b = _mm_unpackhi_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY(const uint8_t* bgr, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; bgr += 3 * 16 * 2) { + __m128i bgr_plane[6]; + int j; + + RGB24PackedToPlanar(bgr, bgr_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + b = _mm_unpacklo_epi8(bgr_plane[0 + j], zero); + g = _mm_unpacklo_epi8(bgr_plane[2 + j], zero); + r = _mm_unpacklo_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + b = _mm_unpackhi_epi8(bgr_plane[0 + j], zero); + g = _mm_unpackhi_epi8(bgr_plane[2 + j], zero); + r = _mm_unpackhi_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY(const uint32_t* argb, uint8_t* y, int width) { + const int max_width = width & ~15; + int i; + for (i = 0; i < max_width; i += 16) { + __m128i Y0, Y1, rgb[6]; + RGB32PackedToPlanar(&argb[i], rgb); + ConvertRGBToY(&rgb[0], &rgb[2], &rgb[4], &Y0); + ConvertRGBToY(&rgb[1], &rgb[3], &rgb[5], &Y1); + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +// Horizontal add (doubled) of two 16b values, result is 16b. +// in: A | B | C | D | ... -> out: 2*(A+B) | 2*(C+D) | ... +static void HorizontalAddPack(const __m128i* const A, const __m128i* const B, + __m128i* const out) { + const __m128i k2 = _mm_set1_epi16(2); + const __m128i C = _mm_madd_epi16(*A, k2); + const __m128i D = _mm_madd_epi16(*B, k2); + *out = _mm_packs_epi32(C, D); +} + +static void ConvertARGBToUV(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store) { + const int max_width = src_width & ~31; + int i; + for (i = 0; i < max_width; i += 32, u += 16, v += 16) { + __m128i rgb[6], U0, V0, U1, V1; + RGB32PackedToPlanar(&argb[i], rgb); + HorizontalAddPack(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV(&rgb[0], &rgb[2], &rgb[4], &U0, &V0); + + RGB32PackedToPlanar(&argb[i + 16], rgb); + HorizontalAddPack(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV(&rgb[0], &rgb[2], &rgb[4], &U1, &V1); + + U0 = _mm_packus_epi16(U0, U1); + V0 = _mm_packus_epi16(V0, V1); + if (!do_store) { + const __m128i prev_u = LOAD_16(u); + const __m128i prev_v = LOAD_16(v); + U0 = _mm_avg_epu8(U0, prev_u); + V0 = _mm_avg_epu8(V0, prev_v); + } + STORE_16(U0, u); + STORE_16(V0, v); + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + +// Convert 16 packed ARGB 16b-values to r[], g[], b[] +static WEBP_INLINE void RGBA32PackedToPlanar_16b(const uint16_t* const rgbx, + __m128i* const r, + __m128i* const g, + __m128i* const b) { + const __m128i in0 = LOAD_16(rgbx + 0); // r0 | g0 | b0 |x| r1 | g1 | b1 |x + const __m128i in1 = LOAD_16(rgbx + 8); // r2 | g2 | b2 |x| r3 | g3 | b3 |x + const __m128i in2 = LOAD_16(rgbx + 16); // r4 | ... + const __m128i in3 = LOAD_16(rgbx + 24); // r6 | ... + // column-wise transpose + const __m128i A0 = _mm_unpacklo_epi16(in0, in1); + const __m128i A1 = _mm_unpackhi_epi16(in0, in1); + const __m128i A2 = _mm_unpacklo_epi16(in2, in3); + const __m128i A3 = _mm_unpackhi_epi16(in2, in3); + const __m128i B0 = _mm_unpacklo_epi16(A0, A1); // r0 r1 r2 r3 | g0 g1 .. + const __m128i B1 = _mm_unpackhi_epi16(A0, A1); // b0 b1 b2 b3 | x x x x + const __m128i B2 = _mm_unpacklo_epi16(A2, A3); // r4 r5 r6 r7 | g4 g5 .. + const __m128i B3 = _mm_unpackhi_epi16(A2, A3); // b4 b5 b6 b7 | x x x x + *r = _mm_unpacklo_epi64(B0, B2); + *g = _mm_unpackhi_epi64(B0, B2); + *b = _mm_unpacklo_epi64(B1, B3); +} + +static void ConvertRGBA32ToUV(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width) { + const int max_width = width & ~15; + const uint16_t* const last_rgb = rgb + 4 * max_width; + while (rgb < last_rgb) { + __m128i r, g, b, U0, V0, U1, V1; + RGBA32PackedToPlanar_16b(rgb + 0, &r, &g, &b); + ConvertRGBToUV(&r, &g, &b, &U0, &V0); + RGBA32PackedToPlanar_16b(rgb + 32, &r, &g, &b); + ConvertRGBToUV(&r, &g, &b, &U1, &V1); + STORE_16(_mm_packus_epi16(U0, U1), u); + STORE_16(_mm_packus_epi16(V0, V1), v); + u += 16; + v += 16; + rgb += 2 * 32; + } + if (max_width < width) { // left-over + WebPConvertRGBA32ToUV_C(rgb, u, v, width - max_width); + } +} + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVSSE2(void) { + WebPConvertARGBToY = ConvertARGBToY; + WebPConvertARGBToUV = ConvertARGBToUV; + + WebPConvertRGB24ToY = ConvertRGB24ToY; + WebPConvertBGR24ToY = ConvertBGR24ToY; + + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV; +} + +//------------------------------------------------------------------------------ + +#define MAX_Y ((1 << 10) - 1) // 10b precision over 16b-arithmetic +static uint16_t clip_y(int v) { + return (v < 0) ? 0 : (v > MAX_Y) ? MAX_Y : (uint16_t)v; +} + +static uint64_t SharpYUVUpdateY_SSE2(const uint16_t* ref, const uint16_t* src, + uint16_t* dst, int len) { + uint64_t diff = 0; + uint32_t tmp[4]; + int i; + const __m128i zero = _mm_setzero_si128(); + const __m128i max = _mm_set1_epi16(MAX_Y); + const __m128i one = _mm_set1_epi16(1); + __m128i sum = zero; + + for (i = 0; i + 8 <= len; i += 8) { + const __m128i A = _mm_loadu_si128((const __m128i*)(ref + i)); + const __m128i B = _mm_loadu_si128((const __m128i*)(src + i)); + const __m128i C = _mm_loadu_si128((const __m128i*)(dst + i)); + const __m128i D = _mm_sub_epi16(A, B); // diff_y + const __m128i E = _mm_cmpgt_epi16(zero, D); // sign (-1 or 0) + const __m128i F = _mm_add_epi16(C, D); // new_y + const __m128i G = _mm_or_si128(E, one); // -1 or 1 + const __m128i H = _mm_max_epi16(_mm_min_epi16(F, max), zero); + const __m128i I = _mm_madd_epi16(D, G); // sum(abs(...)) + _mm_storeu_si128((__m128i*)(dst + i), H); + sum = _mm_add_epi32(sum, I); + } + _mm_storeu_si128((__m128i*)tmp, sum); + diff = tmp[3] + tmp[2] + tmp[1] + tmp[0]; + for (; i < len; ++i) { + const int diff_y = ref[i] - src[i]; + const int new_y = (int)dst[i] + diff_y; + dst[i] = clip_y(new_y); + diff += (uint64_t)abs(diff_y); + } + return diff; +} + +static void SharpYUVUpdateRGB_SSE2(const int16_t* ref, const int16_t* src, + int16_t* dst, int len) { + int i = 0; + for (i = 0; i + 8 <= len; i += 8) { + const __m128i A = _mm_loadu_si128((const __m128i*)(ref + i)); + const __m128i B = _mm_loadu_si128((const __m128i*)(src + i)); + const __m128i C = _mm_loadu_si128((const __m128i*)(dst + i)); + const __m128i D = _mm_sub_epi16(A, B); // diff_uv + const __m128i E = _mm_add_epi16(C, D); // new_uv + _mm_storeu_si128((__m128i*)(dst + i), E); + } + for (; i < len; ++i) { + const int diff_uv = ref[i] - src[i]; + dst[i] += diff_uv; + } +} + +static void SharpYUVFilterRow_SSE2(const int16_t* A, const int16_t* B, int len, + const uint16_t* best_y, uint16_t* out) { + int i; + const __m128i kCst8 = _mm_set1_epi16(8); + const __m128i max = _mm_set1_epi16(MAX_Y); + const __m128i zero = _mm_setzero_si128(); + for (i = 0; i + 8 <= len; i += 8) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)(A + i + 0)); + const __m128i a1 = _mm_loadu_si128((const __m128i*)(A + i + 1)); + const __m128i b0 = _mm_loadu_si128((const __m128i*)(B + i + 0)); + const __m128i b1 = _mm_loadu_si128((const __m128i*)(B + i + 1)); + const __m128i a0b1 = _mm_add_epi16(a0, b1); + const __m128i a1b0 = _mm_add_epi16(a1, b0); + const __m128i a0a1b0b1 = _mm_add_epi16(a0b1, a1b0); // A0+A1+B0+B1 + const __m128i a0a1b0b1_8 = _mm_add_epi16(a0a1b0b1, kCst8); + const __m128i a0b1_2 = _mm_add_epi16(a0b1, a0b1); // 2*(A0+B1) + const __m128i a1b0_2 = _mm_add_epi16(a1b0, a1b0); // 2*(A1+B0) + const __m128i c0 = _mm_srai_epi16(_mm_add_epi16(a0b1_2, a0a1b0b1_8), 3); + const __m128i c1 = _mm_srai_epi16(_mm_add_epi16(a1b0_2, a0a1b0b1_8), 3); + const __m128i d0 = _mm_add_epi16(c1, a0); + const __m128i d1 = _mm_add_epi16(c0, a1); + const __m128i e0 = _mm_srai_epi16(d0, 1); + const __m128i e1 = _mm_srai_epi16(d1, 1); + const __m128i f0 = _mm_unpacklo_epi16(e0, e1); + const __m128i f1 = _mm_unpackhi_epi16(e0, e1); + const __m128i g0 = _mm_loadu_si128((const __m128i*)(best_y + 2 * i + 0)); + const __m128i g1 = _mm_loadu_si128((const __m128i*)(best_y + 2 * i + 8)); + const __m128i h0 = _mm_add_epi16(g0, f0); + const __m128i h1 = _mm_add_epi16(g1, f1); + const __m128i i0 = _mm_max_epi16(_mm_min_epi16(h0, max), zero); + const __m128i i1 = _mm_max_epi16(_mm_min_epi16(h1, max), zero); + _mm_storeu_si128((__m128i*)(out + 2 * i + 0), i0); + _mm_storeu_si128((__m128i*)(out + 2 * i + 8), i1); + } + for (; i < len; ++i) { + // (9 * A0 + 3 * A1 + 3 * B0 + B1 + 8) >> 4 = + // = (8 * A0 + 2 * (A1 + B0) + (A0 + A1 + B0 + B1 + 8)) >> 4 + // We reuse the common sub-expressions. + const int a0b1 = A[i + 0] + B[i + 1]; + const int a1b0 = A[i + 1] + B[i + 0]; + const int a0a1b0b1 = a0b1 + a1b0 + 8; + const int v0 = (8 * A[i + 0] + 2 * a1b0 + a0a1b0b1) >> 4; + const int v1 = (8 * A[i + 1] + 2 * a0b1 + a0a1b0b1) >> 4; + out[2 * i + 0] = clip_y(best_y[2 * i + 0] + v0); + out[2 * i + 1] = clip_y(best_y[2 * i + 1] + v1); + } +} + +#undef MAX_Y + +//------------------------------------------------------------------------------ + +extern void WebPInitSharpYUVSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSharpYUVSSE2(void) { + WebPSharpYUVUpdateY = SharpYUVUpdateY_SSE2; + WebPSharpYUVUpdateRGB = SharpYUVUpdateRGB_SSE2; + WebPSharpYUVFilterRow = SharpYUVFilterRow_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(WebPInitSamplersSSE2) +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVSSE2) +WEBP_DSP_INIT_STUB(WebPInitSharpYUVSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/3rdparty/libwebp/enc/Makefile.am b/3rdparty/libwebp/enc/Makefile.am new file mode 100644 index 0000000000..aead7fec95 --- /dev/null +++ b/3rdparty/libwebp/enc/Makefile.am @@ -0,0 +1,42 @@ +noinst_LTLIBRARIES = libwebpencode.la + +libwebpencode_la_SOURCES = +libwebpencode_la_SOURCES += alpha_enc.c +libwebpencode_la_SOURCES += analysis_enc.c +libwebpencode_la_SOURCES += backward_references_enc.c +libwebpencode_la_SOURCES += backward_references_enc.h +libwebpencode_la_SOURCES += config_enc.c +libwebpencode_la_SOURCES += cost_enc.c +libwebpencode_la_SOURCES += cost_enc.h +libwebpencode_la_SOURCES += delta_palettization_enc.c +libwebpencode_la_SOURCES += delta_palettization_enc.h +libwebpencode_la_SOURCES += filter_enc.c +libwebpencode_la_SOURCES += frame_enc.c +libwebpencode_la_SOURCES += histogram_enc.c +libwebpencode_la_SOURCES += histogram_enc.h +libwebpencode_la_SOURCES += iterator_enc.c +libwebpencode_la_SOURCES += near_lossless_enc.c +libwebpencode_la_SOURCES += picture_enc.c +libwebpencode_la_SOURCES += picture_csp_enc.c +libwebpencode_la_SOURCES += picture_psnr_enc.c +libwebpencode_la_SOURCES += picture_rescale_enc.c +libwebpencode_la_SOURCES += picture_tools_enc.c +libwebpencode_la_SOURCES += predictor_enc.c +libwebpencode_la_SOURCES += quant_enc.c +libwebpencode_la_SOURCES += syntax_enc.c +libwebpencode_la_SOURCES += token_enc.c +libwebpencode_la_SOURCES += tree_enc.c +libwebpencode_la_SOURCES += vp8i_enc.h +libwebpencode_la_SOURCES += vp8l_enc.c +libwebpencode_la_SOURCES += vp8li_enc.h +libwebpencode_la_SOURCES += webp_enc.c + +libwebpencodeinclude_HEADERS = +libwebpencodeinclude_HEADERS += ../webp/encode.h +libwebpencodeinclude_HEADERS += ../webp/types.h +noinst_HEADERS = +noinst_HEADERS += ../webp/format_constants.h + +libwebpencode_la_LDFLAGS = -lm +libwebpencode_la_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE) +libwebpencodeincludedir = $(includedir)/webp diff --git a/3rdparty/libwebp/enc/alpha.c b/3rdparty/libwebp/enc/alpha_enc.c similarity index 52% rename from 3rdparty/libwebp/enc/alpha.c rename to 3rdparty/libwebp/enc/alpha_enc.c index e636c96723..5a2c931f92 100644 --- a/3rdparty/libwebp/enc/alpha.c +++ b/3rdparty/libwebp/enc/alpha_enc.c @@ -14,15 +14,13 @@ #include #include -#include "./vp8enci.h" -#include "../utils/filters.h" -#include "../utils/quant_levels.h" +#include "./vp8i_enc.h" +#include "../dsp/dsp.h" +#include "../utils/filters_utils.h" +#include "../utils/quant_levels_utils.h" +#include "../utils/utils.h" #include "../webp/format_constants.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - // ----------------------------------------------------------------------------- // Encodes the given alpha data via specified compression method 'method'. // The pre-processing (quantization) is performed if 'quality' is less than 100. @@ -38,7 +36,7 @@ extern "C" { // // 'output' corresponds to the buffer containing compressed alpha data. // This buffer is allocated by this method and caller should call -// free(*output) when done. +// WebPSafeFree(*output) when done. // 'output_size' corresponds to size of this compressed alpha buffer. // // Returns 1 on successfully encoding the alpha and @@ -46,16 +44,15 @@ extern "C" { // invalid quality or method, or // memory allocation for the compressed data fails. -#include "../enc/vp8li.h" +#include "../enc/vp8li_enc.h" static int EncodeLossless(const uint8_t* const data, int width, int height, int effort_level, // in [0..6] range - VP8BitWriter* const bw, + VP8LBitWriter* const bw, WebPAuxStats* const stats) { int ok = 0; WebPConfig config; WebPPicture picture; - VP8LBitWriter tmp_bw; WebPPictureInit(&picture); picture.width = width; @@ -65,68 +62,66 @@ static int EncodeLossless(const uint8_t* const data, int width, int height, if (!WebPPictureAlloc(&picture)) return 0; // Transfer the alpha values to the green channel. - { - int i, j; - uint32_t* dst = picture.argb; - const uint8_t* src = data; - for (j = 0; j < picture.height; ++j) { - for (i = 0; i < picture.width; ++i) { - dst[i] = (src[i] << 8) | 0xff000000u; - } - src += width; - dst += picture.argb_stride; - } - } + WebPDispatchAlphaToGreen(data, width, picture.width, picture.height, + picture.argb, picture.argb_stride); WebPConfigInit(&config); config.lossless = 1; + // Enable exact, or it would alter RGB values of transparent alpha, which is + // normally OK but not here since we are not encoding the input image but an + // internal encoding-related image containing necessary exact information in + // RGB channels. + config.exact = 1; config.method = effort_level; // impact is very small - // Set a moderate default quality setting for alpha. - config.quality = 10.f * effort_level; + // Set a low default quality for encoding alpha. Ensure that Alpha quality at + // lower methods (3 and below) is less than the threshold for triggering + // costly 'BackwardReferencesTraceBackwards'. + config.quality = 8.f * effort_level; assert(config.quality >= 0 && config.quality <= 100.f); - ok = VP8LBitWriterInit(&tmp_bw, (width * height) >> 3); - ok = ok && (VP8LEncodeStream(&config, &picture, &tmp_bw) == VP8_ENC_OK); + // TODO(urvang): Temporary fix to avoid generating images that trigger + // a decoder bug related to alpha with color cache. + // See: https://code.google.com/p/webp/issues/detail?id=239 + // Need to re-enable this later. + ok = (VP8LEncodeStream(&config, &picture, bw, 0 /*use_cache*/) == VP8_ENC_OK); WebPPictureFree(&picture); - if (ok) { - const uint8_t* const buffer = VP8LBitWriterFinish(&tmp_bw); - const size_t buffer_size = VP8LBitWriterNumBytes(&tmp_bw); - VP8BitWriterAppend(bw, buffer, buffer_size); + ok = ok && !bw->error_; + if (!ok) { + VP8LBitWriterWipeOut(bw); + return 0; } - VP8LBitWriterDestroy(&tmp_bw); - return ok && !bw->error_; + return 1; } // ----------------------------------------------------------------------------- +// Small struct to hold the result of a filter mode compression attempt. +typedef struct { + size_t score; + VP8BitWriter bw; + WebPAuxStats stats; +} FilterTrial; + +// This function always returns an initialized 'bw' object, even upon error. static int EncodeAlphaInternal(const uint8_t* const data, int width, int height, int method, int filter, int reduce_levels, int effort_level, // in [0..6] range uint8_t* const tmp_alpha, - VP8BitWriter* const bw, - WebPAuxStats* const stats) { + FilterTrial* result) { int ok = 0; const uint8_t* alpha_src; WebPFilterFunc filter_func; uint8_t header; - size_t expected_size; const size_t data_size = width * height; + const uint8_t* output = NULL; + size_t output_size = 0; + VP8LBitWriter tmp_bw; assert((uint64_t)data_size == (uint64_t)width * height); // as per spec assert(filter >= 0 && filter < WEBP_FILTER_LAST); assert(method >= ALPHA_NO_COMPRESSION); assert(method <= ALPHA_LOSSLESS_COMPRESSION); assert(sizeof(header) == ALPHA_HEADER_LEN); - // TODO(skal): have a common function and #define's to validate alpha params. - - expected_size = - (method == ALPHA_NO_COMPRESSION) ? (ALPHA_HEADER_LEN + data_size) - : (data_size >> 5); - header = method | (filter << 2); - if (reduce_levels) header |= ALPHA_PREPROCESSED_LEVELS << 4; - - VP8BitWriterInit(bw, expected_size); - VP8BitWriterAppend(bw, &header, ALPHA_HEADER_LEN); filter_func = WebPFilters[filter]; if (filter_func != NULL) { @@ -136,28 +131,48 @@ static int EncodeAlphaInternal(const uint8_t* const data, int width, int height, alpha_src = data; } - if (method == ALPHA_NO_COMPRESSION) { - ok = VP8BitWriterAppend(bw, alpha_src, width * height); - ok = ok && !bw->error_; - } else { - ok = EncodeLossless(alpha_src, width, height, effort_level, bw, stats); - VP8BitWriterFinish(bw); + if (method != ALPHA_NO_COMPRESSION) { + ok = VP8LBitWriterInit(&tmp_bw, data_size >> 3); + ok = ok && EncodeLossless(alpha_src, width, height, effort_level, + &tmp_bw, &result->stats); + if (ok) { + output = VP8LBitWriterFinish(&tmp_bw); + output_size = VP8LBitWriterNumBytes(&tmp_bw); + if (output_size > data_size) { + // compressed size is larger than source! Revert to uncompressed mode. + method = ALPHA_NO_COMPRESSION; + VP8LBitWriterWipeOut(&tmp_bw); + } + } else { + VP8LBitWriterWipeOut(&tmp_bw); + return 0; + } } + + if (method == ALPHA_NO_COMPRESSION) { + output = alpha_src; + output_size = data_size; + ok = 1; + } + + // Emit final result. + header = method | (filter << 2); + if (reduce_levels) header |= ALPHA_PREPROCESSED_LEVELS << 4; + + VP8BitWriterInit(&result->bw, ALPHA_HEADER_LEN + output_size); + ok = ok && VP8BitWriterAppend(&result->bw, &header, ALPHA_HEADER_LEN); + ok = ok && VP8BitWriterAppend(&result->bw, output, output_size); + + if (method != ALPHA_NO_COMPRESSION) { + VP8LBitWriterWipeOut(&tmp_bw); + } + ok = ok && !result->bw.error_; + result->score = VP8BitWriterSize(&result->bw); return ok; } // ----------------------------------------------------------------------------- -// TODO(skal): move to dsp/ ? -static void CopyPlane(const uint8_t* src, int src_stride, - uint8_t* dst, int dst_stride, int width, int height) { - while (height-- > 0) { - memcpy(dst, src, width); - src += src_stride; - dst += dst_stride; - } -} - static int GetNumColors(const uint8_t* data, int width, int height, int stride) { int j; @@ -177,6 +192,96 @@ static int GetNumColors(const uint8_t* data, int width, int height, return colors; } +#define FILTER_TRY_NONE (1 << WEBP_FILTER_NONE) +#define FILTER_TRY_ALL ((1 << WEBP_FILTER_LAST) - 1) + +// Given the input 'filter' option, return an OR'd bit-set of filters to try. +static uint32_t GetFilterMap(const uint8_t* alpha, int width, int height, + int filter, int effort_level) { + uint32_t bit_map = 0U; + if (filter == WEBP_FILTER_FAST) { + // Quick estimate of the best candidate. + int try_filter_none = (effort_level > 3); + const int kMinColorsForFilterNone = 16; + const int kMaxColorsForFilterNone = 192; + const int num_colors = GetNumColors(alpha, width, height, width); + // For low number of colors, NONE yields better compression. + filter = (num_colors <= kMinColorsForFilterNone) + ? WEBP_FILTER_NONE + : WebPEstimateBestFilter(alpha, width, height, width); + bit_map |= 1 << filter; + // For large number of colors, try FILTER_NONE in addition to the best + // filter as well. + if (try_filter_none || num_colors > kMaxColorsForFilterNone) { + bit_map |= FILTER_TRY_NONE; + } + } else if (filter == WEBP_FILTER_NONE) { + bit_map = FILTER_TRY_NONE; + } else { // WEBP_FILTER_BEST -> try all + bit_map = FILTER_TRY_ALL; + } + return bit_map; +} + +static void InitFilterTrial(FilterTrial* const score) { + score->score = (size_t)~0U; + VP8BitWriterInit(&score->bw, 0); +} + +static int ApplyFiltersAndEncode(const uint8_t* alpha, int width, int height, + size_t data_size, int method, int filter, + int reduce_levels, int effort_level, + uint8_t** const output, + size_t* const output_size, + WebPAuxStats* const stats) { + int ok = 1; + FilterTrial best; + uint32_t try_map = + GetFilterMap(alpha, width, height, filter, effort_level); + InitFilterTrial(&best); + + if (try_map != FILTER_TRY_NONE) { + uint8_t* filtered_alpha = (uint8_t*)WebPSafeMalloc(1ULL, data_size); + if (filtered_alpha == NULL) return 0; + + for (filter = WEBP_FILTER_NONE; ok && try_map; ++filter, try_map >>= 1) { + if (try_map & 1) { + FilterTrial trial; + ok = EncodeAlphaInternal(alpha, width, height, method, filter, + reduce_levels, effort_level, filtered_alpha, + &trial); + if (ok && trial.score < best.score) { + VP8BitWriterWipeOut(&best.bw); + best = trial; + } else { + VP8BitWriterWipeOut(&trial.bw); + } + } + } + WebPSafeFree(filtered_alpha); + } else { + ok = EncodeAlphaInternal(alpha, width, height, method, WEBP_FILTER_NONE, + reduce_levels, effort_level, NULL, &best); + } + if (ok) { + if (stats != NULL) { + stats->lossless_features = best.stats.lossless_features; + stats->histogram_bits = best.stats.histogram_bits; + stats->transform_bits = best.stats.transform_bits; + stats->cache_bits = best.stats.cache_bits; + stats->palette_size = best.stats.palette_size; + stats->lossless_size = best.stats.lossless_size; + stats->lossless_hdr_size = best.stats.lossless_hdr_size; + stats->lossless_data_size = best.stats.lossless_data_size; + } + *output_size = VP8BitWriterSize(&best.bw); + *output = VP8BitWriterBuf(&best.bw); + } else { + VP8BitWriterWipeOut(&best.bw); + } + return ok; +} + static int EncodeAlpha(VP8Encoder* const enc, int quality, int method, int filter, int effort_level, @@ -207,13 +312,18 @@ static int EncodeAlpha(VP8Encoder* const enc, return 0; } - quant_alpha = (uint8_t*)malloc(data_size); + if (method == ALPHA_NO_COMPRESSION) { + // Don't filter, as filtering will make no impact on compressed size. + filter = WEBP_FILTER_NONE; + } + + quant_alpha = (uint8_t*)WebPSafeMalloc(1ULL, data_size); if (quant_alpha == NULL) { return 0; } // Extract alpha data (width x height) from raw_data (stride x height). - CopyPlane(pic->a, pic->a_stride, quant_alpha, width, width, height); + WebPCopyPlane(pic->a, pic->a_stride, quant_alpha, width, width, height); if (reduce_levels) { // No Quantization required for 'quality = 100'. // 16 alpha levels gives quite a low MSE w.r.t original alpha plane hence @@ -225,105 +335,20 @@ static int EncodeAlpha(VP8Encoder* const enc, } if (ok) { - VP8BitWriter bw; - int test_filter; - uint8_t* filtered_alpha = NULL; - int try_filter_none = (effort_level > 3); - - if (filter == WEBP_FILTER_FAST) { // Quick estimate of the best candidate. - const int kMinColorsForFilterNone = 16; - const int kMaxColorsForFilterNone = 192; - const int num_colors = GetNumColors(quant_alpha, width, height, width); - // For low number of colors, NONE yeilds better compression. - filter = (num_colors <= kMinColorsForFilterNone) ? WEBP_FILTER_NONE : - EstimateBestFilter(quant_alpha, width, height, width); - // For large number of colors, try FILTER_NONE in addition to the best - // filter as well. - if (num_colors > kMaxColorsForFilterNone) { - try_filter_none = 1; - } + VP8FiltersInit(); + ok = ApplyFiltersAndEncode(quant_alpha, width, height, data_size, method, + filter, reduce_levels, effort_level, output, + output_size, pic->stats); + if (pic->stats != NULL) { // need stats? + pic->stats->coded_size += (int)(*output_size); + enc->sse_[3] = sse; } - - // Test for WEBP_FILTER_NONE for higher effort levels. - if (try_filter_none || filter == WEBP_FILTER_NONE) { - ok = EncodeAlphaInternal(quant_alpha, width, height, - method, WEBP_FILTER_NONE, reduce_levels, - effort_level, NULL, &bw, pic->stats); - - if (!ok) { - VP8BitWriterWipeOut(&bw); - goto End; - } - } - // Stop? - if (filter == WEBP_FILTER_NONE) { - goto Ok; - } - - filtered_alpha = (uint8_t*)malloc(data_size); - ok = (filtered_alpha != NULL); - if (!ok) { - goto End; - } - - // Try the other mode(s). - { - WebPAuxStats best_stats; - size_t best_score = try_filter_none ? - VP8BitWriterSize(&bw) : (size_t)~0U; - int wipe_tmp_bw = try_filter_none; - - memset(&best_stats, 0, sizeof(best_stats)); // prevent spurious warning - if (pic->stats != NULL) best_stats = *pic->stats; - for (test_filter = - try_filter_none ? WEBP_FILTER_HORIZONTAL : WEBP_FILTER_NONE; - ok && (test_filter <= WEBP_FILTER_GRADIENT); - ++test_filter) { - VP8BitWriter tmp_bw; - if (filter != WEBP_FILTER_BEST && test_filter != filter) { - continue; - } - ok = EncodeAlphaInternal(quant_alpha, width, height, - method, test_filter, reduce_levels, - effort_level, filtered_alpha, &tmp_bw, - pic->stats); - if (ok) { - const size_t score = VP8BitWriterSize(&tmp_bw); - if (score < best_score) { - // swap bitwriter objects. - VP8BitWriter tmp = tmp_bw; - tmp_bw = bw; - bw = tmp; - best_score = score; - if (pic->stats != NULL) best_stats = *pic->stats; - } - } else { - VP8BitWriterWipeOut(&bw); - } - if (wipe_tmp_bw) { - VP8BitWriterWipeOut(&tmp_bw); - } - wipe_tmp_bw = 1; // For next filter trial for WEBP_FILTER_BEST. - } - if (pic->stats != NULL) *pic->stats = best_stats; - } - Ok: - if (ok) { - *output_size = VP8BitWriterSize(&bw); - *output = VP8BitWriterBuf(&bw); - if (pic->stats != NULL) { // need stats? - pic->stats->coded_size += (int)(*output_size); - enc->sse_[3] = sse; - } - } - free(filtered_alpha); } - End: - free(quant_alpha); + + WebPSafeFree(quant_alpha); return ok; } - //------------------------------------------------------------------------------ // Main calls @@ -341,7 +366,7 @@ static int CompressAlphaJob(VP8Encoder* const enc, void* dummy) { return 0; } if (alpha_size != (uint32_t)alpha_size) { // Sanity check. - free(alpha_data); + WebPSafeFree(alpha_data); return 0; } enc->alpha_data_size_ = (uint32_t)alpha_size; @@ -351,12 +376,13 @@ static int CompressAlphaJob(VP8Encoder* const enc, void* dummy) { } void VP8EncInitAlpha(VP8Encoder* const enc) { + WebPInitAlphaProcessing(); enc->has_alpha_ = WebPPictureHasTransparency(enc->pic_); enc->alpha_data_ = NULL; enc->alpha_data_size_ = 0; if (enc->thread_level_ > 0) { WebPWorker* const worker = &enc->alpha_worker_; - WebPWorkerInit(worker); + WebPGetWorkerInterface()->Init(worker); worker->data1 = enc; worker->data2 = NULL; worker->hook = (WebPWorkerHook)CompressAlphaJob; @@ -367,10 +393,11 @@ int VP8EncStartAlpha(VP8Encoder* const enc) { if (enc->has_alpha_) { if (enc->thread_level_ > 0) { WebPWorker* const worker = &enc->alpha_worker_; - if (!WebPWorkerReset(worker)) { // Makes sure worker is good to go. + // Makes sure worker is good to go. + if (!WebPGetWorkerInterface()->Reset(worker)) { return 0; } - WebPWorkerLaunch(worker); + WebPGetWorkerInterface()->Launch(worker); return 1; } else { return CompressAlphaJob(enc, NULL); // just do the job right away @@ -383,7 +410,7 @@ int VP8EncFinishAlpha(VP8Encoder* const enc) { if (enc->has_alpha_) { if (enc->thread_level_ > 0) { WebPWorker* const worker = &enc->alpha_worker_; - if (!WebPWorkerSync(worker)) return 0; // error + if (!WebPGetWorkerInterface()->Sync(worker)) return 0; // error } } return WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_); @@ -393,16 +420,14 @@ int VP8EncDeleteAlpha(VP8Encoder* const enc) { int ok = 1; if (enc->thread_level_ > 0) { WebPWorker* const worker = &enc->alpha_worker_; - ok = WebPWorkerSync(worker); // finish anything left in flight - WebPWorkerEnd(worker); // still need to end the worker, even if !ok + // finish anything left in flight + ok = WebPGetWorkerInterface()->Sync(worker); + // still need to end the worker, even if !ok + WebPGetWorkerInterface()->End(worker); } - free(enc->alpha_data_); + WebPSafeFree(enc->alpha_data_); enc->alpha_data_ = NULL; enc->alpha_data_size_ = 0; enc->has_alpha_ = 0; return ok; } - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/analysis.c b/3rdparty/libwebp/enc/analysis_enc.c similarity index 61% rename from 3rdparty/libwebp/enc/analysis.c rename to 3rdparty/libwebp/enc/analysis_enc.c index 4ff3edd2a7..dce159b316 100644 --- a/3rdparty/libwebp/enc/analysis.c +++ b/3rdparty/libwebp/enc/analysis_enc.c @@ -15,14 +15,10 @@ #include #include -#include "./vp8enci.h" -#include "./cost.h" +#include "./vp8i_enc.h" +#include "./cost_enc.h" #include "../utils/utils.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - #define MAX_ITERS_K_MEANS 6 //------------------------------------------------------------------------------ @@ -34,7 +30,7 @@ static void SmoothSegmentMap(VP8Encoder* const enc) { const int w = enc->mb_w_; const int h = enc->mb_h_; const int majority_cnt_3_x_3_grid = 5; - uint8_t* const tmp = (uint8_t*)WebPSafeMalloc((uint64_t)w * h, sizeof(*tmp)); + uint8_t* const tmp = (uint8_t*)WebPSafeMalloc(w * h, sizeof(*tmp)); assert((uint64_t)(w * h) == (uint64_t)w * h); // no overflow, as per spec if (tmp == NULL) return; @@ -55,6 +51,7 @@ static void SmoothSegmentMap(VP8Encoder* const enc) { for (n = 0; n < NUM_MB_SEGMENTS; ++n) { if (cnt[n] >= majority_cnt_3_x_3_grid) { majority_seg = n; + break; } } tmp[x + y * w] = majority_seg; @@ -66,7 +63,7 @@ static void SmoothSegmentMap(VP8Encoder* const enc) { mb->segment_ = tmp[x + y * w]; } } - free(tmp); + WebPSafeFree(tmp); } //------------------------------------------------------------------------------ @@ -114,28 +111,28 @@ static int FinalAlphaValue(int alpha) { } static int GetAlpha(const VP8Histogram* const histo) { - int max_value = 0, last_non_zero = 1; - int k; - int alpha; - for (k = 0; k <= MAX_COEFF_THRESH; ++k) { - const int value = histo->distribution[k]; - if (value > 0) { - if (value > max_value) max_value = value; - last_non_zero = k; - } - } // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer // values which happen to be mostly noise. This leaves the maximum precision // for handling the useful small values which contribute most. - alpha = (max_value > 1) ? ALPHA_SCALE * last_non_zero / max_value : 0; + const int max_value = histo->max_value; + const int last_non_zero = histo->last_non_zero; + const int alpha = + (max_value > 1) ? ALPHA_SCALE * last_non_zero / max_value : 0; return alpha; } +static void InitHistogram(VP8Histogram* const histo) { + histo->max_value = 0; + histo->last_non_zero = 1; +} + static void MergeHistograms(const VP8Histogram* const in, VP8Histogram* const out) { - int i; - for (i = 0; i <= MAX_COEFF_THRESH; ++i) { - out->distribution[i] += in->distribution[i]; + if (in->max_value > out->max_value) { + out->max_value = in->max_value; + } + if (in->last_non_zero > out->last_non_zero) { + out->last_non_zero = in->last_non_zero; } } @@ -144,7 +141,11 @@ static void MergeHistograms(const VP8Histogram* const in, static void AssignSegments(VP8Encoder* const enc, const int alphas[MAX_ALPHA + 1]) { - const int nb = enc->segment_hdr_.num_segments_; + // 'num_segments_' is previously validated and <= NUM_MB_SEGMENTS, but an + // explicit check is needed to avoid spurious warning about 'n + 1' exceeding + // array bounds of 'centers' with some compilers (noticed with gcc-4.9). + const int nb = (enc->segment_hdr_.num_segments_ < NUM_MB_SEGMENTS) ? + enc->segment_hdr_.num_segments_ : NUM_MB_SEGMENTS; int centers[NUM_MB_SEGMENTS]; int weighted_average = 0; int map[MAX_ALPHA + 1]; @@ -153,6 +154,9 @@ static void AssignSegments(VP8Encoder* const enc, // 'int' type is ok for histo, and won't overflow int accum[NUM_MB_SEGMENTS], dist_accum[NUM_MB_SEGMENTS]; + assert(nb >= 1); + assert(nb <= NUM_MB_SEGMENTS); + // bracket the input for (n = 0; n <= MAX_ALPHA && alphas[n] == 0; ++n) {} min_a = n; @@ -161,8 +165,9 @@ static void AssignSegments(VP8Encoder* const enc, range_a = max_a - min_a; // Spread initial centers evenly - for (n = 1, k = 0; n < 2 * nb; n += 2) { - centers[k++] = min_a + (n * range_a) / (2 * nb); + for (k = 0, n = 1; k < nb; ++k, n += 2) { + assert(n < 2 * nb); + centers[k] = min_a + (n * range_a) / (2 * nb); } for (k = 0; k < MAX_ITERS_K_MEANS; ++k) { // few iters are enough @@ -177,7 +182,7 @@ static void AssignSegments(VP8Encoder* const enc, n = 0; // track the nearest center for current 'a' for (a = min_a; a <= max_a; ++a) { if (alphas[a]) { - while (n < nb - 1 && abs(a - centers[n + 1]) < abs(a - centers[n])) { + while (n + 1 < nb && abs(a - centers[n + 1]) < abs(a - centers[n])) { n++; } map[a] = n; @@ -225,28 +230,26 @@ static void AssignSegments(VP8Encoder* const enc, // susceptibility and set best modes for this macroblock. // Segment assignment is done later. -// Number of modes to inspect for alpha_ evaluation. For high-quality settings -// (method >= FAST_ANALYSIS_METHOD) we don't need to test all the possible modes -// during the analysis phase. -#define FAST_ANALYSIS_METHOD 4 // method above which we do partial analysis +// Number of modes to inspect for alpha_ evaluation. We don't need to test all +// the possible modes during the analysis phase: we risk falling into a local +// optimum, or be subject to boundary effect #define MAX_INTRA16_MODE 2 #define MAX_INTRA4_MODE 2 #define MAX_UV_MODE 2 static int MBAnalyzeBestIntra16Mode(VP8EncIterator* const it) { - const int max_mode = - (it->enc_->method_ >= FAST_ANALYSIS_METHOD) ? MAX_INTRA16_MODE - : NUM_PRED_MODES; + const int max_mode = MAX_INTRA16_MODE; int mode; int best_alpha = DEFAULT_ALPHA; int best_mode = 0; VP8MakeLuma16Preds(it); for (mode = 0; mode < max_mode; ++mode) { - VP8Histogram histo = { { 0 } }; + VP8Histogram histo; int alpha; - VP8CollectHistogram(it->yuv_in_ + Y_OFF, + InitHistogram(&histo); + VP8CollectHistogram(it->yuv_in_ + Y_OFF_ENC, it->yuv_p_ + VP8I16ModeOffsets[mode], 0, 16, &histo); alpha = GetAlpha(&histo); @@ -259,28 +262,50 @@ static int MBAnalyzeBestIntra16Mode(VP8EncIterator* const it) { return best_alpha; } +static int FastMBAnalyze(VP8EncIterator* const it) { + // Empirical cut-off value, should be around 16 (~=block size). We use the + // [8-17] range and favor intra4 at high quality, intra16 for low quality. + const int q = (int)it->enc_->config_->quality; + const uint32_t kThreshold = 8 + (17 - 8) * q / 100; + int k; + uint32_t dc[16], m, m2; + for (k = 0; k < 16; k += 4) { + VP8Mean16x4(it->yuv_in_ + Y_OFF_ENC + k * BPS, &dc[k]); + } + for (m = 0, m2 = 0, k = 0; k < 16; ++k) { + m += dc[k]; + m2 += dc[k] * dc[k]; + } + if (kThreshold * m2 < m * m) { + VP8SetIntra16Mode(it, 0); // DC16 + } else { + const uint8_t modes[16] = { 0 }; // DC4 + VP8SetIntra4Mode(it, modes); + } + return 0; +} + static int MBAnalyzeBestIntra4Mode(VP8EncIterator* const it, int best_alpha) { uint8_t modes[16]; - const int max_mode = - (it->enc_->method_ >= FAST_ANALYSIS_METHOD) ? MAX_INTRA4_MODE - : NUM_BMODES; + const int max_mode = MAX_INTRA4_MODE; int i4_alpha; - VP8Histogram total_histo = { { 0 } }; + VP8Histogram total_histo; int cur_histo = 0; + InitHistogram(&total_histo); VP8IteratorStartI4(it); do { int mode; int best_mode_alpha = DEFAULT_ALPHA; VP8Histogram histos[2]; - const uint8_t* const src = it->yuv_in_ + Y_OFF + VP8Scan[it->i4_]; + const uint8_t* const src = it->yuv_in_ + Y_OFF_ENC + VP8Scan[it->i4_]; VP8MakeIntra4Preds(it); for (mode = 0; mode < max_mode; ++mode) { int alpha; - memset(&histos[cur_histo], 0, sizeof(histos[cur_histo])); + InitHistogram(&histos[cur_histo]); VP8CollectHistogram(src, it->yuv_p_ + VP8I4ModeOffsets[mode], 0, 1, &histos[cur_histo]); alpha = GetAlpha(&histos[cur_histo]); @@ -293,7 +318,7 @@ static int MBAnalyzeBestIntra4Mode(VP8EncIterator* const it, // accumulate best histogram MergeHistograms(&histos[cur_histo ^ 1], &total_histo); // Note: we reuse the original samples for predictors - } while (VP8IteratorRotateI4(it, it->yuv_in_ + Y_OFF)); + } while (VP8IteratorRotateI4(it, it->yuv_in_ + Y_OFF_ENC)); i4_alpha = GetAlpha(&total_histo); if (IS_BETTER_ALPHA(i4_alpha, best_alpha)) { @@ -305,21 +330,26 @@ static int MBAnalyzeBestIntra4Mode(VP8EncIterator* const it, static int MBAnalyzeBestUVMode(VP8EncIterator* const it) { int best_alpha = DEFAULT_ALPHA; + int smallest_alpha = 0; int best_mode = 0; - const int max_mode = - (it->enc_->method_ >= FAST_ANALYSIS_METHOD) ? MAX_UV_MODE - : NUM_PRED_MODES; + const int max_mode = MAX_UV_MODE; int mode; + VP8MakeChroma8Preds(it); for (mode = 0; mode < max_mode; ++mode) { - VP8Histogram histo = { { 0 } }; + VP8Histogram histo; int alpha; - VP8CollectHistogram(it->yuv_in_ + U_OFF, + InitHistogram(&histo); + VP8CollectHistogram(it->yuv_in_ + U_OFF_ENC, it->yuv_p_ + VP8UVModeOffsets[mode], 16, 16 + 4 + 4, &histo); alpha = GetAlpha(&histo); if (IS_BETTER_ALPHA(alpha, best_alpha)) { best_alpha = alpha; + } + // The best prediction mode tends to be the one with the smallest alpha. + if (mode == 0 || alpha < smallest_alpha) { + smallest_alpha = alpha; best_mode = mode; } } @@ -337,13 +367,17 @@ static void MBAnalyze(VP8EncIterator* const it, VP8SetSkip(it, 0); // not skipped VP8SetSegment(it, 0); // default segment, spec-wise. - best_alpha = MBAnalyzeBestIntra16Mode(it); - if (enc->method_ >= 5) { - // We go and make a fast decision for intra4/intra16. - // It's usually not a good and definitive pick, but helps seeding the stats - // about level bit-cost. - // TODO(skal): improve criterion. - best_alpha = MBAnalyzeBestIntra4Mode(it, best_alpha); + if (enc->method_ <= 1) { + best_alpha = FastMBAnalyze(it); + } else { + best_alpha = MBAnalyzeBestIntra16Mode(it); + if (enc->method_ >= 5) { + // We go and make a fast decision for intra4/intra16. + // It's usually not a good and definitive pick, but helps seeding the + // stats about level bit-cost. + // TODO(skal): improve criterion. + best_alpha = MBAnalyzeBestIntra4Mode(it, best_alpha); + } } best_uv_alpha = MBAnalyzeBestUVMode(it); @@ -384,38 +418,116 @@ static void ResetAllMBInfo(VP8Encoder* const enc) { // Default susceptibilities. enc->dqm_[0].alpha_ = 0; enc->dqm_[0].beta_ = 0; - // Note: we can't compute this alpha_ / uv_alpha_. + // Note: we can't compute this alpha_ / uv_alpha_ -> set to default value. + enc->alpha_ = 0; + enc->uv_alpha_ = 0; WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_); } +// struct used to collect job result +typedef struct { + WebPWorker worker; + int alphas[MAX_ALPHA + 1]; + int alpha, uv_alpha; + VP8EncIterator it; + int delta_progress; +} SegmentJob; + +// main work call +static int DoSegmentsJob(SegmentJob* const job, VP8EncIterator* const it) { + int ok = 1; + if (!VP8IteratorIsDone(it)) { + uint8_t tmp[32 + WEBP_ALIGN_CST]; + uint8_t* const scratch = (uint8_t*)WEBP_ALIGN(tmp); + do { + // Let's pretend we have perfect lossless reconstruction. + VP8IteratorImport(it, scratch); + MBAnalyze(it, job->alphas, &job->alpha, &job->uv_alpha); + ok = VP8IteratorProgress(it, job->delta_progress); + } while (ok && VP8IteratorNext(it)); + } + return ok; +} + +static void MergeJobs(const SegmentJob* const src, SegmentJob* const dst) { + int i; + for (i = 0; i <= MAX_ALPHA; ++i) dst->alphas[i] += src->alphas[i]; + dst->alpha += src->alpha; + dst->uv_alpha += src->uv_alpha; +} + +// initialize the job struct with some TODOs +static void InitSegmentJob(VP8Encoder* const enc, SegmentJob* const job, + int start_row, int end_row) { + WebPGetWorkerInterface()->Init(&job->worker); + job->worker.data1 = job; + job->worker.data2 = &job->it; + job->worker.hook = (WebPWorkerHook)DoSegmentsJob; + VP8IteratorInit(enc, &job->it); + VP8IteratorSetRow(&job->it, start_row); + VP8IteratorSetCountDown(&job->it, (end_row - start_row) * enc->mb_w_); + memset(job->alphas, 0, sizeof(job->alphas)); + job->alpha = 0; + job->uv_alpha = 0; + // only one of both jobs can record the progress, since we don't + // expect the user's hook to be multi-thread safe + job->delta_progress = (start_row == 0) ? 20 : 0; +} + +// main entry point int VP8EncAnalyze(VP8Encoder* const enc) { int ok = 1; const int do_segments = enc->config_->emulate_jpeg_size || // We need the complexity evaluation. (enc->segment_hdr_.num_segments_ > 1) || - (enc->method_ == 0); // for method 0, we need preds_[] to be filled. - enc->alpha_ = 0; - enc->uv_alpha_ = 0; + (enc->method_ <= 1); // for method 0 - 1, we need preds_[] to be filled. if (do_segments) { - int alphas[MAX_ALPHA + 1] = { 0 }; - VP8EncIterator it; - - VP8IteratorInit(enc, &it); - do { - VP8IteratorImport(&it); - MBAnalyze(&it, alphas, &enc->alpha_, &enc->uv_alpha_); - ok = VP8IteratorProgress(&it, 20); - // Let's pretend we have perfect lossless reconstruction. - } while (ok && VP8IteratorNext(&it, it.yuv_in_)); - enc->alpha_ /= enc->mb_w_ * enc->mb_h_; - enc->uv_alpha_ /= enc->mb_w_ * enc->mb_h_; - if (ok) AssignSegments(enc, alphas); + const int last_row = enc->mb_h_; + // We give a little more than a half work to the main thread. + const int split_row = (9 * last_row + 15) >> 4; + const int total_mb = last_row * enc->mb_w_; +#ifdef WEBP_USE_THREAD + const int kMinSplitRow = 2; // minimal rows needed for mt to be worth it + const int do_mt = (enc->thread_level_ > 0) && (split_row >= kMinSplitRow); +#else + const int do_mt = 0; +#endif + const WebPWorkerInterface* const worker_interface = + WebPGetWorkerInterface(); + SegmentJob main_job; + if (do_mt) { + SegmentJob side_job; + // Note the use of '&' instead of '&&' because we must call the functions + // no matter what. + InitSegmentJob(enc, &main_job, 0, split_row); + InitSegmentJob(enc, &side_job, split_row, last_row); + // we don't need to call Reset() on main_job.worker, since we're calling + // WebPWorkerExecute() on it + ok &= worker_interface->Reset(&side_job.worker); + // launch the two jobs in parallel + if (ok) { + worker_interface->Launch(&side_job.worker); + worker_interface->Execute(&main_job.worker); + ok &= worker_interface->Sync(&side_job.worker); + ok &= worker_interface->Sync(&main_job.worker); + } + worker_interface->End(&side_job.worker); + if (ok) MergeJobs(&side_job, &main_job); // merge results together + } else { + // Even for single-thread case, we use the generic Worker tools. + InitSegmentJob(enc, &main_job, 0, last_row); + worker_interface->Execute(&main_job.worker); + ok &= worker_interface->Sync(&main_job.worker); + } + worker_interface->End(&main_job.worker); + if (ok) { + enc->alpha_ = main_job.alpha / total_mb; + enc->uv_alpha_ = main_job.uv_alpha / total_mb; + AssignSegments(enc, main_job.alphas); + } } else { // Use only one default segment. ResetAllMBInfo(enc); } return ok; } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/backward_references.c b/3rdparty/libwebp/enc/backward_references.c deleted file mode 100644 index db4f430df5..0000000000 --- a/3rdparty/libwebp/enc/backward_references.c +++ /dev/null @@ -1,894 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Author: Jyrki Alakuijala (jyrki@google.com) -// - -#include -#include -#include - -#include "./backward_references.h" -#include "./histogram.h" -#include "../dsp/lossless.h" -#include "../utils/color_cache.h" -#include "../utils/utils.h" - -#define VALUES_IN_BYTE 256 - -#define HASH_BITS 18 -#define HASH_SIZE (1 << HASH_BITS) -#define HASH_MULTIPLIER (0xc6a4a7935bd1e995ULL) - -// 1M window (4M bytes) minus 120 special codes for short distances. -#define WINDOW_SIZE ((1 << 20) - 120) - -// Bounds for the match length. -#define MIN_LENGTH 2 -#define MAX_LENGTH 4096 - -typedef struct { - // Stores the most recently added position with the given hash value. - int32_t hash_to_first_index_[HASH_SIZE]; - // chain_[pos] stores the previous position with the same hash value - // for every pixel in the image. - int32_t* chain_; -} HashChain; - -// ----------------------------------------------------------------------------- - -static const uint8_t plane_to_code_lut[128] = { - 96, 73, 55, 39, 23, 13, 5, 1, 255, 255, 255, 255, 255, 255, 255, 255, - 101, 78, 58, 42, 26, 16, 8, 2, 0, 3, 9, 17, 27, 43, 59, 79, - 102, 86, 62, 46, 32, 20, 10, 6, 4, 7, 11, 21, 33, 47, 63, 87, - 105, 90, 70, 52, 37, 28, 18, 14, 12, 15, 19, 29, 38, 53, 71, 91, - 110, 99, 82, 66, 48, 35, 30, 24, 22, 25, 31, 36, 49, 67, 83, 100, - 115, 108, 94, 76, 64, 50, 44, 40, 34, 41, 45, 51, 65, 77, 95, 109, - 118, 113, 103, 92, 80, 68, 60, 56, 54, 57, 61, 69, 81, 93, 104, 114, - 119, 116, 111, 106, 97, 88, 84, 74, 72, 75, 85, 89, 98, 107, 112, 117 -}; - -static int DistanceToPlaneCode(int xsize, int dist) { - const int yoffset = dist / xsize; - const int xoffset = dist - yoffset * xsize; - if (xoffset <= 8 && yoffset < 8) { - return plane_to_code_lut[yoffset * 16 + 8 - xoffset] + 1; - } else if (xoffset > xsize - 8 && yoffset < 7) { - return plane_to_code_lut[(yoffset + 1) * 16 + 8 + (xsize - xoffset)] + 1; - } - return dist + 120; -} - -static WEBP_INLINE int FindMatchLength(const uint32_t* const array1, - const uint32_t* const array2, - const int max_limit) { - int match_len = 0; - while (match_len < max_limit && array1[match_len] == array2[match_len]) { - ++match_len; - } - return match_len; -} - -// ----------------------------------------------------------------------------- -// VP8LBackwardRefs - -void VP8LInitBackwardRefs(VP8LBackwardRefs* const refs) { - if (refs != NULL) { - refs->refs = NULL; - refs->size = 0; - refs->max_size = 0; - } -} - -void VP8LClearBackwardRefs(VP8LBackwardRefs* const refs) { - if (refs != NULL) { - free(refs->refs); - VP8LInitBackwardRefs(refs); - } -} - -int VP8LBackwardRefsAlloc(VP8LBackwardRefs* const refs, int max_size) { - assert(refs != NULL); - refs->size = 0; - refs->max_size = 0; - refs->refs = (PixOrCopy*)WebPSafeMalloc((uint64_t)max_size, - sizeof(*refs->refs)); - if (refs->refs == NULL) return 0; - refs->max_size = max_size; - return 1; -} - -// ----------------------------------------------------------------------------- -// Hash chains - -static WEBP_INLINE uint64_t GetPixPairHash64(const uint32_t* const argb) { - uint64_t key = ((uint64_t)(argb[1]) << 32) | argb[0]; - key = (key * HASH_MULTIPLIER) >> (64 - HASH_BITS); - return key; -} - -static int HashChainInit(HashChain* const p, int size) { - int i; - p->chain_ = (int*)WebPSafeMalloc((uint64_t)size, sizeof(*p->chain_)); - if (p->chain_ == NULL) { - return 0; - } - for (i = 0; i < size; ++i) { - p->chain_[i] = -1; - } - for (i = 0; i < HASH_SIZE; ++i) { - p->hash_to_first_index_[i] = -1; - } - return 1; -} - -static void HashChainDelete(HashChain* const p) { - if (p != NULL) { - free(p->chain_); - free(p); - } -} - -// Insertion of two pixels at a time. -static void HashChainInsert(HashChain* const p, - const uint32_t* const argb, int pos) { - const uint64_t hash_code = GetPixPairHash64(argb); - p->chain_[pos] = p->hash_to_first_index_[hash_code]; - p->hash_to_first_index_[hash_code] = pos; -} - -static void GetParamsForHashChainFindCopy(int quality, int xsize, - int cache_bits, int* window_size, - int* iter_pos, int* iter_limit) { - const int iter_mult = (quality < 27) ? 1 : 1 + ((quality - 27) >> 4); - const int iter_neg = -iter_mult * (quality >> 1); - // Limit the backward-ref window size for lower qualities. - const int max_window_size = (quality > 50) ? WINDOW_SIZE - : (quality > 25) ? (xsize << 8) - : (xsize << 4); - assert(xsize > 0); - *window_size = (max_window_size > WINDOW_SIZE) ? WINDOW_SIZE - : max_window_size; - *iter_pos = 8 + (quality >> 3); - // For lower entropy images, the rigourous search loop in HashChainFindCopy - // can be relaxed. - *iter_limit = (cache_bits > 0) ? iter_neg : iter_neg / 2; -} - -static int HashChainFindCopy(const HashChain* const p, - int base_position, int xsize_signed, - const uint32_t* const argb, int maxlen, - int window_size, int iter_pos, int iter_limit, - int* const distance_ptr, - int* const length_ptr) { - const uint32_t* const argb_start = argb + base_position; - uint64_t best_val = 0; - uint32_t best_length = 1; - uint32_t best_distance = 0; - const uint32_t xsize = (uint32_t)xsize_signed; - const int min_pos = - (base_position > window_size) ? base_position - window_size : 0; - int pos; - assert(xsize > 0); - for (pos = p->hash_to_first_index_[GetPixPairHash64(argb_start)]; - pos >= min_pos; - pos = p->chain_[pos]) { - uint64_t val; - uint32_t curr_length; - uint32_t distance; - if (iter_pos < 0) { - if (iter_pos < iter_limit || best_val >= 0xff0000) { - break; - } - } - --iter_pos; - if (argb[pos + best_length - 1] != argb_start[best_length - 1]) { - continue; - } - curr_length = FindMatchLength(argb + pos, argb_start, maxlen); - if (curr_length < best_length) { - continue; - } - distance = (uint32_t)(base_position - pos); - val = curr_length << 16; - // Favoring 2d locality here gives savings for certain images. - if (distance < 9 * xsize) { - const uint32_t y = distance / xsize; - uint32_t x = distance % xsize; - if (x > (xsize >> 1)) { - x = xsize - x; - } - if (x <= 7) { - val += 9 * 9 + 9 * 9; - val -= y * y + x * x; - } - } - if (best_val < val) { - best_val = val; - best_length = curr_length; - best_distance = distance; - if (curr_length >= MAX_LENGTH) { - break; - } - if ((best_distance == 1 || distance == xsize) && - best_length >= 128) { - break; - } - } - } - *distance_ptr = (int)best_distance; - *length_ptr = best_length; - return (best_length >= MIN_LENGTH); -} - -static WEBP_INLINE void PushBackCopy(VP8LBackwardRefs* const refs, int length) { - int size = refs->size; - while (length >= MAX_LENGTH) { - refs->refs[size++] = PixOrCopyCreateCopy(1, MAX_LENGTH); - length -= MAX_LENGTH; - } - if (length > 0) { - refs->refs[size++] = PixOrCopyCreateCopy(1, length); - } - refs->size = size; -} - -static void BackwardReferencesRle(int xsize, int ysize, - const uint32_t* const argb, - VP8LBackwardRefs* const refs) { - const int pix_count = xsize * ysize; - int match_len = 0; - int i; - refs->size = 0; - PushBackCopy(refs, match_len); // i=0 case - refs->refs[refs->size++] = PixOrCopyCreateLiteral(argb[0]); - for (i = 1; i < pix_count; ++i) { - if (argb[i] == argb[i - 1]) { - ++match_len; - } else { - PushBackCopy(refs, match_len); - match_len = 0; - refs->refs[refs->size++] = PixOrCopyCreateLiteral(argb[i]); - } - } - PushBackCopy(refs, match_len); -} - -static int BackwardReferencesHashChain(int xsize, int ysize, - const uint32_t* const argb, - int cache_bits, int quality, - VP8LBackwardRefs* const refs) { - int i; - int ok = 0; - int cc_init = 0; - const int use_color_cache = (cache_bits > 0); - const int pix_count = xsize * ysize; - HashChain* const hash_chain = (HashChain*)malloc(sizeof(*hash_chain)); - VP8LColorCache hashers; - int window_size = WINDOW_SIZE; - int iter_pos = 1; - int iter_limit = -1; - - if (hash_chain == NULL) return 0; - if (use_color_cache) { - cc_init = VP8LColorCacheInit(&hashers, cache_bits); - if (!cc_init) goto Error; - } - - if (!HashChainInit(hash_chain, pix_count)) goto Error; - - refs->size = 0; - GetParamsForHashChainFindCopy(quality, xsize, cache_bits, - &window_size, &iter_pos, &iter_limit); - for (i = 0; i < pix_count; ) { - // Alternative#1: Code the pixels starting at 'i' using backward reference. - int offset = 0; - int len = 0; - if (i < pix_count - 1) { // FindCopy(i,..) reads pixels at [i] and [i + 1]. - int maxlen = pix_count - i; - if (maxlen > MAX_LENGTH) { - maxlen = MAX_LENGTH; - } - HashChainFindCopy(hash_chain, i, xsize, argb, maxlen, - window_size, iter_pos, iter_limit, - &offset, &len); - } - if (len >= MIN_LENGTH) { - // Alternative#2: Insert the pixel at 'i' as literal, and code the - // pixels starting at 'i + 1' using backward reference. - int offset2 = 0; - int len2 = 0; - int k; - HashChainInsert(hash_chain, &argb[i], i); - if (i < pix_count - 2) { // FindCopy(i+1,..) reads [i + 1] and [i + 2]. - int maxlen = pix_count - (i + 1); - if (maxlen > MAX_LENGTH) { - maxlen = MAX_LENGTH; - } - HashChainFindCopy(hash_chain, i + 1, xsize, argb, maxlen, - window_size, iter_pos, iter_limit, - &offset2, &len2); - if (len2 > len + 1) { - const uint32_t pixel = argb[i]; - // Alternative#2 is a better match. So push pixel at 'i' as literal. - if (use_color_cache && VP8LColorCacheContains(&hashers, pixel)) { - const int ix = VP8LColorCacheGetIndex(&hashers, pixel); - refs->refs[refs->size] = PixOrCopyCreateCacheIdx(ix); - } else { - refs->refs[refs->size] = PixOrCopyCreateLiteral(pixel); - } - ++refs->size; - if (use_color_cache) VP8LColorCacheInsert(&hashers, pixel); - i++; // Backward reference to be done for next pixel. - len = len2; - offset = offset2; - } - } - if (len >= MAX_LENGTH) { - len = MAX_LENGTH - 1; - } - refs->refs[refs->size++] = PixOrCopyCreateCopy(offset, len); - if (use_color_cache) { - for (k = 0; k < len; ++k) { - VP8LColorCacheInsert(&hashers, argb[i + k]); - } - } - // Add to the hash_chain (but cannot add the last pixel). - { - const int last = (len < pix_count - 1 - i) ? len : pix_count - 1 - i; - for (k = 1; k < last; ++k) { - HashChainInsert(hash_chain, &argb[i + k], i + k); - } - } - i += len; - } else { - const uint32_t pixel = argb[i]; - if (use_color_cache && VP8LColorCacheContains(&hashers, pixel)) { - // push pixel as a PixOrCopyCreateCacheIdx pixel - const int ix = VP8LColorCacheGetIndex(&hashers, pixel); - refs->refs[refs->size] = PixOrCopyCreateCacheIdx(ix); - } else { - refs->refs[refs->size] = PixOrCopyCreateLiteral(pixel); - } - ++refs->size; - if (use_color_cache) VP8LColorCacheInsert(&hashers, pixel); - if (i + 1 < pix_count) { - HashChainInsert(hash_chain, &argb[i], i); - } - ++i; - } - } - ok = 1; -Error: - if (cc_init) VP8LColorCacheClear(&hashers); - HashChainDelete(hash_chain); - return ok; -} - -// ----------------------------------------------------------------------------- - -typedef struct { - double alpha_[VALUES_IN_BYTE]; - double red_[VALUES_IN_BYTE]; - double literal_[PIX_OR_COPY_CODES_MAX]; - double blue_[VALUES_IN_BYTE]; - double distance_[NUM_DISTANCE_CODES]; -} CostModel; - -static int BackwardReferencesTraceBackwards( - int xsize, int ysize, int recursive_cost_model, - const uint32_t* const argb, int quality, int cache_bits, - VP8LBackwardRefs* const refs); - -static void ConvertPopulationCountTableToBitEstimates( - int num_symbols, const int population_counts[], double output[]) { - int sum = 0; - int nonzeros = 0; - int i; - for (i = 0; i < num_symbols; ++i) { - sum += population_counts[i]; - if (population_counts[i] > 0) { - ++nonzeros; - } - } - if (nonzeros <= 1) { - memset(output, 0, num_symbols * sizeof(*output)); - } else { - const double logsum = VP8LFastLog2(sum); - for (i = 0; i < num_symbols; ++i) { - output[i] = logsum - VP8LFastLog2(population_counts[i]); - } - } -} - -static int CostModelBuild(CostModel* const m, int xsize, int ysize, - int recursion_level, const uint32_t* const argb, - int quality, int cache_bits) { - int ok = 0; - VP8LHistogram histo; - VP8LBackwardRefs refs; - - if (!VP8LBackwardRefsAlloc(&refs, xsize * ysize)) goto Error; - - if (recursion_level > 0) { - if (!BackwardReferencesTraceBackwards(xsize, ysize, recursion_level - 1, - argb, quality, cache_bits, &refs)) { - goto Error; - } - } else { - if (!BackwardReferencesHashChain(xsize, ysize, argb, cache_bits, quality, - &refs)) { - goto Error; - } - } - VP8LHistogramCreate(&histo, &refs, cache_bits); - ConvertPopulationCountTableToBitEstimates( - VP8LHistogramNumCodes(&histo), histo.literal_, m->literal_); - ConvertPopulationCountTableToBitEstimates( - VALUES_IN_BYTE, histo.red_, m->red_); - ConvertPopulationCountTableToBitEstimates( - VALUES_IN_BYTE, histo.blue_, m->blue_); - ConvertPopulationCountTableToBitEstimates( - VALUES_IN_BYTE, histo.alpha_, m->alpha_); - ConvertPopulationCountTableToBitEstimates( - NUM_DISTANCE_CODES, histo.distance_, m->distance_); - ok = 1; - - Error: - VP8LClearBackwardRefs(&refs); - return ok; -} - -static WEBP_INLINE double GetLiteralCost(const CostModel* const m, uint32_t v) { - return m->alpha_[v >> 24] + - m->red_[(v >> 16) & 0xff] + - m->literal_[(v >> 8) & 0xff] + - m->blue_[v & 0xff]; -} - -static WEBP_INLINE double GetCacheCost(const CostModel* const m, uint32_t idx) { - const int literal_idx = VALUES_IN_BYTE + NUM_LENGTH_CODES + idx; - return m->literal_[literal_idx]; -} - -static WEBP_INLINE double GetLengthCost(const CostModel* const m, - uint32_t length) { - int code, extra_bits_count, extra_bits_value; - PrefixEncode(length, &code, &extra_bits_count, &extra_bits_value); - return m->literal_[VALUES_IN_BYTE + code] + extra_bits_count; -} - -static WEBP_INLINE double GetDistanceCost(const CostModel* const m, - uint32_t distance) { - int code, extra_bits_count, extra_bits_value; - PrefixEncode(distance, &code, &extra_bits_count, &extra_bits_value); - return m->distance_[code] + extra_bits_count; -} - -static int BackwardReferencesHashChainDistanceOnly( - int xsize, int ysize, int recursive_cost_model, const uint32_t* const argb, - int quality, int cache_bits, uint32_t* const dist_array) { - int i; - int ok = 0; - int cc_init = 0; - const int pix_count = xsize * ysize; - const int use_color_cache = (cache_bits > 0); - float* const cost = - (float*)WebPSafeMalloc((uint64_t)pix_count, sizeof(*cost)); - CostModel* cost_model = (CostModel*)malloc(sizeof(*cost_model)); - HashChain* hash_chain = (HashChain*)malloc(sizeof(*hash_chain)); - VP8LColorCache hashers; - const double mul0 = (recursive_cost_model != 0) ? 1.0 : 0.68; - const double mul1 = (recursive_cost_model != 0) ? 1.0 : 0.82; - const int min_distance_code = 2; // TODO(vikasa): tune as function of quality - int window_size = WINDOW_SIZE; - int iter_pos = 1; - int iter_limit = -1; - - if (cost == NULL || cost_model == NULL || hash_chain == NULL) goto Error; - - if (!HashChainInit(hash_chain, pix_count)) goto Error; - - if (use_color_cache) { - cc_init = VP8LColorCacheInit(&hashers, cache_bits); - if (!cc_init) goto Error; - } - - if (!CostModelBuild(cost_model, xsize, ysize, recursive_cost_model, argb, - quality, cache_bits)) { - goto Error; - } - - for (i = 0; i < pix_count; ++i) cost[i] = 1e38f; - - // We loop one pixel at a time, but store all currently best points to - // non-processed locations from this point. - dist_array[0] = 0; - GetParamsForHashChainFindCopy(quality, xsize, cache_bits, - &window_size, &iter_pos, &iter_limit); - for (i = 0; i < pix_count; ++i) { - double prev_cost = 0.0; - int shortmax; - if (i > 0) { - prev_cost = cost[i - 1]; - } - for (shortmax = 0; shortmax < 2; ++shortmax) { - int offset = 0; - int len = 0; - if (i < pix_count - 1) { // FindCopy reads pixels at [i] and [i + 1]. - int maxlen = shortmax ? 2 : MAX_LENGTH; - if (maxlen > pix_count - i) { - maxlen = pix_count - i; - } - HashChainFindCopy(hash_chain, i, xsize, argb, maxlen, - window_size, iter_pos, iter_limit, - &offset, &len); - } - if (len >= MIN_LENGTH) { - const int code = DistanceToPlaneCode(xsize, offset); - const double distance_cost = - prev_cost + GetDistanceCost(cost_model, code); - int k; - for (k = 1; k < len; ++k) { - const double cost_val = distance_cost + GetLengthCost(cost_model, k); - if (cost[i + k] > cost_val) { - cost[i + k] = (float)cost_val; - dist_array[i + k] = k + 1; - } - } - // This if is for speedup only. It roughly doubles the speed, and - // makes compression worse by .1 %. - if (len >= 128 && code <= min_distance_code) { - // Long copy for short distances, let's skip the middle - // lookups for better copies. - // 1) insert the hashes. - if (use_color_cache) { - for (k = 0; k < len; ++k) { - VP8LColorCacheInsert(&hashers, argb[i + k]); - } - } - // 2) Add to the hash_chain (but cannot add the last pixel) - { - const int last = (len + i < pix_count - 1) ? len + i - : pix_count - 1; - for (k = i; k < last; ++k) { - HashChainInsert(hash_chain, &argb[k], k); - } - } - // 3) jump. - i += len - 1; // for loop does ++i, thus -1 here. - goto next_symbol; - } - } - } - if (i < pix_count - 1) { - HashChainInsert(hash_chain, &argb[i], i); - } - { - // inserting a literal pixel - double cost_val = prev_cost; - if (use_color_cache && VP8LColorCacheContains(&hashers, argb[i])) { - const int ix = VP8LColorCacheGetIndex(&hashers, argb[i]); - cost_val += GetCacheCost(cost_model, ix) * mul0; - } else { - cost_val += GetLiteralCost(cost_model, argb[i]) * mul1; - } - if (cost[i] > cost_val) { - cost[i] = (float)cost_val; - dist_array[i] = 1; // only one is inserted. - } - if (use_color_cache) VP8LColorCacheInsert(&hashers, argb[i]); - } - next_symbol: ; - } - // Last pixel still to do, it can only be a single step if not reached - // through cheaper means already. - ok = 1; -Error: - if (cc_init) VP8LColorCacheClear(&hashers); - HashChainDelete(hash_chain); - free(cost_model); - free(cost); - return ok; -} - -// We pack the path at the end of *dist_array and return -// a pointer to this part of the array. Example: -// dist_array = [1x2xx3x2] => packed [1x2x1232], chosen_path = [1232] -static void TraceBackwards(uint32_t* const dist_array, - int dist_array_size, - uint32_t** const chosen_path, - int* const chosen_path_size) { - uint32_t* path = dist_array + dist_array_size; - uint32_t* cur = dist_array + dist_array_size - 1; - while (cur >= dist_array) { - const int k = *cur; - --path; - *path = k; - cur -= k; - } - *chosen_path = path; - *chosen_path_size = (int)(dist_array + dist_array_size - path); -} - -static int BackwardReferencesHashChainFollowChosenPath( - int xsize, int ysize, const uint32_t* const argb, - int quality, int cache_bits, - const uint32_t* const chosen_path, int chosen_path_size, - VP8LBackwardRefs* const refs) { - const int pix_count = xsize * ysize; - const int use_color_cache = (cache_bits > 0); - int size = 0; - int i = 0; - int k; - int ix; - int ok = 0; - int cc_init = 0; - int window_size = WINDOW_SIZE; - int iter_pos = 1; - int iter_limit = -1; - HashChain* hash_chain = (HashChain*)malloc(sizeof(*hash_chain)); - VP8LColorCache hashers; - - if (hash_chain == NULL || !HashChainInit(hash_chain, pix_count)) { - goto Error; - } - if (use_color_cache) { - cc_init = VP8LColorCacheInit(&hashers, cache_bits); - if (!cc_init) goto Error; - } - - refs->size = 0; - GetParamsForHashChainFindCopy(quality, xsize, cache_bits, - &window_size, &iter_pos, &iter_limit); - for (ix = 0; ix < chosen_path_size; ++ix, ++size) { - int offset = 0; - int len = 0; - int maxlen = chosen_path[ix]; - if (maxlen != 1) { - HashChainFindCopy(hash_chain, i, xsize, argb, maxlen, - window_size, iter_pos, iter_limit, - &offset, &len); - assert(len == maxlen); - refs->refs[size] = PixOrCopyCreateCopy(offset, len); - if (use_color_cache) { - for (k = 0; k < len; ++k) { - VP8LColorCacheInsert(&hashers, argb[i + k]); - } - } - { - const int last = (len < pix_count - 1 - i) ? len : pix_count - 1 - i; - for (k = 0; k < last; ++k) { - HashChainInsert(hash_chain, &argb[i + k], i + k); - } - } - i += len; - } else { - if (use_color_cache && VP8LColorCacheContains(&hashers, argb[i])) { - // push pixel as a color cache index - const int idx = VP8LColorCacheGetIndex(&hashers, argb[i]); - refs->refs[size] = PixOrCopyCreateCacheIdx(idx); - } else { - refs->refs[size] = PixOrCopyCreateLiteral(argb[i]); - } - if (use_color_cache) VP8LColorCacheInsert(&hashers, argb[i]); - if (i + 1 < pix_count) { - HashChainInsert(hash_chain, &argb[i], i); - } - ++i; - } - } - assert(size <= refs->max_size); - refs->size = size; - ok = 1; -Error: - if (cc_init) VP8LColorCacheClear(&hashers); - HashChainDelete(hash_chain); - return ok; -} - -// Returns 1 on success. -static int BackwardReferencesTraceBackwards(int xsize, int ysize, - int recursive_cost_model, - const uint32_t* const argb, - int quality, int cache_bits, - VP8LBackwardRefs* const refs) { - int ok = 0; - const int dist_array_size = xsize * ysize; - uint32_t* chosen_path = NULL; - int chosen_path_size = 0; - uint32_t* dist_array = - (uint32_t*)WebPSafeMalloc((uint64_t)dist_array_size, sizeof(*dist_array)); - - if (dist_array == NULL) goto Error; - - if (!BackwardReferencesHashChainDistanceOnly( - xsize, ysize, recursive_cost_model, argb, quality, cache_bits, - dist_array)) { - goto Error; - } - TraceBackwards(dist_array, dist_array_size, &chosen_path, &chosen_path_size); - if (!BackwardReferencesHashChainFollowChosenPath( - xsize, ysize, argb, quality, cache_bits, chosen_path, chosen_path_size, - refs)) { - goto Error; - } - ok = 1; - Error: - free(dist_array); - return ok; -} - -static void BackwardReferences2DLocality(int xsize, - VP8LBackwardRefs* const refs) { - int i; - for (i = 0; i < refs->size; ++i) { - if (PixOrCopyIsCopy(&refs->refs[i])) { - const int dist = refs->refs[i].argb_or_distance; - const int transformed_dist = DistanceToPlaneCode(xsize, dist); - refs->refs[i].argb_or_distance = transformed_dist; - } - } -} - -int VP8LGetBackwardReferences(int width, int height, - const uint32_t* const argb, - int quality, int cache_bits, int use_2d_locality, - VP8LBackwardRefs* const best) { - int ok = 0; - int lz77_is_useful; - VP8LBackwardRefs refs_rle, refs_lz77; - const int num_pix = width * height; - - VP8LBackwardRefsAlloc(&refs_rle, num_pix); - VP8LBackwardRefsAlloc(&refs_lz77, num_pix); - VP8LInitBackwardRefs(best); - if (refs_rle.refs == NULL || refs_lz77.refs == NULL) { - Error1: - VP8LClearBackwardRefs(&refs_rle); - VP8LClearBackwardRefs(&refs_lz77); - goto End; - } - - if (!BackwardReferencesHashChain(width, height, argb, cache_bits, quality, - &refs_lz77)) { - goto End; - } - // Backward Reference using RLE only. - BackwardReferencesRle(width, height, argb, &refs_rle); - - { - double bit_cost_lz77, bit_cost_rle; - VP8LHistogram* const histo = (VP8LHistogram*)malloc(sizeof(*histo)); - if (histo == NULL) goto Error1; - // Evaluate lz77 coding - VP8LHistogramCreate(histo, &refs_lz77, cache_bits); - bit_cost_lz77 = VP8LHistogramEstimateBits(histo); - // Evaluate RLE coding - VP8LHistogramCreate(histo, &refs_rle, cache_bits); - bit_cost_rle = VP8LHistogramEstimateBits(histo); - // Decide if LZ77 is useful. - lz77_is_useful = (bit_cost_lz77 < bit_cost_rle); - free(histo); - } - - // Choose appropriate backward reference. - if (lz77_is_useful) { - // TraceBackwards is costly. Don't execute it at lower quality (q <= 10). - const int try_lz77_trace_backwards = (quality > 10); - *best = refs_lz77; // default guess: lz77 is better - VP8LClearBackwardRefs(&refs_rle); - if (try_lz77_trace_backwards) { - // Set recursion level for large images using a color cache. - const int recursion_level = - (num_pix < 320 * 200) && (cache_bits > 0) ? 1 : 0; - VP8LBackwardRefs refs_trace; - if (!VP8LBackwardRefsAlloc(&refs_trace, num_pix)) { - goto End; - } - if (BackwardReferencesTraceBackwards(width, height, recursion_level, argb, - quality, cache_bits, &refs_trace)) { - VP8LClearBackwardRefs(&refs_lz77); - *best = refs_trace; - } - } - } else { - VP8LClearBackwardRefs(&refs_lz77); - *best = refs_rle; - } - - if (use_2d_locality) BackwardReferences2DLocality(width, best); - - ok = 1; - - End: - if (!ok) { - VP8LClearBackwardRefs(best); - } - return ok; -} - -// Returns 1 on success. -static int ComputeCacheHistogram(const uint32_t* const argb, - int xsize, int ysize, - const VP8LBackwardRefs* const refs, - int cache_bits, - VP8LHistogram* const histo) { - int pixel_index = 0; - int i; - uint32_t k; - VP8LColorCache hashers; - const int use_color_cache = (cache_bits > 0); - int cc_init = 0; - - if (use_color_cache) { - cc_init = VP8LColorCacheInit(&hashers, cache_bits); - if (!cc_init) return 0; - } - - for (i = 0; i < refs->size; ++i) { - const PixOrCopy* const v = &refs->refs[i]; - if (PixOrCopyIsLiteral(v)) { - if (use_color_cache && - VP8LColorCacheContains(&hashers, argb[pixel_index])) { - // push pixel as a cache index - const int ix = VP8LColorCacheGetIndex(&hashers, argb[pixel_index]); - const PixOrCopy token = PixOrCopyCreateCacheIdx(ix); - VP8LHistogramAddSinglePixOrCopy(histo, &token); - } else { - VP8LHistogramAddSinglePixOrCopy(histo, v); - } - } else { - VP8LHistogramAddSinglePixOrCopy(histo, v); - } - if (use_color_cache) { - for (k = 0; k < PixOrCopyLength(v); ++k) { - VP8LColorCacheInsert(&hashers, argb[pixel_index + k]); - } - } - pixel_index += PixOrCopyLength(v); - } - assert(pixel_index == xsize * ysize); - (void)xsize; // xsize is not used in non-debug compilations otherwise. - (void)ysize; // ysize is not used in non-debug compilations otherwise. - if (cc_init) VP8LColorCacheClear(&hashers); - return 1; -} - -// Returns how many bits are to be used for a color cache. -int VP8LCalculateEstimateForCacheSize(const uint32_t* const argb, - int xsize, int ysize, - int* const best_cache_bits) { - int ok = 0; - int cache_bits; - double lowest_entropy = 1e99; - VP8LBackwardRefs refs; - static const double kSmallPenaltyForLargeCache = 4.0; - static const int quality = 30; - if (!VP8LBackwardRefsAlloc(&refs, xsize * ysize) || - !BackwardReferencesHashChain(xsize, ysize, argb, 0, quality, &refs)) { - goto Error; - } - for (cache_bits = 0; cache_bits <= MAX_COLOR_CACHE_BITS; ++cache_bits) { - double cur_entropy; - VP8LHistogram histo; - VP8LHistogramInit(&histo, cache_bits); - ComputeCacheHistogram(argb, xsize, ysize, &refs, cache_bits, &histo); - cur_entropy = VP8LHistogramEstimateBits(&histo) + - kSmallPenaltyForLargeCache * cache_bits; - if (cache_bits == 0 || cur_entropy < lowest_entropy) { - *best_cache_bits = cache_bits; - lowest_entropy = cur_entropy; - } - } - ok = 1; - Error: - VP8LClearBackwardRefs(&refs); - return ok; -} diff --git a/3rdparty/libwebp/enc/backward_references.h b/3rdparty/libwebp/enc/backward_references.h deleted file mode 100644 index b0d18135f7..0000000000 --- a/3rdparty/libwebp/enc/backward_references.h +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Author: Jyrki Alakuijala (jyrki@google.com) -// - -#ifndef WEBP_ENC_BACKWARD_REFERENCES_H_ -#define WEBP_ENC_BACKWARD_REFERENCES_H_ - -#include -#include -#include "../webp/types.h" -#include "../webp/format_constants.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -// The spec allows 11, we use 9 bits to reduce memory consumption in encoding. -// Having 9 instead of 11 only removes about 0.25 % of compression density. -#define MAX_COLOR_CACHE_BITS 9 - -// Max ever number of codes we'll use: -#define PIX_OR_COPY_CODES_MAX \ - (NUM_LITERAL_CODES + NUM_LENGTH_CODES + (1 << MAX_COLOR_CACHE_BITS)) - -// ----------------------------------------------------------------------------- -// PrefixEncode() - -// use GNU builtins where available. -#if defined(__GNUC__) && \ - ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ >= 4) -static WEBP_INLINE int BitsLog2Floor(uint32_t n) { - assert(n != 0); - return 31 ^ __builtin_clz(n); -} -#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) -#include -#pragma intrinsic(_BitScanReverse) - -static WEBP_INLINE int BitsLog2Floor(uint32_t n) { - unsigned long first_set_bit; - assert(n != 0); - _BitScanReverse(&first_set_bit, n); - return first_set_bit; -} -#else -// Returns (int)floor(log2(n)). n must be > 0. -static WEBP_INLINE int BitsLog2Floor(uint32_t n) { - int log = 0; - uint32_t value = n; - int i; - - assert(n != 0); - for (i = 4; i >= 0; --i) { - const int shift = (1 << i); - const uint32_t x = value >> shift; - if (x != 0) { - value = x; - log += shift; - } - } - return log; -} -#endif - -static WEBP_INLINE int VP8LBitsLog2Ceiling(uint32_t n) { - const int log_floor = BitsLog2Floor(n); - if (n == (n & ~(n - 1))) // zero or a power of two. - return log_floor; - else - return log_floor + 1; -} - -// Splitting of distance and length codes into prefixes and -// extra bits. The prefixes are encoded with an entropy code -// while the extra bits are stored just as normal bits. -static WEBP_INLINE void PrefixEncode(int distance, int* const code, - int* const extra_bits_count, - int* const extra_bits_value) { - if (distance > 2) { // Collect the two most significant bits. - const int highest_bit = BitsLog2Floor(--distance); - const int second_highest_bit = (distance >> (highest_bit - 1)) & 1; - *extra_bits_count = highest_bit - 1; - *extra_bits_value = distance & ((1 << *extra_bits_count) - 1); - *code = 2 * highest_bit + second_highest_bit; - } else { - *extra_bits_count = 0; - *extra_bits_value = 0; - *code = (distance == 2) ? 1 : 0; - } -} - -// ----------------------------------------------------------------------------- -// PixOrCopy - -enum Mode { - kLiteral, - kCacheIdx, - kCopy, - kNone -}; - -typedef struct { - // mode as uint8_t to make the memory layout to be exactly 8 bytes. - uint8_t mode; - uint16_t len; - uint32_t argb_or_distance; -} PixOrCopy; - -static WEBP_INLINE PixOrCopy PixOrCopyCreateCopy(uint32_t distance, - uint16_t len) { - PixOrCopy retval; - retval.mode = kCopy; - retval.argb_or_distance = distance; - retval.len = len; - return retval; -} - -static WEBP_INLINE PixOrCopy PixOrCopyCreateCacheIdx(int idx) { - PixOrCopy retval; - assert(idx >= 0); - assert(idx < (1 << MAX_COLOR_CACHE_BITS)); - retval.mode = kCacheIdx; - retval.argb_or_distance = idx; - retval.len = 1; - return retval; -} - -static WEBP_INLINE PixOrCopy PixOrCopyCreateLiteral(uint32_t argb) { - PixOrCopy retval; - retval.mode = kLiteral; - retval.argb_or_distance = argb; - retval.len = 1; - return retval; -} - -static WEBP_INLINE int PixOrCopyIsLiteral(const PixOrCopy* const p) { - return (p->mode == kLiteral); -} - -static WEBP_INLINE int PixOrCopyIsCacheIdx(const PixOrCopy* const p) { - return (p->mode == kCacheIdx); -} - -static WEBP_INLINE int PixOrCopyIsCopy(const PixOrCopy* const p) { - return (p->mode == kCopy); -} - -static WEBP_INLINE uint32_t PixOrCopyLiteral(const PixOrCopy* const p, - int component) { - assert(p->mode == kLiteral); - return (p->argb_or_distance >> (component * 8)) & 0xff; -} - -static WEBP_INLINE uint32_t PixOrCopyLength(const PixOrCopy* const p) { - return p->len; -} - -static WEBP_INLINE uint32_t PixOrCopyArgb(const PixOrCopy* const p) { - assert(p->mode == kLiteral); - return p->argb_or_distance; -} - -static WEBP_INLINE uint32_t PixOrCopyCacheIdx(const PixOrCopy* const p) { - assert(p->mode == kCacheIdx); - assert(p->argb_or_distance < (1U << MAX_COLOR_CACHE_BITS)); - return p->argb_or_distance; -} - -static WEBP_INLINE uint32_t PixOrCopyDistance(const PixOrCopy* const p) { - assert(p->mode == kCopy); - return p->argb_or_distance; -} - -// ----------------------------------------------------------------------------- -// VP8LBackwardRefs - -typedef struct { - PixOrCopy* refs; - int size; // currently used - int max_size; // maximum capacity -} VP8LBackwardRefs; - -// Initialize the object. Must be called first. 'refs' can be NULL. -void VP8LInitBackwardRefs(VP8LBackwardRefs* const refs); - -// Release memory and re-initialize the object. 'refs' can be NULL. -void VP8LClearBackwardRefs(VP8LBackwardRefs* const refs); - -// Allocate 'max_size' references. Returns false in case of memory error. -int VP8LBackwardRefsAlloc(VP8LBackwardRefs* const refs, int max_size); - -// ----------------------------------------------------------------------------- -// Main entry points - -// Evaluates best possible backward references for specified quality. -// Further optimize for 2D locality if use_2d_locality flag is set. -int VP8LGetBackwardReferences(int width, int height, - const uint32_t* const argb, - int quality, int cache_bits, int use_2d_locality, - VP8LBackwardRefs* const best); - -// Produce an estimate for a good color cache size for the image. -int VP8LCalculateEstimateForCacheSize(const uint32_t* const argb, - int xsize, int ysize, - int* const best_cache_bits); - -#if defined(__cplusplus) || defined(c_plusplus) -} -#endif - -#endif // WEBP_ENC_BACKWARD_REFERENCES_H_ diff --git a/3rdparty/libwebp/enc/backward_references_enc.c b/3rdparty/libwebp/enc/backward_references_enc.c new file mode 100644 index 0000000000..7c0559ff1e --- /dev/null +++ b/3rdparty/libwebp/enc/backward_references_enc.c @@ -0,0 +1,1800 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Jyrki Alakuijala (jyrki@google.com) +// + +#include +#include + +#include "./backward_references_enc.h" +#include "./histogram_enc.h" +#include "../dsp/lossless.h" +#include "../dsp/lossless_common.h" +#include "../dsp/dsp.h" +#include "../utils/color_cache_utils.h" +#include "../utils/utils.h" + +#define VALUES_IN_BYTE 256 + +#define MIN_BLOCK_SIZE 256 // minimum block size for backward references + +#define MAX_ENTROPY (1e30f) + +// 1M window (4M bytes) minus 120 special codes for short distances. +#define WINDOW_SIZE_BITS 20 +#define WINDOW_SIZE ((1 << WINDOW_SIZE_BITS) - 120) + +// Minimum number of pixels for which it is cheaper to encode a +// distance + length instead of each pixel as a literal. +#define MIN_LENGTH 4 +// If you change this, you need MAX_LENGTH_BITS + WINDOW_SIZE_BITS <= 32 as it +// is used in VP8LHashChain. +#define MAX_LENGTH_BITS 12 +// We want the max value to be attainable and stored in MAX_LENGTH_BITS bits. +#define MAX_LENGTH ((1 << MAX_LENGTH_BITS) - 1) +#if MAX_LENGTH_BITS + WINDOW_SIZE_BITS > 32 +#error "MAX_LENGTH_BITS + WINDOW_SIZE_BITS > 32" +#endif + +// ----------------------------------------------------------------------------- + +static const uint8_t plane_to_code_lut[128] = { + 96, 73, 55, 39, 23, 13, 5, 1, 255, 255, 255, 255, 255, 255, 255, 255, + 101, 78, 58, 42, 26, 16, 8, 2, 0, 3, 9, 17, 27, 43, 59, 79, + 102, 86, 62, 46, 32, 20, 10, 6, 4, 7, 11, 21, 33, 47, 63, 87, + 105, 90, 70, 52, 37, 28, 18, 14, 12, 15, 19, 29, 38, 53, 71, 91, + 110, 99, 82, 66, 48, 35, 30, 24, 22, 25, 31, 36, 49, 67, 83, 100, + 115, 108, 94, 76, 64, 50, 44, 40, 34, 41, 45, 51, 65, 77, 95, 109, + 118, 113, 103, 92, 80, 68, 60, 56, 54, 57, 61, 69, 81, 93, 104, 114, + 119, 116, 111, 106, 97, 88, 84, 74, 72, 75, 85, 89, 98, 107, 112, 117 +}; + +static int DistanceToPlaneCode(int xsize, int dist) { + const int yoffset = dist / xsize; + const int xoffset = dist - yoffset * xsize; + if (xoffset <= 8 && yoffset < 8) { + return plane_to_code_lut[yoffset * 16 + 8 - xoffset] + 1; + } else if (xoffset > xsize - 8 && yoffset < 7) { + return plane_to_code_lut[(yoffset + 1) * 16 + 8 + (xsize - xoffset)] + 1; + } + return dist + 120; +} + +// Returns the exact index where array1 and array2 are different. For an index +// inferior or equal to best_len_match, the return value just has to be strictly +// inferior to best_len_match. The current behavior is to return 0 if this index +// is best_len_match, and the index itself otherwise. +// If no two elements are the same, it returns max_limit. +static WEBP_INLINE int FindMatchLength(const uint32_t* const array1, + const uint32_t* const array2, + int best_len_match, int max_limit) { + // Before 'expensive' linear match, check if the two arrays match at the + // current best length index. + if (array1[best_len_match] != array2[best_len_match]) return 0; + + return VP8LVectorMismatch(array1, array2, max_limit); +} + +// ----------------------------------------------------------------------------- +// VP8LBackwardRefs + +struct PixOrCopyBlock { + PixOrCopyBlock* next_; // next block (or NULL) + PixOrCopy* start_; // data start + int size_; // currently used size +}; + +static void ClearBackwardRefs(VP8LBackwardRefs* const refs) { + assert(refs != NULL); + if (refs->tail_ != NULL) { + *refs->tail_ = refs->free_blocks_; // recycle all blocks at once + } + refs->free_blocks_ = refs->refs_; + refs->tail_ = &refs->refs_; + refs->last_block_ = NULL; + refs->refs_ = NULL; +} + +void VP8LBackwardRefsClear(VP8LBackwardRefs* const refs) { + assert(refs != NULL); + ClearBackwardRefs(refs); + while (refs->free_blocks_ != NULL) { + PixOrCopyBlock* const next = refs->free_blocks_->next_; + WebPSafeFree(refs->free_blocks_); + refs->free_blocks_ = next; + } +} + +void VP8LBackwardRefsInit(VP8LBackwardRefs* const refs, int block_size) { + assert(refs != NULL); + memset(refs, 0, sizeof(*refs)); + refs->tail_ = &refs->refs_; + refs->block_size_ = + (block_size < MIN_BLOCK_SIZE) ? MIN_BLOCK_SIZE : block_size; +} + +VP8LRefsCursor VP8LRefsCursorInit(const VP8LBackwardRefs* const refs) { + VP8LRefsCursor c; + c.cur_block_ = refs->refs_; + if (refs->refs_ != NULL) { + c.cur_pos = c.cur_block_->start_; + c.last_pos_ = c.cur_pos + c.cur_block_->size_; + } else { + c.cur_pos = NULL; + c.last_pos_ = NULL; + } + return c; +} + +void VP8LRefsCursorNextBlock(VP8LRefsCursor* const c) { + PixOrCopyBlock* const b = c->cur_block_->next_; + c->cur_pos = (b == NULL) ? NULL : b->start_; + c->last_pos_ = (b == NULL) ? NULL : b->start_ + b->size_; + c->cur_block_ = b; +} + +// Create a new block, either from the free list or allocated +static PixOrCopyBlock* BackwardRefsNewBlock(VP8LBackwardRefs* const refs) { + PixOrCopyBlock* b = refs->free_blocks_; + if (b == NULL) { // allocate new memory chunk + const size_t total_size = + sizeof(*b) + refs->block_size_ * sizeof(*b->start_); + b = (PixOrCopyBlock*)WebPSafeMalloc(1ULL, total_size); + if (b == NULL) { + refs->error_ |= 1; + return NULL; + } + b->start_ = (PixOrCopy*)((uint8_t*)b + sizeof(*b)); // not always aligned + } else { // recycle from free-list + refs->free_blocks_ = b->next_; + } + *refs->tail_ = b; + refs->tail_ = &b->next_; + refs->last_block_ = b; + b->next_ = NULL; + b->size_ = 0; + return b; +} + +static WEBP_INLINE void BackwardRefsCursorAdd(VP8LBackwardRefs* const refs, + const PixOrCopy v) { + PixOrCopyBlock* b = refs->last_block_; + if (b == NULL || b->size_ == refs->block_size_) { + b = BackwardRefsNewBlock(refs); + if (b == NULL) return; // refs->error_ is set + } + b->start_[b->size_++] = v; +} + +int VP8LBackwardRefsCopy(const VP8LBackwardRefs* const src, + VP8LBackwardRefs* const dst) { + const PixOrCopyBlock* b = src->refs_; + ClearBackwardRefs(dst); + assert(src->block_size_ == dst->block_size_); + while (b != NULL) { + PixOrCopyBlock* const new_b = BackwardRefsNewBlock(dst); + if (new_b == NULL) return 0; // dst->error_ is set + memcpy(new_b->start_, b->start_, b->size_ * sizeof(*b->start_)); + new_b->size_ = b->size_; + b = b->next_; + } + return 1; +} + +// ----------------------------------------------------------------------------- +// Hash chains + +int VP8LHashChainInit(VP8LHashChain* const p, int size) { + assert(p->size_ == 0); + assert(p->offset_length_ == NULL); + assert(size > 0); + p->offset_length_ = + (uint32_t*)WebPSafeMalloc(size, sizeof(*p->offset_length_)); + if (p->offset_length_ == NULL) return 0; + p->size_ = size; + + return 1; +} + +void VP8LHashChainClear(VP8LHashChain* const p) { + assert(p != NULL); + WebPSafeFree(p->offset_length_); + + p->size_ = 0; + p->offset_length_ = NULL; +} + +// ----------------------------------------------------------------------------- + +#define HASH_MULTIPLIER_HI (0xc6a4a793ULL) +#define HASH_MULTIPLIER_LO (0x5bd1e996ULL) + +static WEBP_INLINE uint32_t GetPixPairHash64(const uint32_t* const argb) { + uint32_t key; + key = (argb[1] * HASH_MULTIPLIER_HI) & 0xffffffffu; + key += (argb[0] * HASH_MULTIPLIER_LO) & 0xffffffffu; + key = key >> (32 - HASH_BITS); + return key; +} + +// Returns the maximum number of hash chain lookups to do for a +// given compression quality. Return value in range [8, 86]. +static int GetMaxItersForQuality(int quality) { + return 8 + (quality * quality) / 128; +} + +static int GetWindowSizeForHashChain(int quality, int xsize) { + const int max_window_size = (quality > 75) ? WINDOW_SIZE + : (quality > 50) ? (xsize << 8) + : (quality > 25) ? (xsize << 6) + : (xsize << 4); + assert(xsize > 0); + return (max_window_size > WINDOW_SIZE) ? WINDOW_SIZE : max_window_size; +} + +static WEBP_INLINE int MaxFindCopyLength(int len) { + return (len < MAX_LENGTH) ? len : MAX_LENGTH; +} + +int VP8LHashChainFill(VP8LHashChain* const p, int quality, + const uint32_t* const argb, int xsize, int ysize, + int low_effort) { + const int size = xsize * ysize; + const int iter_max = GetMaxItersForQuality(quality); + const uint32_t window_size = GetWindowSizeForHashChain(quality, xsize); + int pos; + int argb_comp; + uint32_t base_position; + int32_t* hash_to_first_index; + // Temporarily use the p->offset_length_ as a hash chain. + int32_t* chain = (int32_t*)p->offset_length_; + assert(size > 0); + assert(p->size_ != 0); + assert(p->offset_length_ != NULL); + + if (size <= 2) { + p->offset_length_[0] = p->offset_length_[size - 1] = 0; + return 1; + } + + hash_to_first_index = + (int32_t*)WebPSafeMalloc(HASH_SIZE, sizeof(*hash_to_first_index)); + if (hash_to_first_index == NULL) return 0; + + // Set the int32_t array to -1. + memset(hash_to_first_index, 0xff, HASH_SIZE * sizeof(*hash_to_first_index)); + // Fill the chain linking pixels with the same hash. + argb_comp = (argb[0] == argb[1]); + for (pos = 0; pos < size - 2;) { + uint32_t hash_code; + const int argb_comp_next = (argb[pos + 1] == argb[pos + 2]); + if (argb_comp && argb_comp_next) { + // Consecutive pixels with the same color will share the same hash. + // We therefore use a different hash: the color and its repetition + // length. + uint32_t tmp[2]; + uint32_t len = 1; + tmp[0] = argb[pos]; + // Figure out how far the pixels are the same. + // The last pixel has a different 64 bit hash, as its next pixel does + // not have the same color, so we just need to get to the last pixel equal + // to its follower. + while (pos + (int)len + 2 < size && argb[pos + len + 2] == argb[pos]) { + ++len; + } + if (len > MAX_LENGTH) { + // Skip the pixels that match for distance=1 and length>MAX_LENGTH + // because they are linked to their predecessor and we automatically + // check that in the main for loop below. Skipping means setting no + // predecessor in the chain, hence -1. + memset(chain + pos, 0xff, (len - MAX_LENGTH) * sizeof(*chain)); + pos += len - MAX_LENGTH; + len = MAX_LENGTH; + } + // Process the rest of the hash chain. + while (len) { + tmp[1] = len--; + hash_code = GetPixPairHash64(tmp); + chain[pos] = hash_to_first_index[hash_code]; + hash_to_first_index[hash_code] = pos++; + } + argb_comp = 0; + } else { + // Just move one pixel forward. + hash_code = GetPixPairHash64(argb + pos); + chain[pos] = hash_to_first_index[hash_code]; + hash_to_first_index[hash_code] = pos++; + argb_comp = argb_comp_next; + } + } + // Process the penultimate pixel. + chain[pos] = hash_to_first_index[GetPixPairHash64(argb + pos)]; + + WebPSafeFree(hash_to_first_index); + + // Find the best match interval at each pixel, defined by an offset to the + // pixel and a length. The right-most pixel cannot match anything to the right + // (hence a best length of 0) and the left-most pixel nothing to the left + // (hence an offset of 0). + assert(size > 2); + p->offset_length_[0] = p->offset_length_[size - 1] = 0; + for (base_position = size - 2; base_position > 0;) { + const int max_len = MaxFindCopyLength(size - 1 - base_position); + const uint32_t* const argb_start = argb + base_position; + int iter = iter_max; + int best_length = 0; + uint32_t best_distance = 0; + uint32_t best_argb; + const int min_pos = + (base_position > window_size) ? base_position - window_size : 0; + const int length_max = (max_len < 256) ? max_len : 256; + uint32_t max_base_position; + + pos = chain[base_position]; + if (!low_effort) { + int curr_length; + // Heuristic: use the comparison with the above line as an initialization. + if (base_position >= (uint32_t)xsize) { + curr_length = FindMatchLength(argb_start - xsize, argb_start, + best_length, max_len); + if (curr_length > best_length) { + best_length = curr_length; + best_distance = xsize; + } + --iter; + } + // Heuristic: compare to the previous pixel. + curr_length = + FindMatchLength(argb_start - 1, argb_start, best_length, max_len); + if (curr_length > best_length) { + best_length = curr_length; + best_distance = 1; + } + --iter; + // Skip the for loop if we already have the maximum. + if (best_length == MAX_LENGTH) pos = min_pos - 1; + } + best_argb = argb_start[best_length]; + + for (; pos >= min_pos && --iter; pos = chain[pos]) { + int curr_length; + assert(base_position > (uint32_t)pos); + + if (argb[pos + best_length] != best_argb) continue; + + curr_length = VP8LVectorMismatch(argb + pos, argb_start, max_len); + if (best_length < curr_length) { + best_length = curr_length; + best_distance = base_position - pos; + best_argb = argb_start[best_length]; + // Stop if we have reached a good enough length. + if (best_length >= length_max) break; + } + } + // We have the best match but in case the two intervals continue matching + // to the left, we have the best matches for the left-extended pixels. + max_base_position = base_position; + while (1) { + assert(best_length <= MAX_LENGTH); + assert(best_distance <= WINDOW_SIZE); + p->offset_length_[base_position] = + (best_distance << MAX_LENGTH_BITS) | (uint32_t)best_length; + --base_position; + // Stop if we don't have a match or if we are out of bounds. + if (best_distance == 0 || base_position == 0) break; + // Stop if we cannot extend the matching intervals to the left. + if (base_position < best_distance || + argb[base_position - best_distance] != argb[base_position]) { + break; + } + // Stop if we are matching at its limit because there could be a closer + // matching interval with the same maximum length. Then again, if the + // matching interval is as close as possible (best_distance == 1), we will + // never find anything better so let's continue. + if (best_length == MAX_LENGTH && best_distance != 1 && + base_position + MAX_LENGTH < max_base_position) { + break; + } + if (best_length < MAX_LENGTH) { + ++best_length; + max_base_position = base_position; + } + } + } + return 1; +} + +static WEBP_INLINE int HashChainFindOffset(const VP8LHashChain* const p, + const int base_position) { + return p->offset_length_[base_position] >> MAX_LENGTH_BITS; +} + +static WEBP_INLINE int HashChainFindLength(const VP8LHashChain* const p, + const int base_position) { + return p->offset_length_[base_position] & ((1U << MAX_LENGTH_BITS) - 1); +} + +static WEBP_INLINE void HashChainFindCopy(const VP8LHashChain* const p, + int base_position, + int* const offset_ptr, + int* const length_ptr) { + *offset_ptr = HashChainFindOffset(p, base_position); + *length_ptr = HashChainFindLength(p, base_position); +} + +static WEBP_INLINE void AddSingleLiteral(uint32_t pixel, int use_color_cache, + VP8LColorCache* const hashers, + VP8LBackwardRefs* const refs) { + PixOrCopy v; + if (use_color_cache) { + const uint32_t key = VP8LColorCacheGetIndex(hashers, pixel); + if (VP8LColorCacheLookup(hashers, key) == pixel) { + v = PixOrCopyCreateCacheIdx(key); + } else { + v = PixOrCopyCreateLiteral(pixel); + VP8LColorCacheSet(hashers, key, pixel); + } + } else { + v = PixOrCopyCreateLiteral(pixel); + } + BackwardRefsCursorAdd(refs, v); +} + +static int BackwardReferencesRle(int xsize, int ysize, + const uint32_t* const argb, + int cache_bits, VP8LBackwardRefs* const refs) { + const int pix_count = xsize * ysize; + int i, k; + const int use_color_cache = (cache_bits > 0); + VP8LColorCache hashers; + + if (use_color_cache && !VP8LColorCacheInit(&hashers, cache_bits)) { + return 0; + } + ClearBackwardRefs(refs); + // Add first pixel as literal. + AddSingleLiteral(argb[0], use_color_cache, &hashers, refs); + i = 1; + while (i < pix_count) { + const int max_len = MaxFindCopyLength(pix_count - i); + const int rle_len = FindMatchLength(argb + i, argb + i - 1, 0, max_len); + const int prev_row_len = (i < xsize) ? 0 : + FindMatchLength(argb + i, argb + i - xsize, 0, max_len); + if (rle_len >= prev_row_len && rle_len >= MIN_LENGTH) { + BackwardRefsCursorAdd(refs, PixOrCopyCreateCopy(1, rle_len)); + // We don't need to update the color cache here since it is always the + // same pixel being copied, and that does not change the color cache + // state. + i += rle_len; + } else if (prev_row_len >= MIN_LENGTH) { + BackwardRefsCursorAdd(refs, PixOrCopyCreateCopy(xsize, prev_row_len)); + if (use_color_cache) { + for (k = 0; k < prev_row_len; ++k) { + VP8LColorCacheInsert(&hashers, argb[i + k]); + } + } + i += prev_row_len; + } else { + AddSingleLiteral(argb[i], use_color_cache, &hashers, refs); + i++; + } + } + if (use_color_cache) VP8LColorCacheClear(&hashers); + return !refs->error_; +} + +static int BackwardReferencesLz77(int xsize, int ysize, + const uint32_t* const argb, int cache_bits, + const VP8LHashChain* const hash_chain, + VP8LBackwardRefs* const refs) { + int i; + int i_last_check = -1; + int ok = 0; + int cc_init = 0; + const int use_color_cache = (cache_bits > 0); + const int pix_count = xsize * ysize; + VP8LColorCache hashers; + + if (use_color_cache) { + cc_init = VP8LColorCacheInit(&hashers, cache_bits); + if (!cc_init) goto Error; + } + ClearBackwardRefs(refs); + for (i = 0; i < pix_count;) { + // Alternative#1: Code the pixels starting at 'i' using backward reference. + int offset = 0; + int len = 0; + int j; + HashChainFindCopy(hash_chain, i, &offset, &len); + if (len >= MIN_LENGTH) { + const int len_ini = len; + int max_reach = 0; + assert(i + len < pix_count); + // Only start from what we have not checked already. + i_last_check = (i > i_last_check) ? i : i_last_check; + // We know the best match for the current pixel but we try to find the + // best matches for the current pixel AND the next one combined. + // The naive method would use the intervals: + // [i,i+len) + [i+len, length of best match at i+len) + // while we check if we can use: + // [i,j) (where j<=i+len) + [j, length of best match at j) + for (j = i_last_check + 1; j <= i + len_ini; ++j) { + const int len_j = HashChainFindLength(hash_chain, j); + const int reach = + j + (len_j >= MIN_LENGTH ? len_j : 1); // 1 for single literal. + if (reach > max_reach) { + len = j - i; + max_reach = reach; + } + } + } else { + len = 1; + } + // Go with literal or backward reference. + assert(len > 0); + if (len == 1) { + AddSingleLiteral(argb[i], use_color_cache, &hashers, refs); + } else { + BackwardRefsCursorAdd(refs, PixOrCopyCreateCopy(offset, len)); + if (use_color_cache) { + for (j = i; j < i + len; ++j) VP8LColorCacheInsert(&hashers, argb[j]); + } + } + i += len; + } + + ok = !refs->error_; + Error: + if (cc_init) VP8LColorCacheClear(&hashers); + return ok; +} + +// ----------------------------------------------------------------------------- + +typedef struct { + double alpha_[VALUES_IN_BYTE]; + double red_[VALUES_IN_BYTE]; + double blue_[VALUES_IN_BYTE]; + double distance_[NUM_DISTANCE_CODES]; + double* literal_; +} CostModel; + +static int BackwardReferencesTraceBackwards( + int xsize, int ysize, const uint32_t* const argb, int quality, + int cache_bits, const VP8LHashChain* const hash_chain, + VP8LBackwardRefs* const refs); + +static void ConvertPopulationCountTableToBitEstimates( + int num_symbols, const uint32_t population_counts[], double output[]) { + uint32_t sum = 0; + int nonzeros = 0; + int i; + for (i = 0; i < num_symbols; ++i) { + sum += population_counts[i]; + if (population_counts[i] > 0) { + ++nonzeros; + } + } + if (nonzeros <= 1) { + memset(output, 0, num_symbols * sizeof(*output)); + } else { + const double logsum = VP8LFastLog2(sum); + for (i = 0; i < num_symbols; ++i) { + output[i] = logsum - VP8LFastLog2(population_counts[i]); + } + } +} + +static int CostModelBuild(CostModel* const m, int cache_bits, + VP8LBackwardRefs* const refs) { + int ok = 0; + VP8LHistogram* const histo = VP8LAllocateHistogram(cache_bits); + if (histo == NULL) goto Error; + + VP8LHistogramCreate(histo, refs, cache_bits); + + ConvertPopulationCountTableToBitEstimates( + VP8LHistogramNumCodes(histo->palette_code_bits_), + histo->literal_, m->literal_); + ConvertPopulationCountTableToBitEstimates( + VALUES_IN_BYTE, histo->red_, m->red_); + ConvertPopulationCountTableToBitEstimates( + VALUES_IN_BYTE, histo->blue_, m->blue_); + ConvertPopulationCountTableToBitEstimates( + VALUES_IN_BYTE, histo->alpha_, m->alpha_); + ConvertPopulationCountTableToBitEstimates( + NUM_DISTANCE_CODES, histo->distance_, m->distance_); + ok = 1; + + Error: + VP8LFreeHistogram(histo); + return ok; +} + +static WEBP_INLINE double GetLiteralCost(const CostModel* const m, uint32_t v) { + return m->alpha_[v >> 24] + + m->red_[(v >> 16) & 0xff] + + m->literal_[(v >> 8) & 0xff] + + m->blue_[v & 0xff]; +} + +static WEBP_INLINE double GetCacheCost(const CostModel* const m, uint32_t idx) { + const int literal_idx = VALUES_IN_BYTE + NUM_LENGTH_CODES + idx; + return m->literal_[literal_idx]; +} + +static WEBP_INLINE double GetLengthCost(const CostModel* const m, + uint32_t length) { + int code, extra_bits; + VP8LPrefixEncodeBits(length, &code, &extra_bits); + return m->literal_[VALUES_IN_BYTE + code] + extra_bits; +} + +static WEBP_INLINE double GetDistanceCost(const CostModel* const m, + uint32_t distance) { + int code, extra_bits; + VP8LPrefixEncodeBits(distance, &code, &extra_bits); + return m->distance_[code] + extra_bits; +} + +static void AddSingleLiteralWithCostModel(const uint32_t* const argb, + VP8LColorCache* const hashers, + const CostModel* const cost_model, + int idx, int use_color_cache, + double prev_cost, float* const cost, + uint16_t* const dist_array) { + double cost_val = prev_cost; + const uint32_t color = argb[0]; + const int ix = use_color_cache ? VP8LColorCacheContains(hashers, color) : -1; + if (ix >= 0) { + // use_color_cache is true and hashers contains color + const double mul0 = 0.68; + cost_val += GetCacheCost(cost_model, ix) * mul0; + } else { + const double mul1 = 0.82; + if (use_color_cache) VP8LColorCacheInsert(hashers, color); + cost_val += GetLiteralCost(cost_model, color) * mul1; + } + if (cost[idx] > cost_val) { + cost[idx] = (float)cost_val; + dist_array[idx] = 1; // only one is inserted. + } +} + +// ----------------------------------------------------------------------------- +// CostManager and interval handling + +// Empirical value to avoid high memory consumption but good for performance. +#define COST_CACHE_INTERVAL_SIZE_MAX 100 + +// To perform backward reference every pixel at index index_ is considered and +// the cost for the MAX_LENGTH following pixels computed. Those following pixels +// at index index_ + k (k from 0 to MAX_LENGTH) have a cost of: +// distance_cost_ at index_ + GetLengthCost(cost_model, k) +// (named cost) (named cached cost) +// and the minimum value is kept. GetLengthCost(cost_model, k) is cached in an +// array of size MAX_LENGTH. +// Instead of performing MAX_LENGTH comparisons per pixel, we keep track of the +// minimal values using intervals, for which lower_ and upper_ bounds are kept. +// An interval is defined by the index_ of the pixel that generated it and +// is only useful in a range of indices from start_ to end_ (exclusive), i.e. +// it contains the minimum value for pixels between start_ and end_. +// Intervals are stored in a linked list and ordered by start_. When a new +// interval has a better minimum, old intervals are split or removed. +typedef struct CostInterval CostInterval; +struct CostInterval { + double lower_; + double upper_; + int start_; + int end_; + double distance_cost_; + int index_; + CostInterval* previous_; + CostInterval* next_; +}; + +// The GetLengthCost(cost_model, k) part of the costs is also bounded for +// efficiency in a set of intervals of a different type. +// If those intervals are small enough, they are not used for comparison and +// written into the costs right away. +typedef struct { + double lower_; // Lower bound of the interval. + double upper_; // Upper bound of the interval. + int start_; + int end_; // Exclusive. + int do_write_; // If !=0, the interval is saved to cost instead of being kept + // for comparison. +} CostCacheInterval; + +// This structure is in charge of managing intervals and costs. +// It caches the different CostCacheInterval, caches the different +// GetLengthCost(cost_model, k) in cost_cache_ and the CostInterval's (whose +// count_ is limited by COST_CACHE_INTERVAL_SIZE_MAX). +#define COST_MANAGER_MAX_FREE_LIST 10 +typedef struct { + CostInterval* head_; + int count_; // The number of stored intervals. + CostCacheInterval* cache_intervals_; + size_t cache_intervals_size_; + double cost_cache_[MAX_LENGTH]; // Contains the GetLengthCost(cost_model, k). + double min_cost_cache_; // The minimum value in cost_cache_[1:]. + double max_cost_cache_; // The maximum value in cost_cache_[1:]. + float* costs_; + uint16_t* dist_array_; + // Most of the time, we only need few intervals -> use a free-list, to avoid + // fragmentation with small allocs in most common cases. + CostInterval intervals_[COST_MANAGER_MAX_FREE_LIST]; + CostInterval* free_intervals_; + // These are regularly malloc'd remains. This list can't grow larger than than + // size COST_CACHE_INTERVAL_SIZE_MAX - COST_MANAGER_MAX_FREE_LIST, note. + CostInterval* recycled_intervals_; + // Buffer used in BackwardReferencesHashChainDistanceOnly to store the ends + // of the intervals that can have impacted the cost at a pixel. + int* interval_ends_; + int interval_ends_size_; +} CostManager; + +static int IsCostCacheIntervalWritable(int start, int end) { + // 100 is the length for which we consider an interval for comparison, and not + // for writing. + // The first intervals are very small and go in increasing size. This constant + // helps merging them into one big interval (up to index 150/200 usually from + // which intervals start getting much bigger). + // This value is empirical. + return (end - start + 1 < 100); +} + +static void CostIntervalAddToFreeList(CostManager* const manager, + CostInterval* const interval) { + interval->next_ = manager->free_intervals_; + manager->free_intervals_ = interval; +} + +static int CostIntervalIsInFreeList(const CostManager* const manager, + const CostInterval* const interval) { + return (interval >= &manager->intervals_[0] && + interval <= &manager->intervals_[COST_MANAGER_MAX_FREE_LIST - 1]); +} + +static void CostManagerInitFreeList(CostManager* const manager) { + int i; + manager->free_intervals_ = NULL; + for (i = 0; i < COST_MANAGER_MAX_FREE_LIST; ++i) { + CostIntervalAddToFreeList(manager, &manager->intervals_[i]); + } +} + +static void DeleteIntervalList(CostManager* const manager, + const CostInterval* interval) { + while (interval != NULL) { + const CostInterval* const next = interval->next_; + if (!CostIntervalIsInFreeList(manager, interval)) { + WebPSafeFree((void*)interval); + } // else: do nothing + interval = next; + } +} + +static void CostManagerClear(CostManager* const manager) { + if (manager == NULL) return; + + WebPSafeFree(manager->costs_); + WebPSafeFree(manager->cache_intervals_); + WebPSafeFree(manager->interval_ends_); + + // Clear the interval lists. + DeleteIntervalList(manager, manager->head_); + manager->head_ = NULL; + DeleteIntervalList(manager, manager->recycled_intervals_); + manager->recycled_intervals_ = NULL; + + // Reset pointers, count_ and cache_intervals_size_. + memset(manager, 0, sizeof(*manager)); + CostManagerInitFreeList(manager); +} + +static int CostManagerInit(CostManager* const manager, + uint16_t* const dist_array, int pix_count, + const CostModel* const cost_model) { + int i; + const int cost_cache_size = (pix_count > MAX_LENGTH) ? MAX_LENGTH : pix_count; + // This constant is tied to the cost_model we use. + // Empirically, differences between intervals is usually of more than 1. + const double min_cost_diff = 0.1; + + manager->costs_ = NULL; + manager->cache_intervals_ = NULL; + manager->interval_ends_ = NULL; + manager->head_ = NULL; + manager->recycled_intervals_ = NULL; + manager->count_ = 0; + manager->dist_array_ = dist_array; + CostManagerInitFreeList(manager); + + // Fill in the cost_cache_. + manager->cache_intervals_size_ = 1; + manager->cost_cache_[0] = 0; + for (i = 1; i < cost_cache_size; ++i) { + manager->cost_cache_[i] = GetLengthCost(cost_model, i); + // Get an approximation of the number of bound intervals. + if (fabs(manager->cost_cache_[i] - manager->cost_cache_[i - 1]) > + min_cost_diff) { + ++manager->cache_intervals_size_; + } + // Compute the minimum of cost_cache_. + if (i == 1) { + manager->min_cost_cache_ = manager->cost_cache_[1]; + manager->max_cost_cache_ = manager->cost_cache_[1]; + } else if (manager->cost_cache_[i] < manager->min_cost_cache_) { + manager->min_cost_cache_ = manager->cost_cache_[i]; + } else if (manager->cost_cache_[i] > manager->max_cost_cache_) { + manager->max_cost_cache_ = manager->cost_cache_[i]; + } + } + + // With the current cost models, we have 15 intervals, so we are safe by + // setting a maximum of COST_CACHE_INTERVAL_SIZE_MAX. + if (manager->cache_intervals_size_ > COST_CACHE_INTERVAL_SIZE_MAX) { + manager->cache_intervals_size_ = COST_CACHE_INTERVAL_SIZE_MAX; + } + manager->cache_intervals_ = (CostCacheInterval*)WebPSafeMalloc( + manager->cache_intervals_size_, sizeof(*manager->cache_intervals_)); + if (manager->cache_intervals_ == NULL) { + CostManagerClear(manager); + return 0; + } + + // Fill in the cache_intervals_. + { + double cost_prev = -1e38f; // unprobably low initial value + CostCacheInterval* prev = NULL; + CostCacheInterval* cur = manager->cache_intervals_; + const CostCacheInterval* const end = + manager->cache_intervals_ + manager->cache_intervals_size_; + + // Consecutive values in cost_cache_ are compared and if a big enough + // difference is found, a new interval is created and bounded. + for (i = 0; i < cost_cache_size; ++i) { + const double cost_val = manager->cost_cache_[i]; + if (i == 0 || + (fabs(cost_val - cost_prev) > min_cost_diff && cur + 1 < end)) { + if (i > 1) { + const int is_writable = + IsCostCacheIntervalWritable(cur->start_, cur->end_); + // Merge with the previous interval if both are writable. + if (is_writable && cur != manager->cache_intervals_ && + prev->do_write_) { + // Update the previous interval. + prev->end_ = cur->end_; + if (cur->lower_ < prev->lower_) { + prev->lower_ = cur->lower_; + } else if (cur->upper_ > prev->upper_) { + prev->upper_ = cur->upper_; + } + } else { + cur->do_write_ = is_writable; + prev = cur; + ++cur; + } + } + // Initialize an interval. + cur->start_ = i; + cur->do_write_ = 0; + cur->lower_ = cost_val; + cur->upper_ = cost_val; + } else { + // Update the current interval bounds. + if (cost_val < cur->lower_) { + cur->lower_ = cost_val; + } else if (cost_val > cur->upper_) { + cur->upper_ = cost_val; + } + } + cur->end_ = i + 1; + cost_prev = cost_val; + } + manager->cache_intervals_size_ = cur + 1 - manager->cache_intervals_; + } + + manager->costs_ = (float*)WebPSafeMalloc(pix_count, sizeof(*manager->costs_)); + if (manager->costs_ == NULL) { + CostManagerClear(manager); + return 0; + } + // Set the initial costs_ high for every pixel as we will keep the minimum. + for (i = 0; i < pix_count; ++i) manager->costs_[i] = 1e38f; + + // The cost at pixel is influenced by the cost intervals from previous pixels. + // Let us take the specific case where the offset is the same (which actually + // happens a lot in case of uniform regions). + // pixel i contributes to j>i a cost of: offset cost + cost_cache_[j-i] + // pixel i+1 contributes to j>i a cost of: 2*offset cost + cost_cache_[j-i-1] + // pixel i+2 contributes to j>i a cost of: 3*offset cost + cost_cache_[j-i-2] + // and so on. + // A pixel i influences the following length(j) < MAX_LENGTH pixels. What is + // the value of j such that pixel i + j cannot influence any of those pixels? + // This value is such that: + // max of cost_cache_ < j*offset cost + min of cost_cache_ + // (pixel i + j 's cost cannot beat the worst cost given by pixel i). + // This value will be used to optimize the cost computation in + // BackwardReferencesHashChainDistanceOnly. + { + // The offset cost is computed in GetDistanceCost and has a minimum value of + // the minimum in cost_model->distance_. The case where the offset cost is 0 + // will be dealt with differently later so we are only interested in the + // minimum non-zero offset cost. + double offset_cost_min = 0.; + int size; + for (i = 0; i < NUM_DISTANCE_CODES; ++i) { + if (cost_model->distance_[i] != 0) { + if (offset_cost_min == 0.) { + offset_cost_min = cost_model->distance_[i]; + } else if (cost_model->distance_[i] < offset_cost_min) { + offset_cost_min = cost_model->distance_[i]; + } + } + } + // In case all the cost_model->distance_ is 0, the next non-zero cost we + // can have is from the extra bit in GetDistanceCost, hence 1. + if (offset_cost_min < 1.) offset_cost_min = 1.; + + size = 1 + (int)ceil((manager->max_cost_cache_ - manager->min_cost_cache_) / + offset_cost_min); + // Empirically, we usually end up with a value below 100. + if (size > MAX_LENGTH) size = MAX_LENGTH; + + manager->interval_ends_ = + (int*)WebPSafeMalloc(size, sizeof(*manager->interval_ends_)); + if (manager->interval_ends_ == NULL) { + CostManagerClear(manager); + return 0; + } + manager->interval_ends_size_ = size; + } + + return 1; +} + +// Given the distance_cost for pixel 'index', update the cost at pixel 'i' if it +// is smaller than the previously computed value. +static WEBP_INLINE void UpdateCost(CostManager* const manager, int i, int index, + double distance_cost) { + int k = i - index; + double cost_tmp; + assert(k >= 0 && k < MAX_LENGTH); + cost_tmp = distance_cost + manager->cost_cache_[k]; + + if (manager->costs_[i] > cost_tmp) { + manager->costs_[i] = (float)cost_tmp; + manager->dist_array_[i] = k + 1; + } +} + +// Given the distance_cost for pixel 'index', update the cost for all the pixels +// between 'start' and 'end' excluded. +static WEBP_INLINE void UpdateCostPerInterval(CostManager* const manager, + int start, int end, int index, + double distance_cost) { + int i; + for (i = start; i < end; ++i) UpdateCost(manager, i, index, distance_cost); +} + +// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'. +static WEBP_INLINE void ConnectIntervals(CostManager* const manager, + CostInterval* const prev, + CostInterval* const next) { + if (prev != NULL) { + prev->next_ = next; + } else { + manager->head_ = next; + } + + if (next != NULL) next->previous_ = prev; +} + +// Pop an interval in the manager. +static WEBP_INLINE void PopInterval(CostManager* const manager, + CostInterval* const interval) { + CostInterval* const next = interval->next_; + + if (interval == NULL) return; + + ConnectIntervals(manager, interval->previous_, next); + if (CostIntervalIsInFreeList(manager, interval)) { + CostIntervalAddToFreeList(manager, interval); + } else { // recycle regularly malloc'd intervals too + interval->next_ = manager->recycled_intervals_; + manager->recycled_intervals_ = interval; + } + --manager->count_; + assert(manager->count_ >= 0); +} + +// Update the cost at index i by going over all the stored intervals that +// overlap with i. +static WEBP_INLINE void UpdateCostPerIndex(CostManager* const manager, int i) { + CostInterval* current = manager->head_; + + while (current != NULL && current->start_ <= i) { + if (current->end_ <= i) { + // We have an outdated interval, remove it. + CostInterval* next = current->next_; + PopInterval(manager, current); + current = next; + } else { + UpdateCost(manager, i, current->index_, current->distance_cost_); + current = current->next_; + } + } +} + +// Given a current orphan interval and its previous interval, before +// it was orphaned (which can be NULL), set it at the right place in the list +// of intervals using the start_ ordering and the previous interval as a hint. +static WEBP_INLINE void PositionOrphanInterval(CostManager* const manager, + CostInterval* const current, + CostInterval* previous) { + assert(current != NULL); + + if (previous == NULL) previous = manager->head_; + while (previous != NULL && current->start_ < previous->start_) { + previous = previous->previous_; + } + while (previous != NULL && previous->next_ != NULL && + previous->next_->start_ < current->start_) { + previous = previous->next_; + } + + if (previous != NULL) { + ConnectIntervals(manager, current, previous->next_); + } else { + ConnectIntervals(manager, current, manager->head_); + } + ConnectIntervals(manager, previous, current); +} + +// Insert an interval in the list contained in the manager by starting at +// interval_in as a hint. The intervals are sorted by start_ value. +static WEBP_INLINE void InsertInterval(CostManager* const manager, + CostInterval* const interval_in, + double distance_cost, double lower, + double upper, int index, int start, + int end) { + CostInterval* interval_new; + + if (IsCostCacheIntervalWritable(start, end) || + manager->count_ >= COST_CACHE_INTERVAL_SIZE_MAX) { + // Write down the interval if it is too small. + UpdateCostPerInterval(manager, start, end, index, distance_cost); + return; + } + if (manager->free_intervals_ != NULL) { + interval_new = manager->free_intervals_; + manager->free_intervals_ = interval_new->next_; + } else if (manager->recycled_intervals_ != NULL) { + interval_new = manager->recycled_intervals_; + manager->recycled_intervals_ = interval_new->next_; + } else { // malloc for good + interval_new = (CostInterval*)WebPSafeMalloc(1, sizeof(*interval_new)); + if (interval_new == NULL) { + // Write down the interval if we cannot create it. + UpdateCostPerInterval(manager, start, end, index, distance_cost); + return; + } + } + + interval_new->distance_cost_ = distance_cost; + interval_new->lower_ = lower; + interval_new->upper_ = upper; + interval_new->index_ = index; + interval_new->start_ = start; + interval_new->end_ = end; + PositionOrphanInterval(manager, interval_new, interval_in); + + ++manager->count_; +} + +// When an interval has its start_ or end_ modified, it needs to be +// repositioned in the linked list. +static WEBP_INLINE void RepositionInterval(CostManager* const manager, + CostInterval* const interval) { + if (IsCostCacheIntervalWritable(interval->start_, interval->end_)) { + // Maybe interval has been resized and is small enough to be removed. + UpdateCostPerInterval(manager, interval->start_, interval->end_, + interval->index_, interval->distance_cost_); + PopInterval(manager, interval); + return; + } + + // Early exit if interval is at the right spot. + if ((interval->previous_ == NULL || + interval->previous_->start_ <= interval->start_) && + (interval->next_ == NULL || + interval->start_ <= interval->next_->start_)) { + return; + } + + ConnectIntervals(manager, interval->previous_, interval->next_); + PositionOrphanInterval(manager, interval, interval->previous_); +} + +// Given a new cost interval defined by its start at index, its last value and +// distance_cost, add its contributions to the previous intervals and costs. +// If handling the interval or one of its subintervals becomes to heavy, its +// contribution is added to the costs right away. +static WEBP_INLINE void PushInterval(CostManager* const manager, + double distance_cost, int index, + int last) { + size_t i; + CostInterval* interval = manager->head_; + CostInterval* interval_next; + const CostCacheInterval* const cost_cache_intervals = + manager->cache_intervals_; + + for (i = 0; i < manager->cache_intervals_size_ && + cost_cache_intervals[i].start_ < last; + ++i) { + // Define the intersection of the ith interval with the new one. + int start = index + cost_cache_intervals[i].start_; + const int end = index + (cost_cache_intervals[i].end_ > last + ? last + : cost_cache_intervals[i].end_); + const double lower_in = cost_cache_intervals[i].lower_; + const double upper_in = cost_cache_intervals[i].upper_; + const double lower_full_in = distance_cost + lower_in; + const double upper_full_in = distance_cost + upper_in; + + if (cost_cache_intervals[i].do_write_) { + UpdateCostPerInterval(manager, start, end, index, distance_cost); + continue; + } + + for (; interval != NULL && interval->start_ < end && start < end; + interval = interval_next) { + const double lower_full_interval = + interval->distance_cost_ + interval->lower_; + const double upper_full_interval = + interval->distance_cost_ + interval->upper_; + + interval_next = interval->next_; + + // Make sure we have some overlap + if (start >= interval->end_) continue; + + if (lower_full_in >= upper_full_interval) { + // When intervals are represented, the lower, the better. + // [**********************************************************] + // start end + // [----------------------------------] + // interval->start_ interval->end_ + // If we are worse than what we already have, add whatever we have so + // far up to interval. + const int start_new = interval->end_; + InsertInterval(manager, interval, distance_cost, lower_in, upper_in, + index, start, interval->start_); + start = start_new; + continue; + } + + // We know the two intervals intersect. + if (upper_full_in >= lower_full_interval) { + // There is no clear cut on which is best, so let's keep both. + // [*********[*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*]***********] + // start interval->start_ interval->end_ end + // OR + // [*********[*-*-*-*-*-*-*-*-*-*-*-]----------------------] + // start interval->start_ end interval->end_ + const int end_new = (interval->end_ <= end) ? interval->end_ : end; + InsertInterval(manager, interval, distance_cost, lower_in, upper_in, + index, start, end_new); + start = end_new; + } else if (start <= interval->start_ && interval->end_ <= end) { + // [----------------------------------] + // interval->start_ interval->end_ + // [**************************************************************] + // start end + // We can safely remove the old interval as it is fully included. + PopInterval(manager, interval); + } else { + if (interval->start_ <= start && end <= interval->end_) { + // [--------------------------------------------------------------] + // interval->start_ interval->end_ + // [*****************************] + // start end + // We have to split the old interval as it fully contains the new one. + const int end_original = interval->end_; + interval->end_ = start; + InsertInterval(manager, interval, interval->distance_cost_, + interval->lower_, interval->upper_, interval->index_, + end, end_original); + } else if (interval->start_ < start) { + // [------------------------------------] + // interval->start_ interval->end_ + // [*****************************] + // start end + interval->end_ = start; + } else { + // [------------------------------------] + // interval->start_ interval->end_ + // [*****************************] + // start end + interval->start_ = end; + } + + // The interval has been modified, we need to reposition it or write it. + RepositionInterval(manager, interval); + } + } + // Insert the remaining interval from start to end. + InsertInterval(manager, interval, distance_cost, lower_in, upper_in, index, + start, end); + } +} + +static int BackwardReferencesHashChainDistanceOnly( + int xsize, int ysize, const uint32_t* const argb, int quality, + int cache_bits, const VP8LHashChain* const hash_chain, + VP8LBackwardRefs* const refs, uint16_t* const dist_array) { + int i; + int ok = 0; + int cc_init = 0; + const int pix_count = xsize * ysize; + const int use_color_cache = (cache_bits > 0); + const size_t literal_array_size = sizeof(double) * + (NUM_LITERAL_CODES + NUM_LENGTH_CODES + + ((cache_bits > 0) ? (1 << cache_bits) : 0)); + const size_t cost_model_size = sizeof(CostModel) + literal_array_size; + CostModel* const cost_model = + (CostModel*)WebPSafeCalloc(1ULL, cost_model_size); + VP8LColorCache hashers; + const int skip_length = 32 + quality; + const int skip_min_distance_code = 2; + CostManager* cost_manager = + (CostManager*)WebPSafeMalloc(1ULL, sizeof(*cost_manager)); + + if (cost_model == NULL || cost_manager == NULL) goto Error; + + cost_model->literal_ = (double*)(cost_model + 1); + if (use_color_cache) { + cc_init = VP8LColorCacheInit(&hashers, cache_bits); + if (!cc_init) goto Error; + } + + if (!CostModelBuild(cost_model, cache_bits, refs)) { + goto Error; + } + + if (!CostManagerInit(cost_manager, dist_array, pix_count, cost_model)) { + goto Error; + } + + // We loop one pixel at a time, but store all currently best points to + // non-processed locations from this point. + dist_array[0] = 0; + // Add first pixel as literal. + AddSingleLiteralWithCostModel(argb + 0, &hashers, cost_model, 0, + use_color_cache, 0.0, cost_manager->costs_, + dist_array); + + for (i = 1; i < pix_count - 1; ++i) { + int offset = 0, len = 0; + double prev_cost = cost_manager->costs_[i - 1]; + HashChainFindCopy(hash_chain, i, &offset, &len); + if (len >= 2) { + // If we are dealing with a non-literal. + const int code = DistanceToPlaneCode(xsize, offset); + const double offset_cost = GetDistanceCost(cost_model, code); + const int first_i = i; + int j_max = 0, interval_ends_index = 0; + const int is_offset_zero = (offset_cost == 0.); + + if (!is_offset_zero) { + j_max = (int)ceil( + (cost_manager->max_cost_cache_ - cost_manager->min_cost_cache_) / + offset_cost); + if (j_max < 1) { + j_max = 1; + } else if (j_max > cost_manager->interval_ends_size_ - 1) { + // This could only happen in the case of MAX_LENGTH. + j_max = cost_manager->interval_ends_size_ - 1; + } + } // else j_max is unused anyway. + + // Instead of considering all contributions from a pixel i by calling: + // PushInterval(cost_manager, prev_cost + offset_cost, i, len); + // we optimize these contributions in case offset_cost stays the same for + // consecutive pixels. This describes a set of pixels similar to a + // previous set (e.g. constant color regions). + for (; i < pix_count - 1; ++i) { + int offset_next, len_next; + prev_cost = cost_manager->costs_[i - 1]; + + if (is_offset_zero) { + // No optimization can be made so we just push all of the + // contributions from i. + PushInterval(cost_manager, prev_cost, i, len); + } else { + // j_max is chosen as the smallest j such that: + // max of cost_cache_ < j*offset cost + min of cost_cache_ + // Therefore, the pixel influenced by i-j_max, cannot be influenced + // by i. Only the costs after the end of what i contributed need to be + // updated. cost_manager->interval_ends_ is a circular buffer that + // stores those ends. + const double distance_cost = prev_cost + offset_cost; + int j = cost_manager->interval_ends_[interval_ends_index]; + if (i - first_i <= j_max || + !IsCostCacheIntervalWritable(j, i + len)) { + PushInterval(cost_manager, distance_cost, i, len); + } else { + for (; j < i + len; ++j) { + UpdateCost(cost_manager, j, i, distance_cost); + } + } + // Store the new end in the circular buffer. + assert(interval_ends_index < cost_manager->interval_ends_size_); + cost_manager->interval_ends_[interval_ends_index] = i + len; + if (++interval_ends_index > j_max) interval_ends_index = 0; + } + + // Check whether i is the last pixel to consider, as it is handled + // differently. + if (i + 1 >= pix_count - 1) break; + HashChainFindCopy(hash_chain, i + 1, &offset_next, &len_next); + if (offset_next != offset) break; + len = len_next; + UpdateCostPerIndex(cost_manager, i); + AddSingleLiteralWithCostModel(argb + i, &hashers, cost_model, i, + use_color_cache, prev_cost, + cost_manager->costs_, dist_array); + } + // Submit the last pixel. + UpdateCostPerIndex(cost_manager, i + 1); + + // This if is for speedup only. It roughly doubles the speed, and + // makes compression worse by .1 %. + if (len >= skip_length && code <= skip_min_distance_code) { + // Long copy for short distances, let's skip the middle + // lookups for better copies. + // 1) insert the hashes. + if (use_color_cache) { + int k; + for (k = 0; k < len; ++k) { + VP8LColorCacheInsert(&hashers, argb[i + k]); + } + } + // 2) jump. + { + const int i_next = i + len - 1; // for loop does ++i, thus -1 here. + for (; i <= i_next; ++i) UpdateCostPerIndex(cost_manager, i + 1); + i = i_next; + } + goto next_symbol; + } + if (len > 2) { + // Also try the smallest interval possible (size 2). + double cost_total = + prev_cost + offset_cost + GetLengthCost(cost_model, 1); + if (cost_manager->costs_[i + 1] > cost_total) { + cost_manager->costs_[i + 1] = (float)cost_total; + dist_array[i + 1] = 2; + } + } + } else { + // The pixel is added as a single literal so just update the costs. + UpdateCostPerIndex(cost_manager, i + 1); + } + + AddSingleLiteralWithCostModel(argb + i, &hashers, cost_model, i, + use_color_cache, prev_cost, + cost_manager->costs_, dist_array); + + next_symbol: ; + } + // Handle the last pixel. + if (i == (pix_count - 1)) { + AddSingleLiteralWithCostModel( + argb + i, &hashers, cost_model, i, use_color_cache, + cost_manager->costs_[pix_count - 2], cost_manager->costs_, dist_array); + } + + ok = !refs->error_; + Error: + if (cc_init) VP8LColorCacheClear(&hashers); + CostManagerClear(cost_manager); + WebPSafeFree(cost_model); + WebPSafeFree(cost_manager); + return ok; +} + +// We pack the path at the end of *dist_array and return +// a pointer to this part of the array. Example: +// dist_array = [1x2xx3x2] => packed [1x2x1232], chosen_path = [1232] +static void TraceBackwards(uint16_t* const dist_array, + int dist_array_size, + uint16_t** const chosen_path, + int* const chosen_path_size) { + uint16_t* path = dist_array + dist_array_size; + uint16_t* cur = dist_array + dist_array_size - 1; + while (cur >= dist_array) { + const int k = *cur; + --path; + *path = k; + cur -= k; + } + *chosen_path = path; + *chosen_path_size = (int)(dist_array + dist_array_size - path); +} + +static int BackwardReferencesHashChainFollowChosenPath( + const uint32_t* const argb, int cache_bits, + const uint16_t* const chosen_path, int chosen_path_size, + const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs) { + const int use_color_cache = (cache_bits > 0); + int ix; + int i = 0; + int ok = 0; + int cc_init = 0; + VP8LColorCache hashers; + + if (use_color_cache) { + cc_init = VP8LColorCacheInit(&hashers, cache_bits); + if (!cc_init) goto Error; + } + + ClearBackwardRefs(refs); + for (ix = 0; ix < chosen_path_size; ++ix) { + const int len = chosen_path[ix]; + if (len != 1) { + int k; + const int offset = HashChainFindOffset(hash_chain, i); + BackwardRefsCursorAdd(refs, PixOrCopyCreateCopy(offset, len)); + if (use_color_cache) { + for (k = 0; k < len; ++k) { + VP8LColorCacheInsert(&hashers, argb[i + k]); + } + } + i += len; + } else { + PixOrCopy v; + const int idx = + use_color_cache ? VP8LColorCacheContains(&hashers, argb[i]) : -1; + if (idx >= 0) { + // use_color_cache is true and hashers contains argb[i] + // push pixel as a color cache index + v = PixOrCopyCreateCacheIdx(idx); + } else { + if (use_color_cache) VP8LColorCacheInsert(&hashers, argb[i]); + v = PixOrCopyCreateLiteral(argb[i]); + } + BackwardRefsCursorAdd(refs, v); + ++i; + } + } + ok = !refs->error_; + Error: + if (cc_init) VP8LColorCacheClear(&hashers); + return ok; +} + +// Returns 1 on success. +static int BackwardReferencesTraceBackwards( + int xsize, int ysize, const uint32_t* const argb, int quality, + int cache_bits, const VP8LHashChain* const hash_chain, + VP8LBackwardRefs* const refs) { + int ok = 0; + const int dist_array_size = xsize * ysize; + uint16_t* chosen_path = NULL; + int chosen_path_size = 0; + uint16_t* dist_array = + (uint16_t*)WebPSafeMalloc(dist_array_size, sizeof(*dist_array)); + + if (dist_array == NULL) goto Error; + + if (!BackwardReferencesHashChainDistanceOnly( + xsize, ysize, argb, quality, cache_bits, hash_chain, + refs, dist_array)) { + goto Error; + } + TraceBackwards(dist_array, dist_array_size, &chosen_path, &chosen_path_size); + if (!BackwardReferencesHashChainFollowChosenPath( + argb, cache_bits, chosen_path, chosen_path_size, hash_chain, refs)) { + goto Error; + } + ok = 1; + Error: + WebPSafeFree(dist_array); + return ok; +} + +static void BackwardReferences2DLocality(int xsize, + const VP8LBackwardRefs* const refs) { + VP8LRefsCursor c = VP8LRefsCursorInit(refs); + while (VP8LRefsCursorOk(&c)) { + if (PixOrCopyIsCopy(c.cur_pos)) { + const int dist = c.cur_pos->argb_or_distance; + const int transformed_dist = DistanceToPlaneCode(xsize, dist); + c.cur_pos->argb_or_distance = transformed_dist; + } + VP8LRefsCursorNext(&c); + } +} + +// Computes the entropies for a color cache size (in bits) between 0 (unused) +// and cache_bits_max (inclusive). +// Returns 1 on success, 0 in case of allocation error. +static int ComputeCacheEntropies(const uint32_t* argb, + const VP8LBackwardRefs* const refs, + int cache_bits_max, double entropies[]) { + int cc_init[MAX_COLOR_CACHE_BITS + 1] = { 0 }; + VP8LColorCache hashers[MAX_COLOR_CACHE_BITS + 1]; + VP8LRefsCursor c = VP8LRefsCursorInit(refs); + VP8LHistogram* histos[MAX_COLOR_CACHE_BITS + 1] = { NULL }; + int ok = 0; + int i; + + for (i = 0; i <= cache_bits_max; ++i) { + histos[i] = VP8LAllocateHistogram(i); + if (histos[i] == NULL) goto Error; + if (i == 0) continue; + cc_init[i] = VP8LColorCacheInit(&hashers[i], i); + if (!cc_init[i]) goto Error; + } + + assert(cache_bits_max >= 0); + // Do not use the color cache for cache_bits=0. + while (VP8LRefsCursorOk(&c)) { + VP8LHistogramAddSinglePixOrCopy(histos[0], c.cur_pos); + VP8LRefsCursorNext(&c); + } + if (cache_bits_max > 0) { + c = VP8LRefsCursorInit(refs); + while (VP8LRefsCursorOk(&c)) { + const PixOrCopy* const v = c.cur_pos; + if (PixOrCopyIsLiteral(v)) { + const uint32_t pix = *argb++; + // The keys of the caches can be derived from the longest one. + int key = HashPix(pix, 32 - cache_bits_max); + for (i = cache_bits_max; i >= 1; --i, key >>= 1) { + if (VP8LColorCacheLookup(&hashers[i], key) == pix) { + ++histos[i]->literal_[NUM_LITERAL_CODES + NUM_LENGTH_CODES + key]; + } else { + VP8LColorCacheSet(&hashers[i], key, pix); + ++histos[i]->blue_[pix & 0xff]; + ++histos[i]->literal_[(pix >> 8) & 0xff]; + ++histos[i]->red_[(pix >> 16) & 0xff]; + ++histos[i]->alpha_[pix >> 24]; + } + } + } else { + // Update the histograms for distance/length. + int len = PixOrCopyLength(v); + int code_dist, code_len, extra_bits; + uint32_t argb_prev = *argb ^ 0xffffffffu; + VP8LPrefixEncodeBits(len, &code_len, &extra_bits); + VP8LPrefixEncodeBits(PixOrCopyDistance(v), &code_dist, &extra_bits); + for (i = 1; i <= cache_bits_max; ++i) { + ++histos[i]->literal_[NUM_LITERAL_CODES + code_len]; + ++histos[i]->distance_[code_dist]; + } + // Update the colors caches. + do { + if (*argb != argb_prev) { + // Efficiency: insert only if the color changes. + int key = HashPix(*argb, 32 - cache_bits_max); + for (i = cache_bits_max; i >= 1; --i, key >>= 1) { + hashers[i].colors_[key] = *argb; + } + argb_prev = *argb; + } + argb++; + } while (--len != 0); + } + VP8LRefsCursorNext(&c); + } + } + for (i = 0; i <= cache_bits_max; ++i) { + entropies[i] = VP8LHistogramEstimateBits(histos[i]); + } + ok = 1; +Error: + for (i = 0; i <= cache_bits_max; ++i) { + if (cc_init[i]) VP8LColorCacheClear(&hashers[i]); + VP8LFreeHistogram(histos[i]); + } + return ok; +} + +// Evaluate optimal cache bits for the local color cache. +// The input *best_cache_bits sets the maximum cache bits to use (passing 0 +// implies disabling the local color cache). The local color cache is also +// disabled for the lower (<= 25) quality. +// Returns 0 in case of memory error. +static int CalculateBestCacheSize(const uint32_t* const argb, + int xsize, int ysize, int quality, + const VP8LHashChain* const hash_chain, + VP8LBackwardRefs* const refs, + int* const lz77_computed, + int* const best_cache_bits) { + int i; + int cache_bits_high = (quality <= 25) ? 0 : *best_cache_bits; + double entropy_min = MAX_ENTROPY; + double entropies[MAX_COLOR_CACHE_BITS + 1]; + + assert(cache_bits_high <= MAX_COLOR_CACHE_BITS); + + *lz77_computed = 0; + if (cache_bits_high == 0) { + *best_cache_bits = 0; + // Local color cache is disabled. + return 1; + } + // Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color cache + // is not that different in practice. + if (!BackwardReferencesLz77(xsize, ysize, argb, 0, hash_chain, refs)) { + return 0; + } + // Find the cache_bits giving the lowest entropy. The search is done in a + // brute-force way as the function (entropy w.r.t cache_bits) can be + // anything in practice. + if (!ComputeCacheEntropies(argb, refs, cache_bits_high, entropies)) { + return 0; + } + for (i = 0; i <= cache_bits_high; ++i) { + if (i == 0 || entropies[i] < entropy_min) { + entropy_min = entropies[i]; + *best_cache_bits = i; + } + } + return 1; +} + +// Update (in-place) backward references for specified cache_bits. +static int BackwardRefsWithLocalCache(const uint32_t* const argb, + int cache_bits, + VP8LBackwardRefs* const refs) { + int pixel_index = 0; + VP8LColorCache hashers; + VP8LRefsCursor c = VP8LRefsCursorInit(refs); + if (!VP8LColorCacheInit(&hashers, cache_bits)) return 0; + + while (VP8LRefsCursorOk(&c)) { + PixOrCopy* const v = c.cur_pos; + if (PixOrCopyIsLiteral(v)) { + const uint32_t argb_literal = v->argb_or_distance; + const int ix = VP8LColorCacheContains(&hashers, argb_literal); + if (ix >= 0) { + // hashers contains argb_literal + *v = PixOrCopyCreateCacheIdx(ix); + } else { + VP8LColorCacheInsert(&hashers, argb_literal); + } + ++pixel_index; + } else { + // refs was created without local cache, so it can not have cache indexes. + int k; + assert(PixOrCopyIsCopy(v)); + for (k = 0; k < v->len; ++k) { + VP8LColorCacheInsert(&hashers, argb[pixel_index++]); + } + } + VP8LRefsCursorNext(&c); + } + VP8LColorCacheClear(&hashers); + return 1; +} + +static VP8LBackwardRefs* GetBackwardReferencesLowEffort( + int width, int height, const uint32_t* const argb, + int* const cache_bits, const VP8LHashChain* const hash_chain, + VP8LBackwardRefs refs_array[2]) { + VP8LBackwardRefs* refs_lz77 = &refs_array[0]; + *cache_bits = 0; + if (!BackwardReferencesLz77(width, height, argb, 0, hash_chain, refs_lz77)) { + return NULL; + } + BackwardReferences2DLocality(width, refs_lz77); + return refs_lz77; +} + +static VP8LBackwardRefs* GetBackwardReferences( + int width, int height, const uint32_t* const argb, int quality, + int* const cache_bits, const VP8LHashChain* const hash_chain, + VP8LBackwardRefs refs_array[2]) { + int lz77_is_useful; + int lz77_computed; + double bit_cost_lz77, bit_cost_rle; + VP8LBackwardRefs* best = NULL; + VP8LBackwardRefs* refs_lz77 = &refs_array[0]; + VP8LBackwardRefs* refs_rle = &refs_array[1]; + VP8LHistogram* histo = NULL; + + if (!CalculateBestCacheSize(argb, width, height, quality, hash_chain, + refs_lz77, &lz77_computed, cache_bits)) { + goto Error; + } + + if (lz77_computed) { + // Transform refs_lz77 for the optimized cache_bits. + if (*cache_bits > 0) { + if (!BackwardRefsWithLocalCache(argb, *cache_bits, refs_lz77)) { + goto Error; + } + } + } else { + if (!BackwardReferencesLz77(width, height, argb, *cache_bits, hash_chain, + refs_lz77)) { + goto Error; + } + } + + if (!BackwardReferencesRle(width, height, argb, *cache_bits, refs_rle)) { + goto Error; + } + + histo = VP8LAllocateHistogram(*cache_bits); + if (histo == NULL) goto Error; + + { + // Evaluate LZ77 coding. + VP8LHistogramCreate(histo, refs_lz77, *cache_bits); + bit_cost_lz77 = VP8LHistogramEstimateBits(histo); + // Evaluate RLE coding. + VP8LHistogramCreate(histo, refs_rle, *cache_bits); + bit_cost_rle = VP8LHistogramEstimateBits(histo); + // Decide if LZ77 is useful. + lz77_is_useful = (bit_cost_lz77 < bit_cost_rle); + } + + // Choose appropriate backward reference. + if (lz77_is_useful) { + // TraceBackwards is costly. Don't execute it at lower quality. + const int try_lz77_trace_backwards = (quality >= 25); + best = refs_lz77; // default guess: lz77 is better + if (try_lz77_trace_backwards) { + VP8LBackwardRefs* const refs_trace = refs_rle; + if (!VP8LBackwardRefsCopy(refs_lz77, refs_trace)) { + best = NULL; + goto Error; + } + if (BackwardReferencesTraceBackwards(width, height, argb, quality, + *cache_bits, hash_chain, + refs_trace)) { + double bit_cost_trace; + // Evaluate LZ77 coding. + VP8LHistogramCreate(histo, refs_trace, *cache_bits); + bit_cost_trace = VP8LHistogramEstimateBits(histo); + if (bit_cost_trace < bit_cost_lz77) { + best = refs_trace; + } + } + } + } else { + best = refs_rle; + } + + BackwardReferences2DLocality(width, best); + + Error: + VP8LFreeHistogram(histo); + return best; +} + +VP8LBackwardRefs* VP8LGetBackwardReferences( + int width, int height, const uint32_t* const argb, int quality, + int low_effort, int* const cache_bits, + const VP8LHashChain* const hash_chain, VP8LBackwardRefs refs_array[2]) { + if (low_effort) { + return GetBackwardReferencesLowEffort(width, height, argb, cache_bits, + hash_chain, refs_array); + } else { + return GetBackwardReferences(width, height, argb, quality, cache_bits, + hash_chain, refs_array); + } +} diff --git a/3rdparty/libwebp/enc/backward_references_enc.h b/3rdparty/libwebp/enc/backward_references_enc.h new file mode 100644 index 0000000000..3a19aa763e --- /dev/null +++ b/3rdparty/libwebp/enc/backward_references_enc.h @@ -0,0 +1,207 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Jyrki Alakuijala (jyrki@google.com) +// + +#ifndef WEBP_ENC_BACKWARD_REFERENCES_H_ +#define WEBP_ENC_BACKWARD_REFERENCES_H_ + +#include +#include +#include "../webp/types.h" +#include "../webp/format_constants.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// The maximum allowed limit is 11. +#define MAX_COLOR_CACHE_BITS 10 + +// ----------------------------------------------------------------------------- +// PixOrCopy + +enum Mode { + kLiteral, + kCacheIdx, + kCopy, + kNone +}; + +typedef struct { + // mode as uint8_t to make the memory layout to be exactly 8 bytes. + uint8_t mode; + uint16_t len; + uint32_t argb_or_distance; +} PixOrCopy; + +static WEBP_INLINE PixOrCopy PixOrCopyCreateCopy(uint32_t distance, + uint16_t len) { + PixOrCopy retval; + retval.mode = kCopy; + retval.argb_or_distance = distance; + retval.len = len; + return retval; +} + +static WEBP_INLINE PixOrCopy PixOrCopyCreateCacheIdx(int idx) { + PixOrCopy retval; + assert(idx >= 0); + assert(idx < (1 << MAX_COLOR_CACHE_BITS)); + retval.mode = kCacheIdx; + retval.argb_or_distance = idx; + retval.len = 1; + return retval; +} + +static WEBP_INLINE PixOrCopy PixOrCopyCreateLiteral(uint32_t argb) { + PixOrCopy retval; + retval.mode = kLiteral; + retval.argb_or_distance = argb; + retval.len = 1; + return retval; +} + +static WEBP_INLINE int PixOrCopyIsLiteral(const PixOrCopy* const p) { + return (p->mode == kLiteral); +} + +static WEBP_INLINE int PixOrCopyIsCacheIdx(const PixOrCopy* const p) { + return (p->mode == kCacheIdx); +} + +static WEBP_INLINE int PixOrCopyIsCopy(const PixOrCopy* const p) { + return (p->mode == kCopy); +} + +static WEBP_INLINE uint32_t PixOrCopyLiteral(const PixOrCopy* const p, + int component) { + assert(p->mode == kLiteral); + return (p->argb_or_distance >> (component * 8)) & 0xff; +} + +static WEBP_INLINE uint32_t PixOrCopyLength(const PixOrCopy* const p) { + return p->len; +} + +static WEBP_INLINE uint32_t PixOrCopyArgb(const PixOrCopy* const p) { + assert(p->mode == kLiteral); + return p->argb_or_distance; +} + +static WEBP_INLINE uint32_t PixOrCopyCacheIdx(const PixOrCopy* const p) { + assert(p->mode == kCacheIdx); + assert(p->argb_or_distance < (1U << MAX_COLOR_CACHE_BITS)); + return p->argb_or_distance; +} + +static WEBP_INLINE uint32_t PixOrCopyDistance(const PixOrCopy* const p) { + assert(p->mode == kCopy); + return p->argb_or_distance; +} + +// ----------------------------------------------------------------------------- +// VP8LHashChain + +#define HASH_BITS 18 +#define HASH_SIZE (1 << HASH_BITS) + +typedef struct VP8LHashChain VP8LHashChain; +struct VP8LHashChain { + // The 20 most significant bits contain the offset at which the best match + // is found. These 20 bits are the limit defined by GetWindowSizeForHashChain + // (through WINDOW_SIZE = 1<<20). + // The lower 12 bits contain the length of the match. The 12 bit limit is + // defined in MaxFindCopyLength with MAX_LENGTH=4096. + uint32_t* offset_length_; + // This is the maximum size of the hash_chain that can be constructed. + // Typically this is the pixel count (width x height) for a given image. + int size_; +}; + +// Must be called first, to set size. +int VP8LHashChainInit(VP8LHashChain* const p, int size); +// Pre-compute the best matches for argb. +int VP8LHashChainFill(VP8LHashChain* const p, int quality, + const uint32_t* const argb, int xsize, int ysize, + int low_effort); +void VP8LHashChainClear(VP8LHashChain* const p); // release memory + +// ----------------------------------------------------------------------------- +// VP8LBackwardRefs (block-based backward-references storage) + +// maximum number of reference blocks the image will be segmented into +#define MAX_REFS_BLOCK_PER_IMAGE 16 + +typedef struct PixOrCopyBlock PixOrCopyBlock; // forward declaration +typedef struct VP8LBackwardRefs VP8LBackwardRefs; + +// Container for blocks chain +struct VP8LBackwardRefs { + int block_size_; // common block-size + int error_; // set to true if some memory error occurred + PixOrCopyBlock* refs_; // list of currently used blocks + PixOrCopyBlock** tail_; // for list recycling + PixOrCopyBlock* free_blocks_; // free-list + PixOrCopyBlock* last_block_; // used for adding new refs (internal) +}; + +// Initialize the object. 'block_size' is the common block size to store +// references (typically, width * height / MAX_REFS_BLOCK_PER_IMAGE). +void VP8LBackwardRefsInit(VP8LBackwardRefs* const refs, int block_size); +// Release memory for backward references. +void VP8LBackwardRefsClear(VP8LBackwardRefs* const refs); +// Copies the 'src' backward refs to the 'dst'. Returns 0 in case of error. +int VP8LBackwardRefsCopy(const VP8LBackwardRefs* const src, + VP8LBackwardRefs* const dst); + +// Cursor for iterating on references content +typedef struct { + // public: + PixOrCopy* cur_pos; // current position + // private: + PixOrCopyBlock* cur_block_; // current block in the refs list + const PixOrCopy* last_pos_; // sentinel for switching to next block +} VP8LRefsCursor; + +// Returns a cursor positioned at the beginning of the references list. +VP8LRefsCursor VP8LRefsCursorInit(const VP8LBackwardRefs* const refs); +// Returns true if cursor is pointing at a valid position. +static WEBP_INLINE int VP8LRefsCursorOk(const VP8LRefsCursor* const c) { + return (c->cur_pos != NULL); +} +// Move to next block of references. Internal, not to be called directly. +void VP8LRefsCursorNextBlock(VP8LRefsCursor* const c); +// Move to next position, or NULL. Should not be called if !VP8LRefsCursorOk(). +static WEBP_INLINE void VP8LRefsCursorNext(VP8LRefsCursor* const c) { + assert(c != NULL); + assert(VP8LRefsCursorOk(c)); + if (++c->cur_pos == c->last_pos_) VP8LRefsCursorNextBlock(c); +} + +// ----------------------------------------------------------------------------- +// Main entry points + +// Evaluates best possible backward references for specified quality. +// The input cache_bits to 'VP8LGetBackwardReferences' sets the maximum cache +// bits to use (passing 0 implies disabling the local color cache). +// The optimal cache bits is evaluated and set for the *cache_bits parameter. +// The return value is the pointer to the best of the two backward refs viz, +// refs[0] or refs[1]. +VP8LBackwardRefs* VP8LGetBackwardReferences( + int width, int height, const uint32_t* const argb, int quality, + int low_effort, int* const cache_bits, + const VP8LHashChain* const hash_chain, VP8LBackwardRefs refs[2]); + +#ifdef __cplusplus +} +#endif + +#endif // WEBP_ENC_BACKWARD_REFERENCES_H_ diff --git a/3rdparty/libwebp/enc/config.c b/3rdparty/libwebp/enc/config_enc.c similarity index 55% rename from 3rdparty/libwebp/enc/config.c rename to 3rdparty/libwebp/enc/config_enc.c index acf96b0f30..4589dc0619 100644 --- a/3rdparty/libwebp/enc/config.c +++ b/3rdparty/libwebp/enc/config_enc.c @@ -11,12 +11,12 @@ // // Author: Skal (pascal.massimino@gmail.com) -#include "../webp/encode.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" #endif +#include "../webp/encode.h" + //------------------------------------------------------------------------------ // WebPConfig //------------------------------------------------------------------------------ @@ -33,7 +33,7 @@ int WebPConfigInitInternal(WebPConfig* config, config->target_PSNR = 0.; config->method = 4; config->sns_strength = 50; - config->filter_strength = 60; // rather high filtering, helps w/ gradients. + config->filter_strength = 60; // mid-filtering config->filter_sharpness = 0; config->filter_type = 1; // default: strong (so U/V is filtered too) config->partitions = 0; @@ -47,10 +47,14 @@ int WebPConfigInitInternal(WebPConfig* config, config->alpha_filtering = 1; config->alpha_quality = 100; config->lossless = 0; + config->exact = 0; config->image_hint = WEBP_HINT_DEFAULT; config->emulate_jpeg_size = 0; config->thread_level = 0; config->low_memory = 0; + config->near_lossless = 100; + config->use_delta_palette = 0; + config->use_sharp_yuv = 0; // TODO(skal): tune. switch (preset) { @@ -58,11 +62,13 @@ int WebPConfigInitInternal(WebPConfig* config, config->sns_strength = 80; config->filter_sharpness = 4; config->filter_strength = 35; + config->preprocessing &= ~2; // no dithering break; case WEBP_PRESET_PHOTO: config->sns_strength = 80; config->filter_sharpness = 3; config->filter_strength = 30; + config->preprocessing |= 2; break; case WEBP_PRESET_DRAWING: config->sns_strength = 25; @@ -72,10 +78,12 @@ int WebPConfigInitInternal(WebPConfig* config, case WEBP_PRESET_ICON: config->sns_strength = 0; config->filter_strength = 0; // disable filtering to retain sharpness + config->preprocessing &= ~2; // no dithering break; case WEBP_PRESET_TEXT: config->sns_strength = 0; config->filter_strength = 0; // disable filtering to retain sharpness + config->preprocessing &= ~2; // no dithering config->segments = 2; break; case WEBP_PRESET_DEFAULT: @@ -87,57 +95,58 @@ int WebPConfigInitInternal(WebPConfig* config, int WebPValidateConfig(const WebPConfig* config) { if (config == NULL) return 0; - if (config->quality < 0 || config->quality > 100) - return 0; - if (config->target_size < 0) - return 0; - if (config->target_PSNR < 0) - return 0; - if (config->method < 0 || config->method > 6) - return 0; - if (config->segments < 1 || config->segments > 4) - return 0; - if (config->sns_strength < 0 || config->sns_strength > 100) - return 0; - if (config->filter_strength < 0 || config->filter_strength > 100) - return 0; - if (config->filter_sharpness < 0 || config->filter_sharpness > 7) - return 0; - if (config->filter_type < 0 || config->filter_type > 1) - return 0; - if (config->autofilter < 0 || config->autofilter > 1) - return 0; - if (config->pass < 1 || config->pass > 10) - return 0; - if (config->show_compressed < 0 || config->show_compressed > 1) - return 0; - if (config->preprocessing < 0 || config->preprocessing > 1) - return 0; - if (config->partitions < 0 || config->partitions > 3) - return 0; - if (config->partition_limit < 0 || config->partition_limit > 100) - return 0; - if (config->alpha_compression < 0) - return 0; - if (config->alpha_filtering < 0) - return 0; - if (config->alpha_quality < 0 || config->alpha_quality > 100) - return 0; - if (config->lossless < 0 || config->lossless > 1) - return 0; - if (config->image_hint >= WEBP_HINT_LAST) - return 0; - if (config->emulate_jpeg_size < 0 || config->emulate_jpeg_size > 1) - return 0; - if (config->thread_level < 0 || config->thread_level > 1) - return 0; - if (config->low_memory < 0 || config->low_memory > 1) + if (config->quality < 0 || config->quality > 100) return 0; + if (config->target_size < 0) return 0; + if (config->target_PSNR < 0) return 0; + if (config->method < 0 || config->method > 6) return 0; + if (config->segments < 1 || config->segments > 4) return 0; + if (config->sns_strength < 0 || config->sns_strength > 100) return 0; + if (config->filter_strength < 0 || config->filter_strength > 100) return 0; + if (config->filter_sharpness < 0 || config->filter_sharpness > 7) return 0; + if (config->filter_type < 0 || config->filter_type > 1) return 0; + if (config->autofilter < 0 || config->autofilter > 1) return 0; + if (config->pass < 1 || config->pass > 10) return 0; + if (config->show_compressed < 0 || config->show_compressed > 1) return 0; + if (config->preprocessing < 0 || config->preprocessing > 7) return 0; + if (config->partitions < 0 || config->partitions > 3) return 0; + if (config->partition_limit < 0 || config->partition_limit > 100) return 0; + if (config->alpha_compression < 0) return 0; + if (config->alpha_filtering < 0) return 0; + if (config->alpha_quality < 0 || config->alpha_quality > 100) return 0; + if (config->lossless < 0 || config->lossless > 1) return 0; + if (config->near_lossless < 0 || config->near_lossless > 100) return 0; + if (config->image_hint >= WEBP_HINT_LAST) return 0; + if (config->emulate_jpeg_size < 0 || config->emulate_jpeg_size > 1) return 0; + if (config->thread_level < 0 || config->thread_level > 1) return 0; + if (config->low_memory < 0 || config->low_memory > 1) return 0; + if (config->exact < 0 || config->exact > 1) return 0; + if (config->use_delta_palette < 0 || config->use_delta_palette > 1) { return 0; + } + if (config->use_sharp_yuv < 0 || config->use_sharp_yuv > 1) return 0; + return 1; } //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif +#define MAX_LEVEL 9 + +// Mapping between -z level and -m / -q parameter settings. +static const struct { + uint8_t method_; + uint8_t quality_; +} kLosslessPresets[MAX_LEVEL + 1] = { + { 0, 0 }, { 1, 20 }, { 2, 25 }, { 3, 30 }, { 3, 50 }, + { 4, 50 }, { 4, 75 }, { 4, 90 }, { 5, 90 }, { 6, 100 } +}; + +int WebPConfigLosslessPreset(WebPConfig* config, int level) { + if (config == NULL || level < 0 || level > MAX_LEVEL) return 0; + config->lossless = 1; + config->method = kLosslessPresets[level].method_; + config->quality = kLosslessPresets[level].quality_; + return 1; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/enc/cost_enc.c b/3rdparty/libwebp/enc/cost_enc.c new file mode 100644 index 0000000000..c823f5a664 --- /dev/null +++ b/3rdparty/libwebp/enc/cost_enc.c @@ -0,0 +1,342 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Cost tables for level and modes +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./cost_enc.h" + +//------------------------------------------------------------------------------ +// Level cost tables + +// For each given level, the following table gives the pattern of contexts to +// use for coding it (in [][0]) as well as the bit value to use for each +// context (in [][1]). +const uint16_t VP8LevelCodes[MAX_VARIABLE_LEVEL][2] = { + {0x001, 0x000}, {0x007, 0x001}, {0x00f, 0x005}, + {0x00f, 0x00d}, {0x033, 0x003}, {0x033, 0x003}, {0x033, 0x023}, + {0x033, 0x023}, {0x033, 0x023}, {0x033, 0x023}, {0x0d3, 0x013}, + {0x0d3, 0x013}, {0x0d3, 0x013}, {0x0d3, 0x013}, {0x0d3, 0x013}, + {0x0d3, 0x013}, {0x0d3, 0x013}, {0x0d3, 0x013}, {0x0d3, 0x093}, + {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, + {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, + {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, + {0x0d3, 0x093}, {0x0d3, 0x093}, {0x0d3, 0x093}, {0x153, 0x053}, + {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, + {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, + {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, + {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, + {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, + {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, + {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, + {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x053}, {0x153, 0x153} +}; + +static int VariableLevelCost(int level, const uint8_t probas[NUM_PROBAS]) { + int pattern = VP8LevelCodes[level - 1][0]; + int bits = VP8LevelCodes[level - 1][1]; + int cost = 0; + int i; + for (i = 2; pattern; ++i) { + if (pattern & 1) { + cost += VP8BitCost(bits & 1, probas[i]); + } + bits >>= 1; + pattern >>= 1; + } + return cost; +} + +//------------------------------------------------------------------------------ +// Pre-calc level costs once for all + +void VP8CalculateLevelCosts(VP8EncProba* const proba) { + int ctype, band, ctx; + + if (!proba->dirty_) return; // nothing to do. + + for (ctype = 0; ctype < NUM_TYPES; ++ctype) { + int n; + for (band = 0; band < NUM_BANDS; ++band) { + for (ctx = 0; ctx < NUM_CTX; ++ctx) { + const uint8_t* const p = proba->coeffs_[ctype][band][ctx]; + uint16_t* const table = proba->level_cost_[ctype][band][ctx]; + const int cost0 = (ctx > 0) ? VP8BitCost(1, p[0]) : 0; + const int cost_base = VP8BitCost(1, p[1]) + cost0; + int v; + table[0] = VP8BitCost(0, p[1]) + cost0; + for (v = 1; v <= MAX_VARIABLE_LEVEL; ++v) { + table[v] = cost_base + VariableLevelCost(v, p); + } + // Starting at level 67 and up, the variable part of the cost is + // actually constant. + } + } + for (n = 0; n < 16; ++n) { // replicate bands. We don't need to sentinel. + for (ctx = 0; ctx < NUM_CTX; ++ctx) { + proba->remapped_costs_[ctype][n][ctx] = + proba->level_cost_[ctype][VP8EncBands[n]][ctx]; + } + } + } + proba->dirty_ = 0; +} + +//------------------------------------------------------------------------------ +// Mode cost tables. + +// These are the fixed probabilities (in the coding trees) turned into bit-cost +// by calling VP8BitCost(). +const uint16_t VP8FixedCostsUV[4] = { 302, 984, 439, 642 }; +// note: these values include the fixed VP8BitCost(1, 145) mode selection cost. +const uint16_t VP8FixedCostsI16[4] = { 663, 919, 872, 919 }; +const uint16_t VP8FixedCostsI4[NUM_BMODES][NUM_BMODES][NUM_BMODES] = { + { { 40, 1151, 1723, 1874, 2103, 2019, 1628, 1777, 2226, 2137 }, + { 192, 469, 1296, 1308, 1849, 1794, 1781, 1703, 1713, 1522 }, + { 142, 910, 762, 1684, 1849, 1576, 1460, 1305, 1801, 1657 }, + { 559, 641, 1370, 421, 1182, 1569, 1612, 1725, 863, 1007 }, + { 299, 1059, 1256, 1108, 636, 1068, 1581, 1883, 869, 1142 }, + { 277, 1111, 707, 1362, 1089, 672, 1603, 1541, 1545, 1291 }, + { 214, 781, 1609, 1303, 1632, 2229, 726, 1560, 1713, 918 }, + { 152, 1037, 1046, 1759, 1983, 2174, 1358, 742, 1740, 1390 }, + { 512, 1046, 1420, 753, 752, 1297, 1486, 1613, 460, 1207 }, + { 424, 827, 1362, 719, 1462, 1202, 1199, 1476, 1199, 538 } }, + { { 240, 402, 1134, 1491, 1659, 1505, 1517, 1555, 1979, 2099 }, + { 467, 242, 960, 1232, 1714, 1620, 1834, 1570, 1676, 1391 }, + { 500, 455, 463, 1507, 1699, 1282, 1564, 982, 2114, 2114 }, + { 672, 643, 1372, 331, 1589, 1667, 1453, 1938, 996, 876 }, + { 458, 783, 1037, 911, 738, 968, 1165, 1518, 859, 1033 }, + { 504, 815, 504, 1139, 1219, 719, 1506, 1085, 1268, 1268 }, + { 333, 630, 1445, 1239, 1883, 3672, 799, 1548, 1865, 598 }, + { 399, 644, 746, 1342, 1856, 1350, 1493, 613, 1855, 1015 }, + { 622, 749, 1205, 608, 1066, 1408, 1290, 1406, 546, 971 }, + { 500, 753, 1041, 668, 1230, 1617, 1297, 1425, 1383, 523 } }, + { { 394, 553, 523, 1502, 1536, 981, 1608, 1142, 1666, 2181 }, + { 655, 430, 375, 1411, 1861, 1220, 1677, 1135, 1978, 1553 }, + { 690, 640, 245, 1954, 2070, 1194, 1528, 982, 1972, 2232 }, + { 559, 834, 741, 867, 1131, 980, 1225, 852, 1092, 784 }, + { 690, 875, 516, 959, 673, 894, 1056, 1190, 1528, 1126 }, + { 740, 951, 384, 1277, 1177, 492, 1579, 1155, 1846, 1513 }, + { 323, 775, 1062, 1776, 3062, 1274, 813, 1188, 1372, 655 }, + { 488, 971, 484, 1767, 1515, 1775, 1115, 503, 1539, 1461 }, + { 740, 1006, 998, 709, 851, 1230, 1337, 788, 741, 721 }, + { 522, 1073, 573, 1045, 1346, 887, 1046, 1146, 1203, 697 } }, + { { 105, 864, 1442, 1009, 1934, 1840, 1519, 1920, 1673, 1579 }, + { 534, 305, 1193, 683, 1388, 2164, 1802, 1894, 1264, 1170 }, + { 305, 518, 877, 1108, 1426, 3215, 1425, 1064, 1320, 1242 }, + { 683, 732, 1927, 257, 1493, 2048, 1858, 1552, 1055, 947 }, + { 394, 814, 1024, 660, 959, 1556, 1282, 1289, 893, 1047 }, + { 528, 615, 996, 940, 1201, 635, 1094, 2515, 803, 1358 }, + { 347, 614, 1609, 1187, 3133, 1345, 1007, 1339, 1017, 667 }, + { 218, 740, 878, 1605, 3650, 3650, 1345, 758, 1357, 1617 }, + { 672, 750, 1541, 558, 1257, 1599, 1870, 2135, 402, 1087 }, + { 592, 684, 1161, 430, 1092, 1497, 1475, 1489, 1095, 822 } }, + { { 228, 1056, 1059, 1368, 752, 982, 1512, 1518, 987, 1782 }, + { 494, 514, 818, 942, 965, 892, 1610, 1356, 1048, 1363 }, + { 512, 648, 591, 1042, 761, 991, 1196, 1454, 1309, 1463 }, + { 683, 749, 1043, 676, 841, 1396, 1133, 1138, 654, 939 }, + { 622, 1101, 1126, 994, 361, 1077, 1203, 1318, 877, 1219 }, + { 631, 1068, 857, 1650, 651, 477, 1650, 1419, 828, 1170 }, + { 555, 727, 1068, 1335, 3127, 1339, 820, 1331, 1077, 429 }, + { 504, 879, 624, 1398, 889, 889, 1392, 808, 891, 1406 }, + { 683, 1602, 1289, 977, 578, 983, 1280, 1708, 406, 1122 }, + { 399, 865, 1433, 1070, 1072, 764, 968, 1477, 1223, 678 } }, + { { 333, 760, 935, 1638, 1010, 529, 1646, 1410, 1472, 2219 }, + { 512, 494, 750, 1160, 1215, 610, 1870, 1868, 1628, 1169 }, + { 572, 646, 492, 1934, 1208, 603, 1580, 1099, 1398, 1995 }, + { 786, 789, 942, 581, 1018, 951, 1599, 1207, 731, 768 }, + { 690, 1015, 672, 1078, 582, 504, 1693, 1438, 1108, 2897 }, + { 768, 1267, 571, 2005, 1243, 244, 2881, 1380, 1786, 1453 }, + { 452, 899, 1293, 903, 1311, 3100, 465, 1311, 1319, 813 }, + { 394, 927, 942, 1103, 1358, 1104, 946, 593, 1363, 1109 }, + { 559, 1005, 1007, 1016, 658, 1173, 1021, 1164, 623, 1028 }, + { 564, 796, 632, 1005, 1014, 863, 2316, 1268, 938, 764 } }, + { { 266, 606, 1098, 1228, 1497, 1243, 948, 1030, 1734, 1461 }, + { 366, 585, 901, 1060, 1407, 1247, 876, 1134, 1620, 1054 }, + { 452, 565, 542, 1729, 1479, 1479, 1016, 886, 2938, 1150 }, + { 555, 1088, 1533, 950, 1354, 895, 834, 1019, 1021, 496 }, + { 704, 815, 1193, 971, 973, 640, 1217, 2214, 832, 578 }, + { 672, 1245, 579, 871, 875, 774, 872, 1273, 1027, 949 }, + { 296, 1134, 2050, 1784, 1636, 3425, 442, 1550, 2076, 722 }, + { 342, 982, 1259, 1846, 1848, 1848, 622, 568, 1847, 1052 }, + { 555, 1064, 1304, 828, 746, 1343, 1075, 1329, 1078, 494 }, + { 288, 1167, 1285, 1174, 1639, 1639, 833, 2254, 1304, 509 } }, + { { 342, 719, 767, 1866, 1757, 1270, 1246, 550, 1746, 2151 }, + { 483, 653, 694, 1509, 1459, 1410, 1218, 507, 1914, 1266 }, + { 488, 757, 447, 2979, 1813, 1268, 1654, 539, 1849, 2109 }, + { 522, 1097, 1085, 851, 1365, 1111, 851, 901, 961, 605 }, + { 709, 716, 841, 728, 736, 945, 941, 862, 2845, 1057 }, + { 512, 1323, 500, 1336, 1083, 681, 1342, 717, 1604, 1350 }, + { 452, 1155, 1372, 1900, 1501, 3290, 311, 944, 1919, 922 }, + { 403, 1520, 977, 2132, 1733, 3522, 1076, 276, 3335, 1547 }, + { 559, 1374, 1101, 615, 673, 2462, 974, 795, 984, 984 }, + { 547, 1122, 1062, 812, 1410, 951, 1140, 622, 1268, 651 } }, + { { 165, 982, 1235, 938, 1334, 1366, 1659, 1578, 964, 1612 }, + { 592, 422, 925, 847, 1139, 1112, 1387, 2036, 861, 1041 }, + { 403, 837, 732, 770, 941, 1658, 1250, 809, 1407, 1407 }, + { 896, 874, 1071, 381, 1568, 1722, 1437, 2192, 480, 1035 }, + { 640, 1098, 1012, 1032, 684, 1382, 1581, 2106, 416, 865 }, + { 559, 1005, 819, 914, 710, 770, 1418, 920, 838, 1435 }, + { 415, 1258, 1245, 870, 1278, 3067, 770, 1021, 1287, 522 }, + { 406, 990, 601, 1009, 1265, 1265, 1267, 759, 1017, 1277 }, + { 968, 1182, 1329, 788, 1032, 1292, 1705, 1714, 203, 1403 }, + { 732, 877, 1279, 471, 901, 1161, 1545, 1294, 755, 755 } }, + { { 111, 931, 1378, 1185, 1933, 1648, 1148, 1714, 1873, 1307 }, + { 406, 414, 1030, 1023, 1910, 1404, 1313, 1647, 1509, 793 }, + { 342, 640, 575, 1088, 1241, 1349, 1161, 1350, 1756, 1502 }, + { 559, 766, 1185, 357, 1682, 1428, 1329, 1897, 1219, 802 }, + { 473, 909, 1164, 771, 719, 2508, 1427, 1432, 722, 782 }, + { 342, 892, 785, 1145, 1150, 794, 1296, 1550, 973, 1057 }, + { 208, 1036, 1326, 1343, 1606, 3395, 815, 1455, 1618, 712 }, + { 228, 928, 890, 1046, 3499, 1711, 994, 829, 1720, 1318 }, + { 768, 724, 1058, 636, 991, 1075, 1319, 1324, 616, 825 }, + { 305, 1167, 1358, 899, 1587, 1587, 987, 1988, 1332, 501 } } +}; + +//------------------------------------------------------------------------------ +// helper functions for residuals struct VP8Residual. + +void VP8InitResidual(int first, int coeff_type, + VP8Encoder* const enc, VP8Residual* const res) { + res->coeff_type = coeff_type; + res->prob = enc->proba_.coeffs_[coeff_type]; + res->stats = enc->proba_.stats_[coeff_type]; + res->costs = enc->proba_.remapped_costs_[coeff_type]; + res->first = first; +} + +//------------------------------------------------------------------------------ +// Mode costs + +int VP8GetCostLuma4(VP8EncIterator* const it, const int16_t levels[16]) { + const int x = (it->i4_ & 3), y = (it->i4_ >> 2); + VP8Residual res; + VP8Encoder* const enc = it->enc_; + int R = 0; + int ctx; + + VP8InitResidual(0, 3, enc, &res); + ctx = it->top_nz_[x] + it->left_nz_[y]; + VP8SetResidualCoeffs(levels, &res); + R += VP8GetResidualCost(ctx, &res); + return R; +} + +int VP8GetCostLuma16(VP8EncIterator* const it, const VP8ModeScore* const rd) { + VP8Residual res; + VP8Encoder* const enc = it->enc_; + int x, y; + int R = 0; + + VP8IteratorNzToBytes(it); // re-import the non-zero context + + // DC + VP8InitResidual(0, 1, enc, &res); + VP8SetResidualCoeffs(rd->y_dc_levels, &res); + R += VP8GetResidualCost(it->top_nz_[8] + it->left_nz_[8], &res); + + // AC + VP8InitResidual(1, 0, enc, &res); + for (y = 0; y < 4; ++y) { + for (x = 0; x < 4; ++x) { + const int ctx = it->top_nz_[x] + it->left_nz_[y]; + VP8SetResidualCoeffs(rd->y_ac_levels[x + y * 4], &res); + R += VP8GetResidualCost(ctx, &res); + it->top_nz_[x] = it->left_nz_[y] = (res.last >= 0); + } + } + return R; +} + +int VP8GetCostUV(VP8EncIterator* const it, const VP8ModeScore* const rd) { + VP8Residual res; + VP8Encoder* const enc = it->enc_; + int ch, x, y; + int R = 0; + + VP8IteratorNzToBytes(it); // re-import the non-zero context + + VP8InitResidual(0, 2, enc, &res); + for (ch = 0; ch <= 2; ch += 2) { + for (y = 0; y < 2; ++y) { + for (x = 0; x < 2; ++x) { + const int ctx = it->top_nz_[4 + ch + x] + it->left_nz_[4 + ch + y]; + VP8SetResidualCoeffs(rd->uv_levels[ch * 2 + x + y * 2], &res); + R += VP8GetResidualCost(ctx, &res); + it->top_nz_[4 + ch + x] = it->left_nz_[4 + ch + y] = (res.last >= 0); + } + } + } + return R; +} + + +//------------------------------------------------------------------------------ +// Recording of token probabilities. + +// We keep the table-free variant around for reference, in case. +#define USE_LEVEL_CODE_TABLE + +// Simulate block coding, but only record statistics. +// Note: no need to record the fixed probas. +int VP8RecordCoeffs(int ctx, const VP8Residual* const res) { + int n = res->first; + // should be stats[VP8EncBands[n]], but it's equivalent for n=0 or 1 + proba_t* s = res->stats[n][ctx]; + if (res->last < 0) { + VP8RecordStats(0, s + 0); + return 0; + } + while (n <= res->last) { + int v; + VP8RecordStats(1, s + 0); // order of record doesn't matter + while ((v = res->coeffs[n++]) == 0) { + VP8RecordStats(0, s + 1); + s = res->stats[VP8EncBands[n]][0]; + } + VP8RecordStats(1, s + 1); + if (!VP8RecordStats(2u < (unsigned int)(v + 1), s + 2)) { // v = -1 or 1 + s = res->stats[VP8EncBands[n]][1]; + } else { + v = abs(v); +#if !defined(USE_LEVEL_CODE_TABLE) + if (!VP8RecordStats(v > 4, s + 3)) { + if (VP8RecordStats(v != 2, s + 4)) + VP8RecordStats(v == 4, s + 5); + } else if (!VP8RecordStats(v > 10, s + 6)) { + VP8RecordStats(v > 6, s + 7); + } else if (!VP8RecordStats((v >= 3 + (8 << 2)), s + 8)) { + VP8RecordStats((v >= 3 + (8 << 1)), s + 9); + } else { + VP8RecordStats((v >= 3 + (8 << 3)), s + 10); + } +#else + if (v > MAX_VARIABLE_LEVEL) { + v = MAX_VARIABLE_LEVEL; + } + + { + const int bits = VP8LevelCodes[v - 1][1]; + int pattern = VP8LevelCodes[v - 1][0]; + int i; + for (i = 0; (pattern >>= 1) != 0; ++i) { + const int mask = 2 << i; + if (pattern & 1) VP8RecordStats(!!(bits & mask), s + 3 + i); + } + } +#endif + s = res->stats[VP8EncBands[n]][2]; + } + } + if (n < 16) VP8RecordStats(0, s + 0); + return 1; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/enc/cost.h b/3rdparty/libwebp/enc/cost_enc.h similarity index 55% rename from 3rdparty/libwebp/enc/cost.h rename to 3rdparty/libwebp/enc/cost_enc.h index 7d7c2c79a2..99e4b37aa3 100644 --- a/3rdparty/libwebp/enc/cost.h +++ b/3rdparty/libwebp/enc/cost_enc.h @@ -14,15 +14,46 @@ #ifndef WEBP_ENC_COST_H_ #define WEBP_ENC_COST_H_ -#include "./vp8enci.h" +#include +#include +#include "./vp8i_enc.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -// approximate cost per level: -extern const uint16_t VP8LevelFixedCosts[MAX_LEVEL + 1]; -extern const uint16_t VP8EntropyCost[256]; // 8bit fixed-point log(p) +// On-the-fly info about the current set of residuals. Handy to avoid +// passing zillions of params. +typedef struct VP8Residual VP8Residual; +struct VP8Residual { + int first; + int last; + const int16_t* coeffs; + + int coeff_type; + ProbaArray* prob; + StatsArray* stats; + CostArrayPtr costs; +}; + +void VP8InitResidual(int first, int coeff_type, + VP8Encoder* const enc, VP8Residual* const res); + +int VP8RecordCoeffs(int ctx, const VP8Residual* const res); + +// Record proba context used. +static WEBP_INLINE int VP8RecordStats(int bit, proba_t* const stats) { + proba_t p = *stats; + // An overflow is inbound. Note we handle this at 0xfffe0000u instead of + // 0xffff0000u to make sure p + 1u does not overflow. + if (p >= 0xfffe0000u) { + p = ((p + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2. + } + // record bit count (lower 16 bits) and increment total count (upper 16 bits). + p += 0x00010000u + bit; + *stats = p; + return bit; +} // Cost of coding one event with probability 'proba'. static WEBP_INLINE int VP8BitCost(int bit, uint8_t proba) { @@ -31,7 +62,7 @@ static WEBP_INLINE int VP8BitCost(int bit, uint8_t proba) { // Level cost calculations extern const uint16_t VP8LevelCodes[MAX_VARIABLE_LEVEL][2]; -void VP8CalculateLevelCosts(VP8Proba* const proba); +void VP8CalculateLevelCosts(VP8EncProba* const proba); static WEBP_INLINE int VP8LevelCost(const uint16_t* const table, int level) { return VP8LevelFixedCosts[level] + table[(level > MAX_VARIABLE_LEVEL) ? MAX_VARIABLE_LEVEL : level]; @@ -44,7 +75,7 @@ extern const uint16_t VP8FixedCostsI4[NUM_BMODES][NUM_BMODES][NUM_BMODES]; //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/enc/delta_palettization_enc.c b/3rdparty/libwebp/enc/delta_palettization_enc.c new file mode 100644 index 0000000000..eaf0f050ea --- /dev/null +++ b/3rdparty/libwebp/enc/delta_palettization_enc.c @@ -0,0 +1,455 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Mislav Bradac (mislavm@google.com) +// + +#include "./delta_palettization_enc.h" + +#ifdef WEBP_EXPERIMENTAL_FEATURES +#include "../webp/types.h" +#include "../dsp/lossless.h" + +#define MK_COL(r, g, b) (((r) << 16) + ((g) << 8) + (b)) + +// Format allows palette up to 256 entries, but more palette entries produce +// bigger entropy. In the future it will probably be useful to add more entries +// that are far from the origin of the palette or choose remaining entries +// dynamically. +#define DELTA_PALETTE_SIZE 226 + +// Palette used for delta_palettization. Entries are roughly sorted by distance +// of their signed equivalents from the origin. +static const uint32_t kDeltaPalette[DELTA_PALETTE_SIZE] = { + MK_COL(0u, 0u, 0u), + MK_COL(255u, 255u, 255u), + MK_COL(1u, 1u, 1u), + MK_COL(254u, 254u, 254u), + MK_COL(2u, 2u, 2u), + MK_COL(4u, 4u, 4u), + MK_COL(252u, 252u, 252u), + MK_COL(250u, 0u, 0u), + MK_COL(0u, 250u, 0u), + MK_COL(0u, 0u, 250u), + MK_COL(6u, 0u, 0u), + MK_COL(0u, 6u, 0u), + MK_COL(0u, 0u, 6u), + MK_COL(0u, 0u, 248u), + MK_COL(0u, 0u, 8u), + MK_COL(0u, 248u, 0u), + MK_COL(0u, 248u, 248u), + MK_COL(0u, 248u, 8u), + MK_COL(0u, 8u, 0u), + MK_COL(0u, 8u, 248u), + MK_COL(0u, 8u, 8u), + MK_COL(8u, 8u, 8u), + MK_COL(248u, 0u, 0u), + MK_COL(248u, 0u, 248u), + MK_COL(248u, 0u, 8u), + MK_COL(248u, 248u, 0u), + MK_COL(248u, 8u, 0u), + MK_COL(8u, 0u, 0u), + MK_COL(8u, 0u, 248u), + MK_COL(8u, 0u, 8u), + MK_COL(8u, 248u, 0u), + MK_COL(8u, 8u, 0u), + MK_COL(23u, 23u, 23u), + MK_COL(13u, 13u, 13u), + MK_COL(232u, 232u, 232u), + MK_COL(244u, 244u, 244u), + MK_COL(245u, 245u, 250u), + MK_COL(50u, 50u, 50u), + MK_COL(204u, 204u, 204u), + MK_COL(236u, 236u, 236u), + MK_COL(16u, 16u, 16u), + MK_COL(240u, 16u, 16u), + MK_COL(16u, 240u, 16u), + MK_COL(240u, 240u, 16u), + MK_COL(16u, 16u, 240u), + MK_COL(240u, 16u, 240u), + MK_COL(16u, 240u, 240u), + MK_COL(240u, 240u, 240u), + MK_COL(0u, 0u, 232u), + MK_COL(0u, 232u, 0u), + MK_COL(232u, 0u, 0u), + MK_COL(0u, 0u, 24u), + MK_COL(0u, 24u, 0u), + MK_COL(24u, 0u, 0u), + MK_COL(32u, 32u, 32u), + MK_COL(224u, 32u, 32u), + MK_COL(32u, 224u, 32u), + MK_COL(224u, 224u, 32u), + MK_COL(32u, 32u, 224u), + MK_COL(224u, 32u, 224u), + MK_COL(32u, 224u, 224u), + MK_COL(224u, 224u, 224u), + MK_COL(0u, 0u, 176u), + MK_COL(0u, 0u, 80u), + MK_COL(0u, 176u, 0u), + MK_COL(0u, 176u, 176u), + MK_COL(0u, 176u, 80u), + MK_COL(0u, 80u, 0u), + MK_COL(0u, 80u, 176u), + MK_COL(0u, 80u, 80u), + MK_COL(176u, 0u, 0u), + MK_COL(176u, 0u, 176u), + MK_COL(176u, 0u, 80u), + MK_COL(176u, 176u, 0u), + MK_COL(176u, 80u, 0u), + MK_COL(80u, 0u, 0u), + MK_COL(80u, 0u, 176u), + MK_COL(80u, 0u, 80u), + MK_COL(80u, 176u, 0u), + MK_COL(80u, 80u, 0u), + MK_COL(0u, 0u, 152u), + MK_COL(0u, 0u, 104u), + MK_COL(0u, 152u, 0u), + MK_COL(0u, 152u, 152u), + MK_COL(0u, 152u, 104u), + MK_COL(0u, 104u, 0u), + MK_COL(0u, 104u, 152u), + MK_COL(0u, 104u, 104u), + MK_COL(152u, 0u, 0u), + MK_COL(152u, 0u, 152u), + MK_COL(152u, 0u, 104u), + MK_COL(152u, 152u, 0u), + MK_COL(152u, 104u, 0u), + MK_COL(104u, 0u, 0u), + MK_COL(104u, 0u, 152u), + MK_COL(104u, 0u, 104u), + MK_COL(104u, 152u, 0u), + MK_COL(104u, 104u, 0u), + MK_COL(216u, 216u, 216u), + MK_COL(216u, 216u, 40u), + MK_COL(216u, 216u, 176u), + MK_COL(216u, 216u, 80u), + MK_COL(216u, 40u, 216u), + MK_COL(216u, 40u, 40u), + MK_COL(216u, 40u, 176u), + MK_COL(216u, 40u, 80u), + MK_COL(216u, 176u, 216u), + MK_COL(216u, 176u, 40u), + MK_COL(216u, 176u, 176u), + MK_COL(216u, 176u, 80u), + MK_COL(216u, 80u, 216u), + MK_COL(216u, 80u, 40u), + MK_COL(216u, 80u, 176u), + MK_COL(216u, 80u, 80u), + MK_COL(40u, 216u, 216u), + MK_COL(40u, 216u, 40u), + MK_COL(40u, 216u, 176u), + MK_COL(40u, 216u, 80u), + MK_COL(40u, 40u, 216u), + MK_COL(40u, 40u, 40u), + MK_COL(40u, 40u, 176u), + MK_COL(40u, 40u, 80u), + MK_COL(40u, 176u, 216u), + MK_COL(40u, 176u, 40u), + MK_COL(40u, 176u, 176u), + MK_COL(40u, 176u, 80u), + MK_COL(40u, 80u, 216u), + MK_COL(40u, 80u, 40u), + MK_COL(40u, 80u, 176u), + MK_COL(40u, 80u, 80u), + MK_COL(80u, 216u, 216u), + MK_COL(80u, 216u, 40u), + MK_COL(80u, 216u, 176u), + MK_COL(80u, 216u, 80u), + MK_COL(80u, 40u, 216u), + MK_COL(80u, 40u, 40u), + MK_COL(80u, 40u, 176u), + MK_COL(80u, 40u, 80u), + MK_COL(80u, 176u, 216u), + MK_COL(80u, 176u, 40u), + MK_COL(80u, 176u, 176u), + MK_COL(80u, 176u, 80u), + MK_COL(80u, 80u, 216u), + MK_COL(80u, 80u, 40u), + MK_COL(80u, 80u, 176u), + MK_COL(80u, 80u, 80u), + MK_COL(0u, 0u, 192u), + MK_COL(0u, 0u, 64u), + MK_COL(0u, 0u, 128u), + MK_COL(0u, 192u, 0u), + MK_COL(0u, 192u, 192u), + MK_COL(0u, 192u, 64u), + MK_COL(0u, 192u, 128u), + MK_COL(0u, 64u, 0u), + MK_COL(0u, 64u, 192u), + MK_COL(0u, 64u, 64u), + MK_COL(0u, 64u, 128u), + MK_COL(0u, 128u, 0u), + MK_COL(0u, 128u, 192u), + MK_COL(0u, 128u, 64u), + MK_COL(0u, 128u, 128u), + MK_COL(176u, 216u, 216u), + MK_COL(176u, 216u, 40u), + MK_COL(176u, 216u, 176u), + MK_COL(176u, 216u, 80u), + MK_COL(176u, 40u, 216u), + MK_COL(176u, 40u, 40u), + MK_COL(176u, 40u, 176u), + MK_COL(176u, 40u, 80u), + MK_COL(176u, 176u, 216u), + MK_COL(176u, 176u, 40u), + MK_COL(176u, 176u, 176u), + MK_COL(176u, 176u, 80u), + MK_COL(176u, 80u, 216u), + MK_COL(176u, 80u, 40u), + MK_COL(176u, 80u, 176u), + MK_COL(176u, 80u, 80u), + MK_COL(192u, 0u, 0u), + MK_COL(192u, 0u, 192u), + MK_COL(192u, 0u, 64u), + MK_COL(192u, 0u, 128u), + MK_COL(192u, 192u, 0u), + MK_COL(192u, 192u, 192u), + MK_COL(192u, 192u, 64u), + MK_COL(192u, 192u, 128u), + MK_COL(192u, 64u, 0u), + MK_COL(192u, 64u, 192u), + MK_COL(192u, 64u, 64u), + MK_COL(192u, 64u, 128u), + MK_COL(192u, 128u, 0u), + MK_COL(192u, 128u, 192u), + MK_COL(192u, 128u, 64u), + MK_COL(192u, 128u, 128u), + MK_COL(64u, 0u, 0u), + MK_COL(64u, 0u, 192u), + MK_COL(64u, 0u, 64u), + MK_COL(64u, 0u, 128u), + MK_COL(64u, 192u, 0u), + MK_COL(64u, 192u, 192u), + MK_COL(64u, 192u, 64u), + MK_COL(64u, 192u, 128u), + MK_COL(64u, 64u, 0u), + MK_COL(64u, 64u, 192u), + MK_COL(64u, 64u, 64u), + MK_COL(64u, 64u, 128u), + MK_COL(64u, 128u, 0u), + MK_COL(64u, 128u, 192u), + MK_COL(64u, 128u, 64u), + MK_COL(64u, 128u, 128u), + MK_COL(128u, 0u, 0u), + MK_COL(128u, 0u, 192u), + MK_COL(128u, 0u, 64u), + MK_COL(128u, 0u, 128u), + MK_COL(128u, 192u, 0u), + MK_COL(128u, 192u, 192u), + MK_COL(128u, 192u, 64u), + MK_COL(128u, 192u, 128u), + MK_COL(128u, 64u, 0u), + MK_COL(128u, 64u, 192u), + MK_COL(128u, 64u, 64u), + MK_COL(128u, 64u, 128u), + MK_COL(128u, 128u, 0u), + MK_COL(128u, 128u, 192u), + MK_COL(128u, 128u, 64u), + MK_COL(128u, 128u, 128u), +}; + +#undef MK_COL + +//------------------------------------------------------------------------------ +// TODO(skal): move the functions to dsp/lossless.c when the correct +// granularity is found. For now, we'll just copy-paste some useful bits +// here instead. + +// In-place sum of each component with mod 256. +static WEBP_INLINE void AddPixelsEq(uint32_t* a, uint32_t b) { + const uint32_t alpha_and_green = (*a & 0xff00ff00u) + (b & 0xff00ff00u); + const uint32_t red_and_blue = (*a & 0x00ff00ffu) + (b & 0x00ff00ffu); + *a = (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu); +} + +static WEBP_INLINE uint32_t Clip255(uint32_t a) { + if (a < 256) { + return a; + } + // return 0, when a is a negative integer. + // return 255, when a is positive. + return ~a >> 24; +} + +// Delta palettization functions. +static WEBP_INLINE int Square(int x) { + return x * x; +} + +static WEBP_INLINE uint32_t Intensity(uint32_t a) { + return + 30 * ((a >> 16) & 0xff) + + 59 * ((a >> 8) & 0xff) + + 11 * ((a >> 0) & 0xff); +} + +static uint32_t CalcDist(uint32_t predicted_value, uint32_t actual_value, + uint32_t palette_entry) { + int i; + uint32_t distance = 0; + AddPixelsEq(&predicted_value, palette_entry); + for (i = 0; i < 32; i += 8) { + const int32_t av = (actual_value >> i) & 0xff; + const int32_t pv = (predicted_value >> i) & 0xff; + distance += Square(pv - av); + } + // We sum square of intensity difference with factor 10, but because Intensity + // returns 100 times real intensity we need to multiply differences of colors + // by 1000. + distance *= 1000u; + distance += Square(Intensity(predicted_value) + - Intensity(actual_value)); + return distance; +} + +static uint32_t Predict(int x, int y, uint32_t* image) { + const uint32_t t = (y == 0) ? ARGB_BLACK : image[x]; + const uint32_t l = (x == 0) ? ARGB_BLACK : image[x - 1]; + const uint32_t p = + (((((t >> 24) & 0xff) + ((l >> 24) & 0xff)) / 2) << 24) + + (((((t >> 16) & 0xff) + ((l >> 16) & 0xff)) / 2) << 16) + + (((((t >> 8) & 0xff) + ((l >> 8) & 0xff)) / 2) << 8) + + (((((t >> 0) & 0xff) + ((l >> 0) & 0xff)) / 2) << 0); + if (x == 0 && y == 0) return ARGB_BLACK; + if (x == 0) return t; + if (y == 0) return l; + return p; +} + +static WEBP_INLINE int AddSubtractComponentFullWithCoefficient( + int a, int b, int c) { + return Clip255(a + ((b - c) >> 2)); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractFullWithCoefficient( + uint32_t c0, uint32_t c1, uint32_t c2) { + const int a = AddSubtractComponentFullWithCoefficient( + c0 >> 24, c1 >> 24, c2 >> 24); + const int r = AddSubtractComponentFullWithCoefficient((c0 >> 16) & 0xff, + (c1 >> 16) & 0xff, + (c2 >> 16) & 0xff); + const int g = AddSubtractComponentFullWithCoefficient((c0 >> 8) & 0xff, + (c1 >> 8) & 0xff, + (c2 >> 8) & 0xff); + const int b = AddSubtractComponentFullWithCoefficient( + c0 & 0xff, c1 & 0xff, c2 & 0xff); + return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; +} + +//------------------------------------------------------------------------------ + +// Find palette entry with minimum error from difference of actual pixel value +// and predicted pixel value. Propagate error of pixel to its top and left pixel +// in src array. Write predicted_value + palette_entry to new_image. Return +// index of best palette entry. +static int FindBestPaletteEntry(uint32_t src, uint32_t predicted_value, + const uint32_t palette[], int palette_size) { + int i; + int idx = 0; + uint32_t best_distance = CalcDist(predicted_value, src, palette[0]); + for (i = 1; i < palette_size; ++i) { + const uint32_t distance = CalcDist(predicted_value, src, palette[i]); + if (distance < best_distance) { + best_distance = distance; + idx = i; + } + } + return idx; +} + +static void ApplyBestPaletteEntry(int x, int y, + uint32_t new_value, uint32_t palette_value, + uint32_t* src, int src_stride, + uint32_t* new_image) { + AddPixelsEq(&new_value, palette_value); + if (x > 0) { + src[x - 1] = ClampedAddSubtractFullWithCoefficient(src[x - 1], + new_value, src[x]); + } + if (y > 0) { + src[x - src_stride] = + ClampedAddSubtractFullWithCoefficient(src[x - src_stride], + new_value, src[x]); + } + new_image[x] = new_value; +} + +//------------------------------------------------------------------------------ +// Main entry point + +static WebPEncodingError ApplyDeltaPalette(uint32_t* src, uint32_t* dst, + uint32_t src_stride, + uint32_t dst_stride, + const uint32_t* palette, + int palette_size, + int width, int height, + int num_passes) { + int x, y; + WebPEncodingError err = VP8_ENC_OK; + uint32_t* new_image = (uint32_t*)WebPSafeMalloc(width, sizeof(*new_image)); + uint8_t* const tmp_row = (uint8_t*)WebPSafeMalloc(width, sizeof(*tmp_row)); + if (new_image == NULL || tmp_row == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + while (num_passes--) { + uint32_t* cur_src = src; + uint32_t* cur_dst = dst; + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { + const uint32_t predicted_value = Predict(x, y, new_image); + tmp_row[x] = FindBestPaletteEntry(cur_src[x], predicted_value, + palette, palette_size); + ApplyBestPaletteEntry(x, y, predicted_value, palette[tmp_row[x]], + cur_src, src_stride, new_image); + } + for (x = 0; x < width; ++x) { + cur_dst[x] = palette[tmp_row[x]]; + } + cur_src += src_stride; + cur_dst += dst_stride; + } + } + Error: + WebPSafeFree(new_image); + WebPSafeFree(tmp_row); + return err; +} + +// replaces enc->argb_ by a palettizable approximation of it, +// and generates optimal enc->palette_[] +WebPEncodingError WebPSearchOptimalDeltaPalette(VP8LEncoder* const enc) { + const WebPPicture* const pic = enc->pic_; + uint32_t* src = pic->argb; + uint32_t* dst = enc->argb_; + const int width = pic->width; + const int height = pic->height; + + WebPEncodingError err = VP8_ENC_OK; + memcpy(enc->palette_, kDeltaPalette, sizeof(kDeltaPalette)); + enc->palette_[DELTA_PALETTE_SIZE - 1] = src[0] - 0xff000000u; + enc->palette_size_ = DELTA_PALETTE_SIZE; + err = ApplyDeltaPalette(src, dst, pic->argb_stride, enc->current_width_, + enc->palette_, enc->palette_size_, + width, height, 2); + if (err != VP8_ENC_OK) goto Error; + + Error: + return err; +} + +#else // !WEBP_EXPERIMENTAL_FEATURES + +WebPEncodingError WebPSearchOptimalDeltaPalette(VP8LEncoder* const enc) { + (void)enc; + return VP8_ENC_ERROR_INVALID_CONFIGURATION; +} + +#endif // WEBP_EXPERIMENTAL_FEATURES diff --git a/3rdparty/libwebp/enc/delta_palettization_enc.h b/3rdparty/libwebp/enc/delta_palettization_enc.h new file mode 100644 index 0000000000..63048ec6e8 --- /dev/null +++ b/3rdparty/libwebp/enc/delta_palettization_enc.h @@ -0,0 +1,25 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Mislav Bradac (mislavm@google.com) +// + +#ifndef WEBP_ENC_DELTA_PALETTIZATION_H_ +#define WEBP_ENC_DELTA_PALETTIZATION_H_ + +#include "../webp/encode.h" +#include "../enc/vp8li_enc.h" + +// Replaces enc->argb_[] input by a palettizable approximation of it, +// and generates optimal enc->palette_[]. +// This function can revert enc->use_palette_ / enc->use_predict_ flag +// if delta-palettization is not producing expected saving. +WebPEncodingError WebPSearchOptimalDeltaPalette(VP8LEncoder* const enc); + +#endif // WEBP_ENC_DELTA_PALETTIZATION_H_ diff --git a/3rdparty/libwebp/enc/filter.c b/3rdparty/libwebp/enc/filter.c deleted file mode 100644 index aae2723d16..0000000000 --- a/3rdparty/libwebp/enc/filter.c +++ /dev/null @@ -1,411 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Selecting filter level -// -// Author: somnath@google.com (Somnath Banerjee) - -#include "./vp8enci.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -// NOTE: clip1, tables and InitTables are repeated entries of dsp.c -static uint8_t abs0[255 + 255 + 1]; // abs(i) -static uint8_t abs1[255 + 255 + 1]; // abs(i)>>1 -static int8_t sclip1[1020 + 1020 + 1]; // clips [-1020, 1020] to [-128, 127] -static int8_t sclip2[112 + 112 + 1]; // clips [-112, 112] to [-16, 15] -static uint8_t clip1[255 + 510 + 1]; // clips [-255,510] to [0,255] - -static int tables_ok = 0; - -static void InitTables(void) { - if (!tables_ok) { - int i; - for (i = -255; i <= 255; ++i) { - abs0[255 + i] = (i < 0) ? -i : i; - abs1[255 + i] = abs0[255 + i] >> 1; - } - for (i = -1020; i <= 1020; ++i) { - sclip1[1020 + i] = (i < -128) ? -128 : (i > 127) ? 127 : i; - } - for (i = -112; i <= 112; ++i) { - sclip2[112 + i] = (i < -16) ? -16 : (i > 15) ? 15 : i; - } - for (i = -255; i <= 255 + 255; ++i) { - clip1[255 + i] = (i < 0) ? 0 : (i > 255) ? 255 : i; - } - tables_ok = 1; - } -} - -//------------------------------------------------------------------------------ -// Edge filtering functions - -// 4 pixels in, 2 pixels out -static WEBP_INLINE void do_filter2(uint8_t* p, int step) { - const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; - const int a = 3 * (q0 - p0) + sclip1[1020 + p1 - q1]; - const int a1 = sclip2[112 + ((a + 4) >> 3)]; - const int a2 = sclip2[112 + ((a + 3) >> 3)]; - p[-step] = clip1[255 + p0 + a2]; - p[ 0] = clip1[255 + q0 - a1]; -} - -// 4 pixels in, 4 pixels out -static WEBP_INLINE void do_filter4(uint8_t* p, int step) { - const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; - const int a = 3 * (q0 - p0); - const int a1 = sclip2[112 + ((a + 4) >> 3)]; - const int a2 = sclip2[112 + ((a + 3) >> 3)]; - const int a3 = (a1 + 1) >> 1; - p[-2*step] = clip1[255 + p1 + a3]; - p[- step] = clip1[255 + p0 + a2]; - p[ 0] = clip1[255 + q0 - a1]; - p[ step] = clip1[255 + q1 - a3]; -} - -// high edge-variance -static WEBP_INLINE int hev(const uint8_t* p, int step, int thresh) { - const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; - return (abs0[255 + p1 - p0] > thresh) || (abs0[255 + q1 - q0] > thresh); -} - -static WEBP_INLINE int needs_filter(const uint8_t* p, int step, int thresh) { - const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; - return (2 * abs0[255 + p0 - q0] + abs1[255 + p1 - q1]) <= thresh; -} - -static WEBP_INLINE int needs_filter2(const uint8_t* p, - int step, int t, int it) { - const int p3 = p[-4*step], p2 = p[-3*step], p1 = p[-2*step], p0 = p[-step]; - const int q0 = p[0], q1 = p[step], q2 = p[2*step], q3 = p[3*step]; - if ((2 * abs0[255 + p0 - q0] + abs1[255 + p1 - q1]) > t) - return 0; - return abs0[255 + p3 - p2] <= it && abs0[255 + p2 - p1] <= it && - abs0[255 + p1 - p0] <= it && abs0[255 + q3 - q2] <= it && - abs0[255 + q2 - q1] <= it && abs0[255 + q1 - q0] <= it; -} - -//------------------------------------------------------------------------------ -// Simple In-loop filtering (Paragraph 15.2) - -static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { - int i; - for (i = 0; i < 16; ++i) { - if (needs_filter(p + i, stride, thresh)) { - do_filter2(p + i, stride); - } - } -} - -static void SimpleHFilter16(uint8_t* p, int stride, int thresh) { - int i; - for (i = 0; i < 16; ++i) { - if (needs_filter(p + i * stride, 1, thresh)) { - do_filter2(p + i * stride, 1); - } - } -} - -static void SimpleVFilter16i(uint8_t* p, int stride, int thresh) { - int k; - for (k = 3; k > 0; --k) { - p += 4 * stride; - SimpleVFilter16(p, stride, thresh); - } -} - -static void SimpleHFilter16i(uint8_t* p, int stride, int thresh) { - int k; - for (k = 3; k > 0; --k) { - p += 4; - SimpleHFilter16(p, stride, thresh); - } -} - -//------------------------------------------------------------------------------ -// Complex In-loop filtering (Paragraph 15.3) - -static WEBP_INLINE void FilterLoop24(uint8_t* p, - int hstride, int vstride, int size, - int thresh, int ithresh, int hev_thresh) { - while (size-- > 0) { - if (needs_filter2(p, hstride, thresh, ithresh)) { - if (hev(p, hstride, hev_thresh)) { - do_filter2(p, hstride); - } else { - do_filter4(p, hstride); - } - } - p += vstride; - } -} - -// on three inner edges -static void VFilter16i(uint8_t* p, int stride, - int thresh, int ithresh, int hev_thresh) { - int k; - for (k = 3; k > 0; --k) { - p += 4 * stride; - FilterLoop24(p, stride, 1, 16, thresh, ithresh, hev_thresh); - } -} - -static void HFilter16i(uint8_t* p, int stride, - int thresh, int ithresh, int hev_thresh) { - int k; - for (k = 3; k > 0; --k) { - p += 4; - FilterLoop24(p, 1, stride, 16, thresh, ithresh, hev_thresh); - } -} - -static void VFilter8i(uint8_t* u, uint8_t* v, int stride, - int thresh, int ithresh, int hev_thresh) { - FilterLoop24(u + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); - FilterLoop24(v + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); -} - -static void HFilter8i(uint8_t* u, uint8_t* v, int stride, - int thresh, int ithresh, int hev_thresh) { - FilterLoop24(u + 4, 1, stride, 8, thresh, ithresh, hev_thresh); - FilterLoop24(v + 4, 1, stride, 8, thresh, ithresh, hev_thresh); -} - -//------------------------------------------------------------------------------ - -void (*VP8EncVFilter16i)(uint8_t*, int, int, int, int) = VFilter16i; -void (*VP8EncHFilter16i)(uint8_t*, int, int, int, int) = HFilter16i; -void (*VP8EncVFilter8i)(uint8_t*, uint8_t*, int, int, int, int) = VFilter8i; -void (*VP8EncHFilter8i)(uint8_t*, uint8_t*, int, int, int, int) = HFilter8i; - -void (*VP8EncSimpleVFilter16i)(uint8_t*, int, int) = SimpleVFilter16i; -void (*VP8EncSimpleHFilter16i)(uint8_t*, int, int) = SimpleHFilter16i; - -//------------------------------------------------------------------------------ -// Paragraph 15.4: compute the inner-edge filtering strength - -static int GetILevel(int sharpness, int level) { - if (sharpness > 0) { - if (sharpness > 4) { - level >>= 2; - } else { - level >>= 1; - } - if (level > 9 - sharpness) { - level = 9 - sharpness; - } - } - if (level < 1) level = 1; - return level; -} - -static void DoFilter(const VP8EncIterator* const it, int level) { - const VP8Encoder* const enc = it->enc_; - const int ilevel = GetILevel(enc->config_->filter_sharpness, level); - const int limit = 2 * level + ilevel; - - uint8_t* const y_dst = it->yuv_out2_ + Y_OFF; - uint8_t* const u_dst = it->yuv_out2_ + U_OFF; - uint8_t* const v_dst = it->yuv_out2_ + V_OFF; - - // copy current block to yuv_out2_ - memcpy(y_dst, it->yuv_out_, YUV_SIZE * sizeof(uint8_t)); - - if (enc->filter_hdr_.simple_ == 1) { // simple - VP8EncSimpleHFilter16i(y_dst, BPS, limit); - VP8EncSimpleVFilter16i(y_dst, BPS, limit); - } else { // complex - const int hev_thresh = (level >= 40) ? 2 : (level >= 15) ? 1 : 0; - VP8EncHFilter16i(y_dst, BPS, limit, ilevel, hev_thresh); - VP8EncHFilter8i(u_dst, v_dst, BPS, limit, ilevel, hev_thresh); - VP8EncVFilter16i(y_dst, BPS, limit, ilevel, hev_thresh); - VP8EncVFilter8i(u_dst, v_dst, BPS, limit, ilevel, hev_thresh); - } -} - -//------------------------------------------------------------------------------ -// SSIM metric - -enum { KERNEL = 3 }; -static const double kMinValue = 1.e-10; // minimal threshold - -void VP8SSIMAddStats(const DistoStats* const src, DistoStats* const dst) { - dst->w += src->w; - dst->xm += src->xm; - dst->ym += src->ym; - dst->xxm += src->xxm; - dst->xym += src->xym; - dst->yym += src->yym; -} - -static void VP8SSIMAccumulate(const uint8_t* src1, int stride1, - const uint8_t* src2, int stride2, - int xo, int yo, int W, int H, - DistoStats* const stats) { - const int ymin = (yo - KERNEL < 0) ? 0 : yo - KERNEL; - const int ymax = (yo + KERNEL > H - 1) ? H - 1 : yo + KERNEL; - const int xmin = (xo - KERNEL < 0) ? 0 : xo - KERNEL; - const int xmax = (xo + KERNEL > W - 1) ? W - 1 : xo + KERNEL; - int x, y; - src1 += ymin * stride1; - src2 += ymin * stride2; - for (y = ymin; y <= ymax; ++y, src1 += stride1, src2 += stride2) { - for (x = xmin; x <= xmax; ++x) { - const int s1 = src1[x]; - const int s2 = src2[x]; - stats->w += 1; - stats->xm += s1; - stats->ym += s2; - stats->xxm += s1 * s1; - stats->xym += s1 * s2; - stats->yym += s2 * s2; - } - } -} - -double VP8SSIMGet(const DistoStats* const stats) { - const double xmxm = stats->xm * stats->xm; - const double ymym = stats->ym * stats->ym; - const double xmym = stats->xm * stats->ym; - const double w2 = stats->w * stats->w; - double sxx = stats->xxm * stats->w - xmxm; - double syy = stats->yym * stats->w - ymym; - double sxy = stats->xym * stats->w - xmym; - double C1, C2; - double fnum; - double fden; - // small errors are possible, due to rounding. Clamp to zero. - if (sxx < 0.) sxx = 0.; - if (syy < 0.) syy = 0.; - C1 = 6.5025 * w2; - C2 = 58.5225 * w2; - fnum = (2 * xmym + C1) * (2 * sxy + C2); - fden = (xmxm + ymym + C1) * (sxx + syy + C2); - return (fden != 0.) ? fnum / fden : kMinValue; -} - -double VP8SSIMGetSquaredError(const DistoStats* const s) { - if (s->w > 0.) { - const double iw2 = 1. / (s->w * s->w); - const double sxx = s->xxm * s->w - s->xm * s->xm; - const double syy = s->yym * s->w - s->ym * s->ym; - const double sxy = s->xym * s->w - s->xm * s->ym; - const double SSE = iw2 * (sxx + syy - 2. * sxy); - if (SSE > kMinValue) return SSE; - } - return kMinValue; -} - -void VP8SSIMAccumulatePlane(const uint8_t* src1, int stride1, - const uint8_t* src2, int stride2, - int W, int H, DistoStats* const stats) { - int x, y; - for (y = 0; y < H; ++y) { - for (x = 0; x < W; ++x) { - VP8SSIMAccumulate(src1, stride1, src2, stride2, x, y, W, H, stats); - } - } -} - -static double GetMBSSIM(const uint8_t* yuv1, const uint8_t* yuv2) { - int x, y; - DistoStats s = { .0, .0, .0, .0, .0, .0 }; - - // compute SSIM in a 10 x 10 window - for (x = 3; x < 13; x++) { - for (y = 3; y < 13; y++) { - VP8SSIMAccumulate(yuv1 + Y_OFF, BPS, yuv2 + Y_OFF, BPS, x, y, 16, 16, &s); - } - } - for (x = 1; x < 7; x++) { - for (y = 1; y < 7; y++) { - VP8SSIMAccumulate(yuv1 + U_OFF, BPS, yuv2 + U_OFF, BPS, x, y, 8, 8, &s); - VP8SSIMAccumulate(yuv1 + V_OFF, BPS, yuv2 + V_OFF, BPS, x, y, 8, 8, &s); - } - } - return VP8SSIMGet(&s); -} - -//------------------------------------------------------------------------------ -// Exposed APIs: Encoder should call the following 3 functions to adjust -// loop filter strength - -void VP8InitFilter(VP8EncIterator* const it) { - int s, i; - if (!it->lf_stats_) return; - - InitTables(); - for (s = 0; s < NUM_MB_SEGMENTS; s++) { - for (i = 0; i < MAX_LF_LEVELS; i++) { - (*it->lf_stats_)[s][i] = 0; - } - } -} - -void VP8StoreFilterStats(VP8EncIterator* const it) { - int d; - const int s = it->mb_->segment_; - const int level0 = it->enc_->dqm_[s].fstrength_; // TODO: ref_lf_delta[] - - // explore +/-quant range of values around level0 - const int delta_min = -it->enc_->dqm_[s].quant_; - const int delta_max = it->enc_->dqm_[s].quant_; - const int step_size = (delta_max - delta_min >= 4) ? 4 : 1; - - if (!it->lf_stats_) return; - - // NOTE: Currently we are applying filter only across the sublock edges - // There are two reasons for that. - // 1. Applying filter on macro block edges will change the pixels in - // the left and top macro blocks. That will be hard to restore - // 2. Macro Blocks on the bottom and right are not yet compressed. So we - // cannot apply filter on the right and bottom macro block edges. - if (it->mb_->type_ == 1 && it->mb_->skip_) return; - - // Always try filter level zero - (*it->lf_stats_)[s][0] += GetMBSSIM(it->yuv_in_, it->yuv_out_); - - for (d = delta_min; d <= delta_max; d += step_size) { - const int level = level0 + d; - if (level <= 0 || level >= MAX_LF_LEVELS) { - continue; - } - DoFilter(it, level); - (*it->lf_stats_)[s][level] += GetMBSSIM(it->yuv_in_, it->yuv_out2_); - } -} - -void VP8AdjustFilterStrength(VP8EncIterator* const it) { - int s; - VP8Encoder* const enc = it->enc_; - - if (!it->lf_stats_) { - return; - } - for (s = 0; s < NUM_MB_SEGMENTS; s++) { - int i, best_level = 0; - // Improvement over filter level 0 should be at least 1e-5 (relatively) - double best_v = 1.00001 * (*it->lf_stats_)[s][0]; - for (i = 1; i < MAX_LF_LEVELS; i++) { - const double v = (*it->lf_stats_)[s][i]; - if (v > best_v) { - best_v = v; - best_level = i; - } - } - enc->dqm_[s].fstrength_ = best_level; - } -} - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/filter_enc.c b/3rdparty/libwebp/enc/filter_enc.c new file mode 100644 index 0000000000..4bc367274c --- /dev/null +++ b/3rdparty/libwebp/enc/filter_enc.c @@ -0,0 +1,219 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Selecting filter level +// +// Author: somnath@google.com (Somnath Banerjee) + +#include +#include "./vp8i_enc.h" +#include "../dsp/dsp.h" + +// This table gives, for a given sharpness, the filtering strength to be +// used (at least) in order to filter a given edge step delta. +// This is constructed by brute force inspection: for all delta, we iterate +// over all possible filtering strength / thresh until needs_filter() returns +// true. +#define MAX_DELTA_SIZE 64 +static const uint8_t kLevelsFromDelta[8][MAX_DELTA_SIZE] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 }, + { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18, + 20, 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, + 44, 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 19, + 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, + 44, 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, + 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, + 45, 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, + 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, + 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 19, 20, + 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, + 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, 21, + 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, 45, + 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, + 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, + 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 } +}; + +int VP8FilterStrengthFromDelta(int sharpness, int delta) { + const int pos = (delta < MAX_DELTA_SIZE) ? delta : MAX_DELTA_SIZE - 1; + assert(sharpness >= 0 && sharpness <= 7); + return kLevelsFromDelta[sharpness][pos]; +} + +//------------------------------------------------------------------------------ +// Paragraph 15.4: compute the inner-edge filtering strength + +static int GetILevel(int sharpness, int level) { + if (sharpness > 0) { + if (sharpness > 4) { + level >>= 2; + } else { + level >>= 1; + } + if (level > 9 - sharpness) { + level = 9 - sharpness; + } + } + if (level < 1) level = 1; + return level; +} + +static void DoFilter(const VP8EncIterator* const it, int level) { + const VP8Encoder* const enc = it->enc_; + const int ilevel = GetILevel(enc->config_->filter_sharpness, level); + const int limit = 2 * level + ilevel; + + uint8_t* const y_dst = it->yuv_out2_ + Y_OFF_ENC; + uint8_t* const u_dst = it->yuv_out2_ + U_OFF_ENC; + uint8_t* const v_dst = it->yuv_out2_ + V_OFF_ENC; + + // copy current block to yuv_out2_ + memcpy(y_dst, it->yuv_out_, YUV_SIZE_ENC * sizeof(uint8_t)); + + if (enc->filter_hdr_.simple_ == 1) { // simple + VP8SimpleHFilter16i(y_dst, BPS, limit); + VP8SimpleVFilter16i(y_dst, BPS, limit); + } else { // complex + const int hev_thresh = (level >= 40) ? 2 : (level >= 15) ? 1 : 0; + VP8HFilter16i(y_dst, BPS, limit, ilevel, hev_thresh); + VP8HFilter8i(u_dst, v_dst, BPS, limit, ilevel, hev_thresh); + VP8VFilter16i(y_dst, BPS, limit, ilevel, hev_thresh); + VP8VFilter8i(u_dst, v_dst, BPS, limit, ilevel, hev_thresh); + } +} + +//------------------------------------------------------------------------------ +// SSIM metric for one macroblock + +static double GetMBSSIM(const uint8_t* yuv1, const uint8_t* yuv2) { + int x, y; + double sum = 0.; + + // compute SSIM in a 10 x 10 window + for (y = VP8_SSIM_KERNEL; y < 16 - VP8_SSIM_KERNEL; y++) { + for (x = VP8_SSIM_KERNEL; x < 16 - VP8_SSIM_KERNEL; x++) { + sum += VP8SSIMGetClipped(yuv1 + Y_OFF_ENC, BPS, yuv2 + Y_OFF_ENC, BPS, + x, y, 16, 16); + } + } + for (x = 1; x < 7; x++) { + for (y = 1; y < 7; y++) { + sum += VP8SSIMGetClipped(yuv1 + U_OFF_ENC, BPS, yuv2 + U_OFF_ENC, BPS, + x, y, 8, 8); + sum += VP8SSIMGetClipped(yuv1 + V_OFF_ENC, BPS, yuv2 + V_OFF_ENC, BPS, + x, y, 8, 8); + } + } + return sum; +} + +//------------------------------------------------------------------------------ +// Exposed APIs: Encoder should call the following 3 functions to adjust +// loop filter strength + +void VP8InitFilter(VP8EncIterator* const it) { + if (it->lf_stats_ != NULL) { + int s, i; + for (s = 0; s < NUM_MB_SEGMENTS; s++) { + for (i = 0; i < MAX_LF_LEVELS; i++) { + (*it->lf_stats_)[s][i] = 0; + } + } + VP8SSIMDspInit(); + } +} + +void VP8StoreFilterStats(VP8EncIterator* const it) { + int d; + VP8Encoder* const enc = it->enc_; + const int s = it->mb_->segment_; + const int level0 = enc->dqm_[s].fstrength_; + + // explore +/-quant range of values around level0 + const int delta_min = -enc->dqm_[s].quant_; + const int delta_max = enc->dqm_[s].quant_; + const int step_size = (delta_max - delta_min >= 4) ? 4 : 1; + + if (it->lf_stats_ == NULL) return; + + // NOTE: Currently we are applying filter only across the sublock edges + // There are two reasons for that. + // 1. Applying filter on macro block edges will change the pixels in + // the left and top macro blocks. That will be hard to restore + // 2. Macro Blocks on the bottom and right are not yet compressed. So we + // cannot apply filter on the right and bottom macro block edges. + if (it->mb_->type_ == 1 && it->mb_->skip_) return; + + // Always try filter level zero + (*it->lf_stats_)[s][0] += GetMBSSIM(it->yuv_in_, it->yuv_out_); + + for (d = delta_min; d <= delta_max; d += step_size) { + const int level = level0 + d; + if (level <= 0 || level >= MAX_LF_LEVELS) { + continue; + } + DoFilter(it, level); + (*it->lf_stats_)[s][level] += GetMBSSIM(it->yuv_in_, it->yuv_out2_); + } +} + +void VP8AdjustFilterStrength(VP8EncIterator* const it) { + VP8Encoder* const enc = it->enc_; + if (it->lf_stats_ != NULL) { + int s; + for (s = 0; s < NUM_MB_SEGMENTS; s++) { + int i, best_level = 0; + // Improvement over filter level 0 should be at least 1e-5 (relatively) + double best_v = 1.00001 * (*it->lf_stats_)[s][0]; + for (i = 1; i < MAX_LF_LEVELS; i++) { + const double v = (*it->lf_stats_)[s][i]; + if (v > best_v) { + best_v = v; + best_level = i; + } + } + enc->dqm_[s].fstrength_ = best_level; + } + } else if (enc->config_->filter_strength > 0) { + int max_level = 0; + int s; + for (s = 0; s < NUM_MB_SEGMENTS; s++) { + VP8SegmentInfo* const dqm = &enc->dqm_[s]; + // this '>> 3' accounts for some inverse WHT scaling + const int delta = (dqm->max_edge_ * dqm->y2_.q_[1]) >> 3; + const int level = + VP8FilterStrengthFromDelta(enc->filter_hdr_.sharpness_, delta); + if (level > dqm->fstrength_) { + dqm->fstrength_ = level; + } + if (max_level < dqm->fstrength_) { + max_level = dqm->fstrength_; + } + } + enc->filter_hdr_.level_ = max_level; + } +} + +// ----------------------------------------------------------------------------- diff --git a/3rdparty/libwebp/enc/frame.c b/3rdparty/libwebp/enc/frame_enc.c similarity index 60% rename from 3rdparty/libwebp/enc/frame.c rename to 3rdparty/libwebp/enc/frame_enc.c index c56abed75e..abef523bbf 100644 --- a/3rdparty/libwebp/enc/frame.c +++ b/3rdparty/libwebp/enc/frame_enc.c @@ -11,42 +11,77 @@ // // Author: Skal (pascal.massimino@gmail.com) -#include -#include #include #include -#include "./vp8enci.h" -#include "./cost.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./cost_enc.h" +#include "./vp8i_enc.h" +#include "../dsp/dsp.h" +#include "../webp/format_constants.h" // RIFF constants #define SEGMENT_VISU 0 #define DEBUG_SEARCH 0 // useful to track search convergence -// On-the-fly info about the current set of residuals. Handy to avoid -// passing zillions of params. -typedef struct { - int first; - int last; - const int16_t* coeffs; +//------------------------------------------------------------------------------ +// multi-pass convergence - int coeff_type; - ProbaArray* prob; - StatsArray* stats; - CostArray* cost; -} VP8Residual; +#define HEADER_SIZE_ESTIMATE (RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE + \ + VP8_FRAME_HEADER_SIZE) +#define DQ_LIMIT 0.4 // convergence is considered reached if dq < DQ_LIMIT +// we allow 2k of extra head-room in PARTITION0 limit. +#define PARTITION0_SIZE_LIMIT ((VP8_MAX_PARTITION0_SIZE - 2048ULL) << 11) + +typedef struct { // struct for organizing convergence in either size or PSNR + int is_first; + float dq; + float q, last_q; + double value, last_value; // PSNR or size + double target; + int do_size_search; +} PassStats; + +static int InitPassStats(const VP8Encoder* const enc, PassStats* const s) { + const uint64_t target_size = (uint64_t)enc->config_->target_size; + const int do_size_search = (target_size != 0); + const float target_PSNR = enc->config_->target_PSNR; + + s->is_first = 1; + s->dq = 10.f; + s->q = s->last_q = enc->config_->quality; + s->target = do_size_search ? (double)target_size + : (target_PSNR > 0.) ? target_PSNR + : 40.; // default, just in case + s->value = s->last_value = 0.; + s->do_size_search = do_size_search; + return do_size_search; +} + +static float Clamp(float v, float min, float max) { + return (v < min) ? min : (v > max) ? max : v; +} + +static float ComputeNextQ(PassStats* const s) { + float dq; + if (s->is_first) { + dq = (s->value > s->target) ? -s->dq : s->dq; + s->is_first = 0; + } else if (s->value != s->last_value) { + const double slope = (s->target - s->value) / (s->last_value - s->value); + dq = (float)(slope * (s->last_q - s->q)); + } else { + dq = 0.; // we're done?! + } + // Limit variable to avoid large swings. + s->dq = Clamp(dq, -30.f, 30.f); + s->last_q = s->q; + s->last_value = s->value; + s->q = Clamp(s->q + s->dq, 0.f, 100.f); + return s->q; +} //------------------------------------------------------------------------------ // Tables for level coding -const uint8_t VP8EncBands[16 + 1] = { - 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, - 0 // sentinel -}; - const uint8_t VP8Cat3[] = { 173, 148, 140 }; const uint8_t VP8Cat4[] = { 176, 155, 140, 135 }; const uint8_t VP8Cat5[] = { 180, 157, 141, 134, 130 }; @@ -57,7 +92,7 @@ const uint8_t VP8Cat6[] = // Reset the statistics about: number of skips, token proba, level cost,... static void ResetStats(VP8Encoder* const enc) { - VP8Proba* const proba = &enc->proba_; + VP8EncProba* const proba = &enc->proba_; VP8CalculateLevelCosts(proba); proba->nb_skip_ = 0; } @@ -73,7 +108,7 @@ static int CalcSkipProba(uint64_t nb, uint64_t total) { // Returns the bit-cost for coding the skip probability. static int FinalizeSkipProba(VP8Encoder* const enc) { - VP8Proba* const proba = &enc->proba_; + VP8EncProba* const proba = &enc->proba_; const int nb_mbs = enc->mb_w_ * enc->mb_h_; const int nb_events = proba->nb_skip_; int size; @@ -88,83 +123,6 @@ static int FinalizeSkipProba(VP8Encoder* const enc) { return size; } -//------------------------------------------------------------------------------ -// Recording of token probabilities. - -static void ResetTokenStats(VP8Encoder* const enc) { - VP8Proba* const proba = &enc->proba_; - memset(proba->stats_, 0, sizeof(proba->stats_)); -} - -// Record proba context used -static int Record(int bit, proba_t* const stats) { - proba_t p = *stats; - if (p >= 0xffff0000u) { // an overflow is inbound. - p = ((p + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2. - } - // record bit count (lower 16 bits) and increment total count (upper 16 bits). - p += 0x00010000u + bit; - *stats = p; - return bit; -} - -// We keep the table free variant around for reference, in case. -#define USE_LEVEL_CODE_TABLE - -// Simulate block coding, but only record statistics. -// Note: no need to record the fixed probas. -static int RecordCoeffs(int ctx, const VP8Residual* const res) { - int n = res->first; - // should be stats[VP8EncBands[n]], but it's equivalent for n=0 or 1 - proba_t* s = res->stats[n][ctx]; - if (res->last < 0) { - Record(0, s + 0); - return 0; - } - while (n <= res->last) { - int v; - Record(1, s + 0); // order of record doesn't matter - while ((v = res->coeffs[n++]) == 0) { - Record(0, s + 1); - s = res->stats[VP8EncBands[n]][0]; - } - Record(1, s + 1); - if (!Record(2u < (unsigned int)(v + 1), s + 2)) { // v = -1 or 1 - s = res->stats[VP8EncBands[n]][1]; - } else { - v = abs(v); -#if !defined(USE_LEVEL_CODE_TABLE) - if (!Record(v > 4, s + 3)) { - if (Record(v != 2, s + 4)) - Record(v == 4, s + 5); - } else if (!Record(v > 10, s + 6)) { - Record(v > 6, s + 7); - } else if (!Record((v >= 3 + (8 << 2)), s + 8)) { - Record((v >= 3 + (8 << 1)), s + 9); - } else { - Record((v >= 3 + (8 << 3)), s + 10); - } -#else - if (v > MAX_VARIABLE_LEVEL) - v = MAX_VARIABLE_LEVEL; - - { - const int bits = VP8LevelCodes[v - 1][1]; - int pattern = VP8LevelCodes[v - 1][0]; - int i; - for (i = 0; (pattern >>= 1) != 0; ++i) { - const int mask = 2 << i; - if (pattern & 1) Record(!!(bits & mask), s + 3 + i); - } - } -#endif - s = res->stats[VP8EncBands[n]][2]; - } - } - if (n < 16) Record(0, s + 0); - return 1; -} - // Collect statistics and deduce probabilities for next coding pass. // Return the total bit-cost for coding the probability updates. static int CalcTokenProba(int nb, int total) { @@ -177,7 +135,12 @@ static int BranchCost(int nb, int total, int proba) { return nb * VP8BitCost(1, proba) + (total - nb) * VP8BitCost(0, proba); } -static int FinalizeTokenProbas(VP8Proba* const proba) { +static void ResetTokenStats(VP8Encoder* const enc) { + VP8EncProba* const proba = &enc->proba_; + memset(proba->stats_, 0, sizeof(proba->stats_)); +} + +static int FinalizeTokenProbas(VP8EncProba* const proba) { int has_changed = 0; int size = 0; int t, b, c, p; @@ -222,6 +185,13 @@ static int GetProba(int a, int b) { : (255 * a + total / 2) / total; // rounded proba } +static void ResetSegments(VP8Encoder* const enc) { + int n; + for (n = 0; n < enc->mb_w_ * enc->mb_h_; ++n) { + enc->mb_info_[n].segment_ = 0; + } +} + static void SetSegmentProbas(VP8Encoder* const enc) { int p[NUM_MB_SEGMENTS] = { 0 }; int n; @@ -243,6 +213,7 @@ static void SetSegmentProbas(VP8Encoder* const enc) { enc->segment_hdr_.update_map_ = (probas[0] != 255) || (probas[1] != 255) || (probas[2] != 255); + if (!enc->segment_hdr_.update_map_) ResetSegments(enc); enc->segment_hdr_.size_ = p[0] * (VP8BitCost(0, probas[0]) + VP8BitCost(0, probas[1])) + p[1] * (VP8BitCost(0, probas[0]) + VP8BitCost(1, probas[1])) + @@ -254,142 +225,6 @@ static void SetSegmentProbas(VP8Encoder* const enc) { } } -//------------------------------------------------------------------------------ -// helper functions for residuals struct VP8Residual. - -static void InitResidual(int first, int coeff_type, - VP8Encoder* const enc, VP8Residual* const res) { - res->coeff_type = coeff_type; - res->prob = enc->proba_.coeffs_[coeff_type]; - res->stats = enc->proba_.stats_[coeff_type]; - res->cost = enc->proba_.level_cost_[coeff_type]; - res->first = first; -} - -static void SetResidualCoeffs(const int16_t* const coeffs, - VP8Residual* const res) { - int n; - res->last = -1; - for (n = 15; n >= res->first; --n) { - if (coeffs[n]) { - res->last = n; - break; - } - } - res->coeffs = coeffs; -} - -//------------------------------------------------------------------------------ -// Mode costs - -static int GetResidualCost(int ctx0, const VP8Residual* const res) { - int n = res->first; - // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 - int p0 = res->prob[n][ctx0][0]; - const uint16_t* t = res->cost[n][ctx0]; - int cost; - - if (res->last < 0) { - return VP8BitCost(0, p0); - } - cost = 0; - while (n < res->last) { - int v = res->coeffs[n]; - const int b = VP8EncBands[n + 1]; - ++n; - if (v == 0) { - // short-case for VP8LevelCost(t, 0) (note: VP8LevelFixedCosts[0] == 0): - cost += t[0]; - t = res->cost[b][0]; - continue; - } - v = abs(v); - cost += VP8BitCost(1, p0); - cost += VP8LevelCost(t, v); - { - const int ctx = (v == 1) ? 1 : 2; - p0 = res->prob[b][ctx][0]; - t = res->cost[b][ctx]; - } - } - // Last coefficient is always non-zero - { - const int v = abs(res->coeffs[n]); - assert(v != 0); - cost += VP8BitCost(1, p0); - cost += VP8LevelCost(t, v); - if (n < 15) { - const int b = VP8EncBands[n + 1]; - const int ctx = (v == 1) ? 1 : 2; - const int last_p0 = res->prob[b][ctx][0]; - cost += VP8BitCost(0, last_p0); - } - } - return cost; -} - -int VP8GetCostLuma4(VP8EncIterator* const it, const int16_t levels[16]) { - const int x = (it->i4_ & 3), y = (it->i4_ >> 2); - VP8Residual res; - VP8Encoder* const enc = it->enc_; - int R = 0; - int ctx; - - InitResidual(0, 3, enc, &res); - ctx = it->top_nz_[x] + it->left_nz_[y]; - SetResidualCoeffs(levels, &res); - R += GetResidualCost(ctx, &res); - return R; -} - -int VP8GetCostLuma16(VP8EncIterator* const it, const VP8ModeScore* const rd) { - VP8Residual res; - VP8Encoder* const enc = it->enc_; - int x, y; - int R = 0; - - VP8IteratorNzToBytes(it); // re-import the non-zero context - - // DC - InitResidual(0, 1, enc, &res); - SetResidualCoeffs(rd->y_dc_levels, &res); - R += GetResidualCost(it->top_nz_[8] + it->left_nz_[8], &res); - - // AC - InitResidual(1, 0, enc, &res); - for (y = 0; y < 4; ++y) { - for (x = 0; x < 4; ++x) { - const int ctx = it->top_nz_[x] + it->left_nz_[y]; - SetResidualCoeffs(rd->y_ac_levels[x + y * 4], &res); - R += GetResidualCost(ctx, &res); - it->top_nz_[x] = it->left_nz_[y] = (res.last >= 0); - } - } - return R; -} - -int VP8GetCostUV(VP8EncIterator* const it, const VP8ModeScore* const rd) { - VP8Residual res; - VP8Encoder* const enc = it->enc_; - int ch, x, y; - int R = 0; - - VP8IteratorNzToBytes(it); // re-import the non-zero context - - InitResidual(0, 2, enc, &res); - for (ch = 0; ch <= 2; ch += 2) { - for (y = 0; y < 2; ++y) { - for (x = 0; x < 2; ++x) { - const int ctx = it->top_nz_[4 + ch + x] + it->left_nz_[4 + ch + y]; - SetResidualCoeffs(rd->uv_levels[ch * 2 + x + y * 2], &res); - R += GetResidualCost(ctx, &res); - it->top_nz_[4 + ch + x] = it->left_nz_[4 + ch + y] = (res.last >= 0); - } - } - } - return R; -} - //------------------------------------------------------------------------------ // Coefficient coding @@ -413,8 +248,9 @@ static int PutCoeffs(VP8BitWriter* const bw, int ctx, const VP8Residual* res) { p = res->prob[VP8EncBands[n]][1]; } else { if (!VP8PutBit(bw, v > 4, p[3])) { - if (VP8PutBit(bw, v != 2, p[4])) + if (VP8PutBit(bw, v != 2, p[4])) { VP8PutBit(bw, v == 4, p[5]); + } } else if (!VP8PutBit(bw, v > 10, p[6])) { if (!VP8PutBit(bw, v > 6, p[7])) { VP8PutBit(bw, v == 6, 159); @@ -478,32 +314,32 @@ static void CodeResiduals(VP8BitWriter* const bw, VP8EncIterator* const it, pos1 = VP8BitWriterPos(bw); if (i16) { - InitResidual(0, 1, enc, &res); - SetResidualCoeffs(rd->y_dc_levels, &res); + VP8InitResidual(0, 1, enc, &res); + VP8SetResidualCoeffs(rd->y_dc_levels, &res); it->top_nz_[8] = it->left_nz_[8] = PutCoeffs(bw, it->top_nz_[8] + it->left_nz_[8], &res); - InitResidual(1, 0, enc, &res); + VP8InitResidual(1, 0, enc, &res); } else { - InitResidual(0, 3, enc, &res); + VP8InitResidual(0, 3, enc, &res); } // luma-AC for (y = 0; y < 4; ++y) { for (x = 0; x < 4; ++x) { const int ctx = it->top_nz_[x] + it->left_nz_[y]; - SetResidualCoeffs(rd->y_ac_levels[x + y * 4], &res); + VP8SetResidualCoeffs(rd->y_ac_levels[x + y * 4], &res); it->top_nz_[x] = it->left_nz_[y] = PutCoeffs(bw, ctx, &res); } } pos2 = VP8BitWriterPos(bw); // U/V - InitResidual(0, 2, enc, &res); + VP8InitResidual(0, 2, enc, &res); for (ch = 0; ch <= 2; ch += 2) { for (y = 0; y < 2; ++y) { for (x = 0; x < 2; ++x) { const int ctx = it->top_nz_[4 + ch + x] + it->left_nz_[4 + ch + y]; - SetResidualCoeffs(rd->uv_levels[ch * 2 + x + y * 2], &res); + VP8SetResidualCoeffs(rd->uv_levels[ch * 2 + x + y * 2], &res); it->top_nz_[4 + ch + x] = it->left_nz_[4 + ch + y] = PutCoeffs(bw, ctx, &res); } @@ -528,33 +364,33 @@ static void RecordResiduals(VP8EncIterator* const it, VP8IteratorNzToBytes(it); if (it->mb_->type_ == 1) { // i16x16 - InitResidual(0, 1, enc, &res); - SetResidualCoeffs(rd->y_dc_levels, &res); + VP8InitResidual(0, 1, enc, &res); + VP8SetResidualCoeffs(rd->y_dc_levels, &res); it->top_nz_[8] = it->left_nz_[8] = - RecordCoeffs(it->top_nz_[8] + it->left_nz_[8], &res); - InitResidual(1, 0, enc, &res); + VP8RecordCoeffs(it->top_nz_[8] + it->left_nz_[8], &res); + VP8InitResidual(1, 0, enc, &res); } else { - InitResidual(0, 3, enc, &res); + VP8InitResidual(0, 3, enc, &res); } // luma-AC for (y = 0; y < 4; ++y) { for (x = 0; x < 4; ++x) { const int ctx = it->top_nz_[x] + it->left_nz_[y]; - SetResidualCoeffs(rd->y_ac_levels[x + y * 4], &res); - it->top_nz_[x] = it->left_nz_[y] = RecordCoeffs(ctx, &res); + VP8SetResidualCoeffs(rd->y_ac_levels[x + y * 4], &res); + it->top_nz_[x] = it->left_nz_[y] = VP8RecordCoeffs(ctx, &res); } } // U/V - InitResidual(0, 2, enc, &res); + VP8InitResidual(0, 2, enc, &res); for (ch = 0; ch <= 2; ch += 2) { for (y = 0; y < 2; ++y) { for (x = 0; x < 2; ++x) { const int ctx = it->top_nz_[4 + ch + x] + it->left_nz_[4 + ch + y]; - SetResidualCoeffs(rd->uv_levels[ch * 2 + x + y * 2], &res); + VP8SetResidualCoeffs(rd->uv_levels[ch * 2 + x + y * 2], &res); it->top_nz_[4 + ch + x] = it->left_nz_[4 + ch + y] = - RecordCoeffs(ctx, &res); + VP8RecordCoeffs(ctx, &res); } } } @@ -567,8 +403,8 @@ static void RecordResiduals(VP8EncIterator* const it, #if !defined(DISABLE_TOKEN_BUFFER) -static void RecordTokens(VP8EncIterator* const it, const VP8ModeScore* const rd, - VP8TBuffer* const tokens) { +static int RecordTokens(VP8EncIterator* const it, const VP8ModeScore* const rd, + VP8TBuffer* const tokens) { int x, y, ch; VP8Residual res; VP8Encoder* const enc = it->enc_; @@ -576,44 +412,39 @@ static void RecordTokens(VP8EncIterator* const it, const VP8ModeScore* const rd, VP8IteratorNzToBytes(it); if (it->mb_->type_ == 1) { // i16x16 const int ctx = it->top_nz_[8] + it->left_nz_[8]; - InitResidual(0, 1, enc, &res); - SetResidualCoeffs(rd->y_dc_levels, &res); + VP8InitResidual(0, 1, enc, &res); + VP8SetResidualCoeffs(rd->y_dc_levels, &res); it->top_nz_[8] = it->left_nz_[8] = - VP8RecordCoeffTokens(ctx, 1, - res.first, res.last, res.coeffs, tokens); - RecordCoeffs(ctx, &res); - InitResidual(1, 0, enc, &res); + VP8RecordCoeffTokens(ctx, &res, tokens); + VP8InitResidual(1, 0, enc, &res); } else { - InitResidual(0, 3, enc, &res); + VP8InitResidual(0, 3, enc, &res); } // luma-AC for (y = 0; y < 4; ++y) { for (x = 0; x < 4; ++x) { const int ctx = it->top_nz_[x] + it->left_nz_[y]; - SetResidualCoeffs(rd->y_ac_levels[x + y * 4], &res); + VP8SetResidualCoeffs(rd->y_ac_levels[x + y * 4], &res); it->top_nz_[x] = it->left_nz_[y] = - VP8RecordCoeffTokens(ctx, res.coeff_type, - res.first, res.last, res.coeffs, tokens); - RecordCoeffs(ctx, &res); + VP8RecordCoeffTokens(ctx, &res, tokens); } } // U/V - InitResidual(0, 2, enc, &res); + VP8InitResidual(0, 2, enc, &res); for (ch = 0; ch <= 2; ch += 2) { for (y = 0; y < 2; ++y) { for (x = 0; x < 2; ++x) { const int ctx = it->top_nz_[4 + ch + x] + it->left_nz_[4 + ch + y]; - SetResidualCoeffs(rd->uv_levels[ch * 2 + x + y * 2], &res); + VP8SetResidualCoeffs(rd->uv_levels[ch * 2 + x + y * 2], &res); it->top_nz_[4 + ch + x] = it->left_nz_[4 + ch + y] = - VP8RecordCoeffTokens(ctx, 2, - res.first, res.last, res.coeffs, tokens); - RecordCoeffs(ctx, &res); + VP8RecordCoeffTokens(ctx, &res, tokens); } } } VP8IteratorBytesToNz(it); + return !tokens->error_; } #endif // !DISABLE_TOKEN_BUFFER @@ -644,9 +475,9 @@ static void StoreSSE(const VP8EncIterator* const it) { const uint8_t* const in = it->yuv_in_; const uint8_t* const out = it->yuv_out_; // Note: not totally accurate at boundary. And doesn't include in-loop filter. - enc->sse_[0] += VP8SSE16x16(in + Y_OFF, out + Y_OFF); - enc->sse_[1] += VP8SSE8x8(in + U_OFF, out + U_OFF); - enc->sse_[2] += VP8SSE8x8(in + V_OFF, out + V_OFF); + enc->sse_[0] += VP8SSE16x16(in + Y_OFF_ENC, out + Y_OFF_ENC); + enc->sse_[1] += VP8SSE8x8(in + U_OFF_ENC, out + U_OFF_ENC); + enc->sse_[2] += VP8SSE8x8(in + V_OFF_ENC, out + V_OFF_ENC); enc->sse_count_ += 16 * 16; } @@ -676,90 +507,93 @@ static void StoreSideInfo(const VP8EncIterator* const it) { } case 7: *info = mb->alpha_; break; default: *info = 0; break; - }; + } } #if SEGMENT_VISU // visualize segments and prediction modes - SetBlock(it->yuv_out_ + Y_OFF, mb->segment_ * 64, 16); - SetBlock(it->yuv_out_ + U_OFF, it->preds_[0] * 64, 8); - SetBlock(it->yuv_out_ + V_OFF, mb->uv_mode_ * 64, 8); + SetBlock(it->yuv_out_ + Y_OFF_ENC, mb->segment_ * 64, 16); + SetBlock(it->yuv_out_ + U_OFF_ENC, it->preds_[0] * 64, 8); + SetBlock(it->yuv_out_ + V_OFF_ENC, mb->uv_mode_ * 64, 8); #endif } +static double GetPSNR(uint64_t mse, uint64_t size) { + return (mse > 0 && size > 0) ? 10. * log10(255. * 255. * size / mse) : 99; +} + //------------------------------------------------------------------------------ // StatLoop(): only collect statistics (number of skips, token usage, ...). // This is used for deciding optimal probabilities. It also modifies the -// quantizer value if some target (size, PNSR) was specified. - -#define kHeaderSizeEstimate (15 + 20 + 10) // TODO: fix better +// quantizer value if some target (size, PSNR) was specified. static void SetLoopParams(VP8Encoder* const enc, float q) { // Make sure the quality parameter is inside valid bounds - if (q < 0.) { - q = 0; - } else if (q > 100.) { - q = 100; - } + q = Clamp(q, 0.f, 100.f); VP8SetSegmentParams(enc, q); // setup segment quantizations and filters SetSegmentProbas(enc); // compute segment probabilities ResetStats(enc); - ResetTokenStats(enc); - ResetSSE(enc); } -static int OneStatPass(VP8Encoder* const enc, float q, VP8RDLevel rd_opt, - int nb_mbs, float* const PSNR, int percent_delta) { +static uint64_t OneStatPass(VP8Encoder* const enc, VP8RDLevel rd_opt, + int nb_mbs, int percent_delta, + PassStats* const s) { VP8EncIterator it; uint64_t size = 0; + uint64_t size_p0 = 0; uint64_t distortion = 0; const uint64_t pixel_count = nb_mbs * 384; - SetLoopParams(enc, q); - VP8IteratorInit(enc, &it); + SetLoopParams(enc, s->q); do { VP8ModeScore info; - VP8IteratorImport(&it); + VP8IteratorImport(&it, NULL); if (VP8Decimate(&it, &info, rd_opt)) { // Just record the number of skips and act like skip_proba is not used. enc->proba_.nb_skip_++; } RecordResiduals(&it, &info); - size += info.R; + size += info.R + info.H; + size_p0 += info.H; distortion += info.D; - if (percent_delta && !VP8IteratorProgress(&it, percent_delta)) + if (percent_delta && !VP8IteratorProgress(&it, percent_delta)) { return 0; - } while (VP8IteratorNext(&it, it.yuv_out_) && --nb_mbs > 0); - size += FinalizeSkipProba(enc); - size += FinalizeTokenProbas(&enc->proba_); - size += enc->segment_hdr_.size_; - size = ((size + 1024) >> 11) + kHeaderSizeEstimate; + } + VP8IteratorSaveBoundary(&it); + } while (VP8IteratorNext(&it) && --nb_mbs > 0); - if (PSNR) { - *PSNR = (float)(10.* log10(255. * 255. * pixel_count / distortion)); + size_p0 += enc->segment_hdr_.size_; + if (s->do_size_search) { + size += FinalizeSkipProba(enc); + size += FinalizeTokenProbas(&enc->proba_); + size = ((size + size_p0 + 1024) >> 11) + HEADER_SIZE_ESTIMATE; + s->value = (double)size; + } else { + s->value = GetPSNR(distortion, pixel_count); } - return (int)size; + return size_p0; } -// successive refinement increments. -static const int dqs[] = { 20, 15, 10, 8, 6, 4, 2, 1, 0 }; - static int StatLoop(VP8Encoder* const enc) { const int method = enc->method_; const int do_search = enc->do_search_; const int fast_probe = ((method == 0 || method == 3) && !do_search); - float q = enc->config_->quality; - const int max_passes = enc->config_->pass; + int num_pass_left = enc->config_->pass; const int task_percent = 20; - const int percent_per_pass = (task_percent + max_passes / 2) / max_passes; + const int percent_per_pass = + (task_percent + num_pass_left / 2) / num_pass_left; const int final_percent = enc->percent_ + task_percent; - int pass; - int nb_mbs; + const VP8RDLevel rd_opt = + (method >= 3 || do_search) ? RD_OPT_BASIC : RD_OPT_NONE; + int nb_mbs = enc->mb_w_ * enc->mb_h_; + PassStats stats; + + InitPassStats(enc, &stats); + ResetTokenStats(enc); // Fast mode: quick analysis pass over few mbs. Better than nothing. - nb_mbs = enc->mb_w_ * enc->mb_h_; if (fast_probe) { if (method == 3) { // we need more stats for method 3 to be reliable. nb_mbs = (nb_mbs > 200) ? nb_mbs >> 1 : 100; @@ -768,37 +602,35 @@ static int StatLoop(VP8Encoder* const enc) { } } - // No target size: just do several pass without changing 'q' - if (!do_search) { - for (pass = 0; pass < max_passes; ++pass) { - const VP8RDLevel rd_opt = (method >= 3) ? RD_OPT_BASIC : RD_OPT_NONE; - if (!OneStatPass(enc, q, rd_opt, nb_mbs, NULL, percent_per_pass)) { - return 0; - } - } - } else { - // binary search for a size close to target - for (pass = 0; pass < max_passes && (dqs[pass] > 0); ++pass) { - float PSNR; - int criterion; - const int size = OneStatPass(enc, q, RD_OPT_BASIC, nb_mbs, &PSNR, - percent_per_pass); -#if DEBUG_SEARCH - printf("#%d size=%d PSNR=%.2f q=%.2f\n", pass, size, PSNR, q); + while (num_pass_left-- > 0) { + const int is_last_pass = (fabs(stats.dq) <= DQ_LIMIT) || + (num_pass_left == 0) || + (enc->max_i4_header_bits_ == 0); + const uint64_t size_p0 = + OneStatPass(enc, rd_opt, nb_mbs, percent_per_pass, &stats); + if (size_p0 == 0) return 0; +#if (DEBUG_SEARCH > 0) + printf("#%d value:%.1lf -> %.1lf q:%.2f -> %.2f\n", + num_pass_left, stats.last_value, stats.value, stats.last_q, stats.q); #endif - if (size == 0) return 0; - if (enc->config_->target_PSNR > 0) { - criterion = (PSNR < enc->config_->target_PSNR); - } else { - criterion = (size < enc->config_->target_size); - } - // dichotomize - if (criterion) { - q += dqs[pass]; - } else { - q -= dqs[pass]; - } + if (enc->max_i4_header_bits_ > 0 && size_p0 > PARTITION0_SIZE_LIMIT) { + ++num_pass_left; + enc->max_i4_header_bits_ >>= 1; // strengthen header bit limitation... + continue; // ...and start over } + if (is_last_pass) { + break; + } + // If no target size: just do several pass without changing 'q' + if (do_search) { + ComputeNextQ(&stats); + if (fabs(stats.dq) <= DQ_LIMIT) break; + } + } + if (!do_search || !stats.do_size_search) { + // Need to finalize probas now, since it wasn't done during the search. + FinalizeSkipProba(enc); + FinalizeTokenProbas(&enc->proba_); } VP8CalculateLevelCosts(&enc->proba_); // finalize costs return WebPReportProgress(enc->pic_, final_percent, &enc->percent_); @@ -820,7 +652,10 @@ static int PreLoopInitialize(VP8Encoder* const enc) { for (p = 0; ok && p < enc->num_parts_; ++p) { ok = VP8BitWriterInit(enc->parts_ + p, bytes_per_parts); } - if (!ok) VP8EncFreeBitWriters(enc); // malloc error occurred + if (!ok) { + VP8EncFreeBitWriters(enc); // malloc error occurred + WebPEncodingSetError(enc->pic_, VP8_ENC_ERROR_OUT_OF_MEMORY); + } return ok; } @@ -835,7 +670,7 @@ static int PostLoopFinalize(VP8EncIterator* const it, int ok) { } if (ok) { // All good. Finish up. - if (enc->pic_->stats) { // finalize byte counters... + if (enc->pic_->stats != NULL) { // finalize byte counters... int i, s; for (i = 0; i <= 2; ++i) { for (s = 0; s < NUM_MB_SEGMENTS; ++s) { @@ -877,7 +712,7 @@ int VP8EncLoop(VP8Encoder* const enc) { const int dont_use_skip = !enc->proba_.use_skip_proba_; const VP8RDLevel rd_opt = enc->rd_opt_level_; - VP8IteratorImport(&it); + VP8IteratorImport(&it, NULL); // Warning! order is important: first call VP8Decimate() and // *then* decide how to code the skip decision if there's one. if (!VP8Decimate(&it, &info, rd_opt) || dont_use_skip) { @@ -885,16 +720,12 @@ int VP8EncLoop(VP8Encoder* const enc) { } else { // reset predictors after a skip ResetAfterSkip(&it); } -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (enc->use_layer_) { - VP8EncCodeLayerBlock(&it); - } -#endif StoreSideInfo(&it); VP8StoreFilterStats(&it); VP8IteratorExport(&it); ok = VP8IteratorProgress(&it, 20); - } while (ok && VP8IteratorNext(&it, it.yuv_out_)); + VP8IteratorSaveBoundary(&it); + } while (ok && VP8IteratorNext(&it)); return PostLoopFinalize(&it, ok); } @@ -904,62 +735,109 @@ int VP8EncLoop(VP8Encoder* const enc) { #if !defined(DISABLE_TOKEN_BUFFER) -#define MIN_COUNT 96 // minimum number of macroblocks before updating stats +#define MIN_COUNT 96 // minimum number of macroblocks before updating stats int VP8EncTokenLoop(VP8Encoder* const enc) { - int ok; - // Roughly refresh the proba height times per pass + // Roughly refresh the proba eight times per pass int max_count = (enc->mb_w_ * enc->mb_h_) >> 3; - int cnt; + int num_pass_left = enc->config_->pass; + const int do_search = enc->do_search_; VP8EncIterator it; - VP8Proba* const proba = &enc->proba_; + VP8EncProba* const proba = &enc->proba_; const VP8RDLevel rd_opt = enc->rd_opt_level_; + const uint64_t pixel_count = enc->mb_w_ * enc->mb_h_ * 384; + PassStats stats; + int ok; + + InitPassStats(enc, &stats); + ok = PreLoopInitialize(enc); + if (!ok) return 0; if (max_count < MIN_COUNT) max_count = MIN_COUNT; - cnt = max_count; assert(enc->num_parts_ == 1); assert(enc->use_tokens_); assert(proba->use_skip_proba_ == 0); assert(rd_opt >= RD_OPT_BASIC); // otherwise, token-buffer won't be useful - assert(!enc->do_search_); // TODO(skal): handle pass and dichotomy + assert(num_pass_left > 0); - SetLoopParams(enc, enc->config_->quality); - - ok = PreLoopInitialize(enc); - if (!ok) return 0; - - VP8IteratorInit(enc, &it); - VP8InitFilter(&it); - do { - VP8ModeScore info; - VP8IteratorImport(&it); - if (--cnt < 0) { - FinalizeTokenProbas(proba); - VP8CalculateLevelCosts(proba); // refresh cost tables for rd-opt - cnt = max_count; + while (ok && num_pass_left-- > 0) { + const int is_last_pass = (fabs(stats.dq) <= DQ_LIMIT) || + (num_pass_left == 0) || + (enc->max_i4_header_bits_ == 0); + uint64_t size_p0 = 0; + uint64_t distortion = 0; + int cnt = max_count; + VP8IteratorInit(enc, &it); + SetLoopParams(enc, stats.q); + if (is_last_pass) { + ResetTokenStats(enc); + VP8InitFilter(&it); // don't collect stats until last pass (too costly) } - VP8Decimate(&it, &info, rd_opt); - RecordTokens(&it, &info, &enc->tokens_); -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (enc->use_layer_) { - VP8EncCodeLayerBlock(&it); + VP8TBufferClear(&enc->tokens_); + do { + VP8ModeScore info; + VP8IteratorImport(&it, NULL); + if (--cnt < 0) { + FinalizeTokenProbas(proba); + VP8CalculateLevelCosts(proba); // refresh cost tables for rd-opt + cnt = max_count; + } + VP8Decimate(&it, &info, rd_opt); + ok = RecordTokens(&it, &info, &enc->tokens_); + if (!ok) { + WebPEncodingSetError(enc->pic_, VP8_ENC_ERROR_OUT_OF_MEMORY); + break; + } + size_p0 += info.H; + distortion += info.D; + if (is_last_pass) { + StoreSideInfo(&it); + VP8StoreFilterStats(&it); + VP8IteratorExport(&it); + ok = VP8IteratorProgress(&it, 20); + } + VP8IteratorSaveBoundary(&it); + } while (ok && VP8IteratorNext(&it)); + if (!ok) break; + + size_p0 += enc->segment_hdr_.size_; + if (stats.do_size_search) { + uint64_t size = FinalizeTokenProbas(&enc->proba_); + size += VP8EstimateTokenSize(&enc->tokens_, + (const uint8_t*)proba->coeffs_); + size = (size + size_p0 + 1024) >> 11; // -> size in bytes + size += HEADER_SIZE_ESTIMATE; + stats.value = (double)size; + } else { // compute and store PSNR + stats.value = GetPSNR(distortion, pixel_count); } + +#if (DEBUG_SEARCH > 0) + printf("#%2d metric:%.1lf -> %.1lf last_q=%.2lf q=%.2lf dq=%.2lf\n", + num_pass_left, stats.last_value, stats.value, + stats.last_q, stats.q, stats.dq); #endif - StoreSideInfo(&it); - VP8StoreFilterStats(&it); - VP8IteratorExport(&it); - ok = VP8IteratorProgress(&it, 20); - } while (ok && VP8IteratorNext(&it, it.yuv_out_)); - - ok = ok && WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_); - + if (enc->max_i4_header_bits_ > 0 && size_p0 > PARTITION0_SIZE_LIMIT) { + ++num_pass_left; + enc->max_i4_header_bits_ >>= 1; // strengthen header bit limitation... + continue; // ...and start over + } + if (is_last_pass) { + break; // done + } + if (do_search) { + ComputeNextQ(&stats); // Adjust q + } + } if (ok) { - FinalizeTokenProbas(proba); + if (!stats.do_size_search) { + FinalizeTokenProbas(&enc->proba_); + } ok = VP8EmitTokens(&enc->tokens_, enc->parts_ + 0, (const uint8_t*)proba->coeffs_, 1); } - + ok = ok && WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_); return PostLoopFinalize(&it, ok); } @@ -974,6 +852,3 @@ int VP8EncTokenLoop(VP8Encoder* const enc) { //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/histogram.c b/3rdparty/libwebp/enc/histogram.c deleted file mode 100644 index 787ea5d183..0000000000 --- a/3rdparty/libwebp/enc/histogram.c +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Author: Jyrki Alakuijala (jyrki@google.com) -// -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include - -#include "./backward_references.h" -#include "./histogram.h" -#include "../dsp/lossless.h" -#include "../utils/utils.h" - -static void HistogramClear(VP8LHistogram* const p) { - memset(p->literal_, 0, sizeof(p->literal_)); - memset(p->red_, 0, sizeof(p->red_)); - memset(p->blue_, 0, sizeof(p->blue_)); - memset(p->alpha_, 0, sizeof(p->alpha_)); - memset(p->distance_, 0, sizeof(p->distance_)); - p->bit_cost_ = 0; -} - -void VP8LHistogramStoreRefs(const VP8LBackwardRefs* const refs, - VP8LHistogram* const histo) { - int i; - for (i = 0; i < refs->size; ++i) { - VP8LHistogramAddSinglePixOrCopy(histo, &refs->refs[i]); - } -} - -void VP8LHistogramCreate(VP8LHistogram* const p, - const VP8LBackwardRefs* const refs, - int palette_code_bits) { - if (palette_code_bits >= 0) { - p->palette_code_bits_ = palette_code_bits; - } - HistogramClear(p); - VP8LHistogramStoreRefs(refs, p); -} - -void VP8LHistogramInit(VP8LHistogram* const p, int palette_code_bits) { - p->palette_code_bits_ = palette_code_bits; - HistogramClear(p); -} - -VP8LHistogramSet* VP8LAllocateHistogramSet(int size, int cache_bits) { - int i; - VP8LHistogramSet* set; - VP8LHistogram* bulk; - const uint64_t total_size = sizeof(*set) - + (uint64_t)size * sizeof(*set->histograms) - + (uint64_t)size * sizeof(**set->histograms); - uint8_t* memory = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*memory)); - if (memory == NULL) return NULL; - - set = (VP8LHistogramSet*)memory; - memory += sizeof(*set); - set->histograms = (VP8LHistogram**)memory; - memory += size * sizeof(*set->histograms); - bulk = (VP8LHistogram*)memory; - set->max_size = size; - set->size = size; - for (i = 0; i < size; ++i) { - set->histograms[i] = bulk + i; - VP8LHistogramInit(set->histograms[i], cache_bits); - } - return set; -} - -// ----------------------------------------------------------------------------- - -void VP8LHistogramAddSinglePixOrCopy(VP8LHistogram* const histo, - const PixOrCopy* const v) { - if (PixOrCopyIsLiteral(v)) { - ++histo->alpha_[PixOrCopyLiteral(v, 3)]; - ++histo->red_[PixOrCopyLiteral(v, 2)]; - ++histo->literal_[PixOrCopyLiteral(v, 1)]; - ++histo->blue_[PixOrCopyLiteral(v, 0)]; - } else if (PixOrCopyIsCacheIdx(v)) { - int literal_ix = 256 + NUM_LENGTH_CODES + PixOrCopyCacheIdx(v); - ++histo->literal_[literal_ix]; - } else { - int code, extra_bits_count, extra_bits_value; - PrefixEncode(PixOrCopyLength(v), - &code, &extra_bits_count, &extra_bits_value); - ++histo->literal_[256 + code]; - PrefixEncode(PixOrCopyDistance(v), - &code, &extra_bits_count, &extra_bits_value); - ++histo->distance_[code]; - } -} - -static double BitsEntropy(const int* const array, int n) { - double retval = 0.; - int sum = 0; - int nonzeros = 0; - int max_val = 0; - int i; - double mix; - for (i = 0; i < n; ++i) { - if (array[i] != 0) { - sum += array[i]; - ++nonzeros; - retval -= VP8LFastSLog2(array[i]); - if (max_val < array[i]) { - max_val = array[i]; - } - } - } - retval += VP8LFastSLog2(sum); - - if (nonzeros < 5) { - if (nonzeros <= 1) { - return 0; - } - // Two symbols, they will be 0 and 1 in a Huffman code. - // Let's mix in a bit of entropy to favor good clustering when - // distributions of these are combined. - if (nonzeros == 2) { - return 0.99 * sum + 0.01 * retval; - } - // No matter what the entropy says, we cannot be better than min_limit - // with Huffman coding. I am mixing a bit of entropy into the - // min_limit since it produces much better (~0.5 %) compression results - // perhaps because of better entropy clustering. - if (nonzeros == 3) { - mix = 0.95; - } else { - mix = 0.7; // nonzeros == 4. - } - } else { - mix = 0.627; - } - - { - double min_limit = 2 * sum - max_val; - min_limit = mix * min_limit + (1.0 - mix) * retval; - return (retval < min_limit) ? min_limit : retval; - } -} - -// Returns the cost encode the rle-encoded entropy code. -// The constants in this function are experimental. -static double HuffmanCost(const int* const population, int length) { - // Small bias because Huffman code length is typically not stored in - // full length. - static const int kHuffmanCodeOfHuffmanCodeSize = CODE_LENGTH_CODES * 3; - static const double kSmallBias = 9.1; - double retval = kHuffmanCodeOfHuffmanCodeSize - kSmallBias; - int streak = 0; - int i = 0; - for (; i < length - 1; ++i) { - ++streak; - if (population[i] == population[i + 1]) { - continue; - } - last_streak_hack: - // population[i] points now to the symbol in the streak of same values. - if (streak > 3) { - if (population[i] == 0) { - retval += 1.5625 + 0.234375 * streak; - } else { - retval += 2.578125 + 0.703125 * streak; - } - } else { - if (population[i] == 0) { - retval += 1.796875 * streak; - } else { - retval += 3.28125 * streak; - } - } - streak = 0; - } - if (i == length - 1) { - ++streak; - goto last_streak_hack; - } - return retval; -} - -static double PopulationCost(const int* const population, int length) { - return BitsEntropy(population, length) + HuffmanCost(population, length); -} - -static double ExtraCost(const int* const population, int length) { - int i; - double cost = 0.; - for (i = 2; i < length - 2; ++i) cost += (i >> 1) * population[i + 2]; - return cost; -} - -// Estimates the Entropy + Huffman + other block overhead size cost. -double VP8LHistogramEstimateBits(const VP8LHistogram* const p) { - return PopulationCost(p->literal_, VP8LHistogramNumCodes(p)) - + PopulationCost(p->red_, 256) - + PopulationCost(p->blue_, 256) - + PopulationCost(p->alpha_, 256) - + PopulationCost(p->distance_, NUM_DISTANCE_CODES) - + ExtraCost(p->literal_ + 256, NUM_LENGTH_CODES) - + ExtraCost(p->distance_, NUM_DISTANCE_CODES); -} - -double VP8LHistogramEstimateBitsBulk(const VP8LHistogram* const p) { - return BitsEntropy(p->literal_, VP8LHistogramNumCodes(p)) - + BitsEntropy(p->red_, 256) - + BitsEntropy(p->blue_, 256) - + BitsEntropy(p->alpha_, 256) - + BitsEntropy(p->distance_, NUM_DISTANCE_CODES) - + ExtraCost(p->literal_ + 256, NUM_LENGTH_CODES) - + ExtraCost(p->distance_, NUM_DISTANCE_CODES); -} - -// ----------------------------------------------------------------------------- -// Various histogram combine/cost-eval functions - -// Adds 'in' histogram to 'out' -static void HistogramAdd(const VP8LHistogram* const in, - VP8LHistogram* const out) { - int i; - for (i = 0; i < PIX_OR_COPY_CODES_MAX; ++i) { - out->literal_[i] += in->literal_[i]; - } - for (i = 0; i < NUM_DISTANCE_CODES; ++i) { - out->distance_[i] += in->distance_[i]; - } - for (i = 0; i < 256; ++i) { - out->red_[i] += in->red_[i]; - out->blue_[i] += in->blue_[i]; - out->alpha_[i] += in->alpha_[i]; - } -} - -// Performs out = a + b, computing the cost C(a+b) - C(a) - C(b) while comparing -// to the threshold value 'cost_threshold'. The score returned is -// Score = C(a+b) - C(a) - C(b), where C(a) + C(b) is known and fixed. -// Since the previous score passed is 'cost_threshold', we only need to compare -// the partial cost against 'cost_threshold + C(a) + C(b)' to possibly bail-out -// early. -static double HistogramAddEval(const VP8LHistogram* const a, - const VP8LHistogram* const b, - VP8LHistogram* const out, - double cost_threshold) { - double cost = 0; - const double sum_cost = a->bit_cost_ + b->bit_cost_; - int i; - - cost_threshold += sum_cost; - - // palette_code_bits_ is part of the cost evaluation for literal_. - // TODO(skal): remove/simplify this palette_code_bits_? - out->palette_code_bits_ = - (a->palette_code_bits_ > b->palette_code_bits_) ? a->palette_code_bits_ : - b->palette_code_bits_; - for (i = 0; i < PIX_OR_COPY_CODES_MAX; ++i) { - out->literal_[i] = a->literal_[i] + b->literal_[i]; - } - cost += PopulationCost(out->literal_, VP8LHistogramNumCodes(out)); - cost += ExtraCost(out->literal_ + 256, NUM_LENGTH_CODES); - if (cost > cost_threshold) return cost; - - for (i = 0; i < 256; ++i) out->red_[i] = a->red_[i] + b->red_[i]; - cost += PopulationCost(out->red_, 256); - if (cost > cost_threshold) return cost; - - for (i = 0; i < 256; ++i) out->blue_[i] = a->blue_[i] + b->blue_[i]; - cost += PopulationCost(out->blue_, 256); - if (cost > cost_threshold) return cost; - - for (i = 0; i < NUM_DISTANCE_CODES; ++i) { - out->distance_[i] = a->distance_[i] + b->distance_[i]; - } - cost += PopulationCost(out->distance_, NUM_DISTANCE_CODES); - cost += ExtraCost(out->distance_, NUM_DISTANCE_CODES); - if (cost > cost_threshold) return cost; - - for (i = 0; i < 256; ++i) out->alpha_[i] = a->alpha_[i] + b->alpha_[i]; - cost += PopulationCost(out->alpha_, 256); - - out->bit_cost_ = cost; - return cost - sum_cost; -} - -// Same as HistogramAddEval(), except that the resulting histogram -// is not stored. Only the cost C(a+b) - C(a) is evaluated. We omit -// the term C(b) which is constant over all the evaluations. -static double HistogramAddThresh(const VP8LHistogram* const a, - const VP8LHistogram* const b, - double cost_threshold) { - int tmp[PIX_OR_COPY_CODES_MAX]; // <= max storage we'll need - int i; - double cost = -a->bit_cost_; - - for (i = 0; i < PIX_OR_COPY_CODES_MAX; ++i) { - tmp[i] = a->literal_[i] + b->literal_[i]; - } - // note that the tests are ordered so that the usually largest - // cost shares come first. - cost += PopulationCost(tmp, VP8LHistogramNumCodes(a)); - cost += ExtraCost(tmp + 256, NUM_LENGTH_CODES); - if (cost > cost_threshold) return cost; - - for (i = 0; i < 256; ++i) tmp[i] = a->red_[i] + b->red_[i]; - cost += PopulationCost(tmp, 256); - if (cost > cost_threshold) return cost; - - for (i = 0; i < 256; ++i) tmp[i] = a->blue_[i] + b->blue_[i]; - cost += PopulationCost(tmp, 256); - if (cost > cost_threshold) return cost; - - for (i = 0; i < NUM_DISTANCE_CODES; ++i) { - tmp[i] = a->distance_[i] + b->distance_[i]; - } - cost += PopulationCost(tmp, NUM_DISTANCE_CODES); - cost += ExtraCost(tmp, NUM_DISTANCE_CODES); - if (cost > cost_threshold) return cost; - - for (i = 0; i < 256; ++i) tmp[i] = a->alpha_[i] + b->alpha_[i]; - cost += PopulationCost(tmp, 256); - - return cost; -} - -// ----------------------------------------------------------------------------- - -static void HistogramBuildImage(int xsize, int histo_bits, - const VP8LBackwardRefs* const backward_refs, - VP8LHistogramSet* const image) { - int i; - int x = 0, y = 0; - const int histo_xsize = VP8LSubSampleSize(xsize, histo_bits); - VP8LHistogram** const histograms = image->histograms; - assert(histo_bits > 0); - for (i = 0; i < backward_refs->size; ++i) { - const PixOrCopy* const v = &backward_refs->refs[i]; - const int ix = (y >> histo_bits) * histo_xsize + (x >> histo_bits); - VP8LHistogramAddSinglePixOrCopy(histograms[ix], v); - x += PixOrCopyLength(v); - while (x >= xsize) { - x -= xsize; - ++y; - } - } -} - -static uint32_t MyRand(uint32_t *seed) { - *seed *= 16807U; - if (*seed == 0) { - *seed = 1; - } - return *seed; -} - -static int HistogramCombine(const VP8LHistogramSet* const in, - VP8LHistogramSet* const out, int iter_mult, - int num_pairs, int num_tries_no_success) { - int ok = 0; - int i, iter; - uint32_t seed = 0; - int tries_with_no_success = 0; - int out_size = in->size; - const int outer_iters = in->size * iter_mult; - const int min_cluster_size = 2; - VP8LHistogram* const histos = (VP8LHistogram*)malloc(2 * sizeof(*histos)); - VP8LHistogram* cur_combo = histos + 0; // trial merged histogram - VP8LHistogram* best_combo = histos + 1; // best merged histogram so far - if (histos == NULL) goto End; - - // Copy histograms from in[] to out[]. - assert(in->size <= out->size); - for (i = 0; i < in->size; ++i) { - in->histograms[i]->bit_cost_ = VP8LHistogramEstimateBits(in->histograms[i]); - *out->histograms[i] = *in->histograms[i]; - } - - // Collapse similar histograms in 'out'. - for (iter = 0; iter < outer_iters && out_size >= min_cluster_size; ++iter) { - double best_cost_diff = 0.; - int best_idx1 = -1, best_idx2 = 1; - int j; - const int num_tries = (num_pairs < out_size) ? num_pairs : out_size; - seed += iter; - for (j = 0; j < num_tries; ++j) { - double curr_cost_diff; - // Choose two histograms at random and try to combine them. - const uint32_t idx1 = MyRand(&seed) % out_size; - const uint32_t tmp = (j & 7) + 1; - const uint32_t diff = (tmp < 3) ? tmp : MyRand(&seed) % (out_size - 1); - const uint32_t idx2 = (idx1 + diff + 1) % out_size; - if (idx1 == idx2) { - continue; - } - // Calculate cost reduction on combining. - curr_cost_diff = HistogramAddEval(out->histograms[idx1], - out->histograms[idx2], - cur_combo, best_cost_diff); - if (curr_cost_diff < best_cost_diff) { // found a better pair? - { // swap cur/best combo histograms - VP8LHistogram* const tmp_histo = cur_combo; - cur_combo = best_combo; - best_combo = tmp_histo; - } - best_cost_diff = curr_cost_diff; - best_idx1 = idx1; - best_idx2 = idx2; - } - } - - if (best_idx1 >= 0) { - *out->histograms[best_idx1] = *best_combo; - // swap best_idx2 slot with last one (which is now unused) - --out_size; - if (best_idx2 != out_size) { - out->histograms[best_idx2] = out->histograms[out_size]; - out->histograms[out_size] = NULL; // just for sanity check. - } - tries_with_no_success = 0; - } - if (++tries_with_no_success >= num_tries_no_success) { - break; - } - } - out->size = out_size; - ok = 1; - - End: - free(histos); - return ok; -} - -// ----------------------------------------------------------------------------- -// Histogram refinement - -// What is the bit cost of moving square_histogram from cur_symbol to candidate. -static double HistogramDistance(const VP8LHistogram* const square_histogram, - const VP8LHistogram* const candidate, - double cost_threshold) { - return HistogramAddThresh(candidate, square_histogram, cost_threshold); -} - -// Find the best 'out' histogram for each of the 'in' histograms. -// Note: we assume that out[]->bit_cost_ is already up-to-date. -static void HistogramRemap(const VP8LHistogramSet* const in, - const VP8LHistogramSet* const out, - uint16_t* const symbols) { - int i; - for (i = 0; i < in->size; ++i) { - int best_out = 0; - double best_bits = - HistogramDistance(in->histograms[i], out->histograms[0], 1.e38); - int k; - for (k = 1; k < out->size; ++k) { - const double cur_bits = - HistogramDistance(in->histograms[i], out->histograms[k], best_bits); - if (cur_bits < best_bits) { - best_bits = cur_bits; - best_out = k; - } - } - symbols[i] = best_out; - } - - // Recompute each out based on raw and symbols. - for (i = 0; i < out->size; ++i) { - HistogramClear(out->histograms[i]); - } - for (i = 0; i < in->size; ++i) { - HistogramAdd(in->histograms[i], out->histograms[symbols[i]]); - } -} - -int VP8LGetHistoImageSymbols(int xsize, int ysize, - const VP8LBackwardRefs* const refs, - int quality, int histo_bits, int cache_bits, - VP8LHistogramSet* const image_in, - uint16_t* const histogram_symbols) { - int ok = 0; - const int histo_xsize = histo_bits ? VP8LSubSampleSize(xsize, histo_bits) : 1; - const int histo_ysize = histo_bits ? VP8LSubSampleSize(ysize, histo_bits) : 1; - const int histo_image_raw_size = histo_xsize * histo_ysize; - - // Heuristic params for HistogramCombine(). - const int num_tries_no_success = 8 + (quality >> 1); - const int iter_mult = (quality < 27) ? 1 : 1 + ((quality - 27) >> 4); - const int num_pairs = (quality < 25) ? 10 : (5 * quality) >> 3; - - VP8LHistogramSet* const image_out = - VP8LAllocateHistogramSet(histo_image_raw_size, cache_bits); - if (image_out == NULL) return 0; - - // Build histogram image. - HistogramBuildImage(xsize, histo_bits, refs, image_out); - // Collapse similar histograms. - if (!HistogramCombine(image_out, image_in, iter_mult, num_pairs, - num_tries_no_success)) { - goto Error; - } - // Find the optimal map from original histograms to the final ones. - HistogramRemap(image_out, image_in, histogram_symbols); - ok = 1; - -Error: - free(image_out); - return ok; -} diff --git a/3rdparty/libwebp/enc/histogram_enc.c b/3rdparty/libwebp/enc/histogram_enc.c new file mode 100644 index 0000000000..808b6f78ab --- /dev/null +++ b/3rdparty/libwebp/enc/histogram_enc.c @@ -0,0 +1,990 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Jyrki Alakuijala (jyrki@google.com) +// +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" +#endif + +#include + +#include "./backward_references_enc.h" +#include "./histogram_enc.h" +#include "../dsp/lossless.h" +#include "../dsp/lossless_common.h" +#include "../utils/utils.h" + +#define MAX_COST 1.e38 + +// Number of partitions for the three dominant (literal, red and blue) symbol +// costs. +#define NUM_PARTITIONS 4 +// The size of the bin-hash corresponding to the three dominant costs. +#define BIN_SIZE (NUM_PARTITIONS * NUM_PARTITIONS * NUM_PARTITIONS) +// Maximum number of histograms allowed in greedy combining algorithm. +#define MAX_HISTO_GREEDY 100 + +static void HistogramClear(VP8LHistogram* const p) { + uint32_t* const literal = p->literal_; + const int cache_bits = p->palette_code_bits_; + const int histo_size = VP8LGetHistogramSize(cache_bits); + memset(p, 0, histo_size); + p->palette_code_bits_ = cache_bits; + p->literal_ = literal; +} + +// Swap two histogram pointers. +static void HistogramSwap(VP8LHistogram** const A, VP8LHistogram** const B) { + VP8LHistogram* const tmp = *A; + *A = *B; + *B = tmp; +} + +static void HistogramCopy(const VP8LHistogram* const src, + VP8LHistogram* const dst) { + uint32_t* const dst_literal = dst->literal_; + const int dst_cache_bits = dst->palette_code_bits_; + const int histo_size = VP8LGetHistogramSize(dst_cache_bits); + assert(src->palette_code_bits_ == dst_cache_bits); + memcpy(dst, src, histo_size); + dst->literal_ = dst_literal; +} + +int VP8LGetHistogramSize(int cache_bits) { + const int literal_size = VP8LHistogramNumCodes(cache_bits); + const size_t total_size = sizeof(VP8LHistogram) + sizeof(int) * literal_size; + assert(total_size <= (size_t)0x7fffffff); + return (int)total_size; +} + +void VP8LFreeHistogram(VP8LHistogram* const histo) { + WebPSafeFree(histo); +} + +void VP8LFreeHistogramSet(VP8LHistogramSet* const histo) { + WebPSafeFree(histo); +} + +void VP8LHistogramStoreRefs(const VP8LBackwardRefs* const refs, + VP8LHistogram* const histo) { + VP8LRefsCursor c = VP8LRefsCursorInit(refs); + while (VP8LRefsCursorOk(&c)) { + VP8LHistogramAddSinglePixOrCopy(histo, c.cur_pos); + VP8LRefsCursorNext(&c); + } +} + +void VP8LHistogramCreate(VP8LHistogram* const p, + const VP8LBackwardRefs* const refs, + int palette_code_bits) { + if (palette_code_bits >= 0) { + p->palette_code_bits_ = palette_code_bits; + } + HistogramClear(p); + VP8LHistogramStoreRefs(refs, p); +} + +void VP8LHistogramInit(VP8LHistogram* const p, int palette_code_bits) { + p->palette_code_bits_ = palette_code_bits; + HistogramClear(p); +} + +VP8LHistogram* VP8LAllocateHistogram(int cache_bits) { + VP8LHistogram* histo = NULL; + const int total_size = VP8LGetHistogramSize(cache_bits); + uint8_t* const memory = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*memory)); + if (memory == NULL) return NULL; + histo = (VP8LHistogram*)memory; + // literal_ won't necessary be aligned. + histo->literal_ = (uint32_t*)(memory + sizeof(VP8LHistogram)); + VP8LHistogramInit(histo, cache_bits); + return histo; +} + +VP8LHistogramSet* VP8LAllocateHistogramSet(int size, int cache_bits) { + int i; + VP8LHistogramSet* set; + const int histo_size = VP8LGetHistogramSize(cache_bits); + const size_t total_size = + sizeof(*set) + size * (sizeof(*set->histograms) + + histo_size + WEBP_ALIGN_CST); + uint8_t* memory = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*memory)); + if (memory == NULL) return NULL; + + set = (VP8LHistogramSet*)memory; + memory += sizeof(*set); + set->histograms = (VP8LHistogram**)memory; + memory += size * sizeof(*set->histograms); + set->max_size = size; + set->size = size; + for (i = 0; i < size; ++i) { + memory = (uint8_t*)WEBP_ALIGN(memory); + set->histograms[i] = (VP8LHistogram*)memory; + // literal_ won't necessary be aligned. + set->histograms[i]->literal_ = (uint32_t*)(memory + sizeof(VP8LHistogram)); + VP8LHistogramInit(set->histograms[i], cache_bits); + memory += histo_size; + } + return set; +} + +// ----------------------------------------------------------------------------- + +void VP8LHistogramAddSinglePixOrCopy(VP8LHistogram* const histo, + const PixOrCopy* const v) { + if (PixOrCopyIsLiteral(v)) { + ++histo->alpha_[PixOrCopyLiteral(v, 3)]; + ++histo->red_[PixOrCopyLiteral(v, 2)]; + ++histo->literal_[PixOrCopyLiteral(v, 1)]; + ++histo->blue_[PixOrCopyLiteral(v, 0)]; + } else if (PixOrCopyIsCacheIdx(v)) { + const int literal_ix = + NUM_LITERAL_CODES + NUM_LENGTH_CODES + PixOrCopyCacheIdx(v); + ++histo->literal_[literal_ix]; + } else { + int code, extra_bits; + VP8LPrefixEncodeBits(PixOrCopyLength(v), &code, &extra_bits); + ++histo->literal_[NUM_LITERAL_CODES + code]; + VP8LPrefixEncodeBits(PixOrCopyDistance(v), &code, &extra_bits); + ++histo->distance_[code]; + } +} + +// ----------------------------------------------------------------------------- +// Entropy-related functions. + +static WEBP_INLINE double BitsEntropyRefine(const VP8LBitEntropy* entropy) { + double mix; + if (entropy->nonzeros < 5) { + if (entropy->nonzeros <= 1) { + return 0; + } + // Two symbols, they will be 0 and 1 in a Huffman code. + // Let's mix in a bit of entropy to favor good clustering when + // distributions of these are combined. + if (entropy->nonzeros == 2) { + return 0.99 * entropy->sum + 0.01 * entropy->entropy; + } + // No matter what the entropy says, we cannot be better than min_limit + // with Huffman coding. I am mixing a bit of entropy into the + // min_limit since it produces much better (~0.5 %) compression results + // perhaps because of better entropy clustering. + if (entropy->nonzeros == 3) { + mix = 0.95; + } else { + mix = 0.7; // nonzeros == 4. + } + } else { + mix = 0.627; + } + + { + double min_limit = 2 * entropy->sum - entropy->max_val; + min_limit = mix * min_limit + (1.0 - mix) * entropy->entropy; + return (entropy->entropy < min_limit) ? min_limit : entropy->entropy; + } +} + +double VP8LBitsEntropy(const uint32_t* const array, int n, + uint32_t* const trivial_symbol) { + VP8LBitEntropy entropy; + VP8LBitsEntropyUnrefined(array, n, &entropy); + if (trivial_symbol != NULL) { + *trivial_symbol = + (entropy.nonzeros == 1) ? entropy.nonzero_code : VP8L_NON_TRIVIAL_SYM; + } + + return BitsEntropyRefine(&entropy); +} + +static double InitialHuffmanCost(void) { + // Small bias because Huffman code length is typically not stored in + // full length. + static const int kHuffmanCodeOfHuffmanCodeSize = CODE_LENGTH_CODES * 3; + static const double kSmallBias = 9.1; + return kHuffmanCodeOfHuffmanCodeSize - kSmallBias; +} + +// Finalize the Huffman cost based on streak numbers and length type (<3 or >=3) +static double FinalHuffmanCost(const VP8LStreaks* const stats) { + // The constants in this function are experimental and got rounded from + // their original values in 1/8 when switched to 1/1024. + double retval = InitialHuffmanCost(); + // Second coefficient: Many zeros in the histogram are covered efficiently + // by a run-length encode. Originally 2/8. + retval += stats->counts[0] * 1.5625 + 0.234375 * stats->streaks[0][1]; + // Second coefficient: Constant values are encoded less efficiently, but still + // RLE'ed. Originally 6/8. + retval += stats->counts[1] * 2.578125 + 0.703125 * stats->streaks[1][1]; + // 0s are usually encoded more efficiently than non-0s. + // Originally 15/8. + retval += 1.796875 * stats->streaks[0][0]; + // Originally 26/8. + retval += 3.28125 * stats->streaks[1][0]; + return retval; +} + +// Get the symbol entropy for the distribution 'population'. +// Set 'trivial_sym', if there's only one symbol present in the distribution. +static double PopulationCost(const uint32_t* const population, int length, + uint32_t* const trivial_sym) { + VP8LBitEntropy bit_entropy; + VP8LStreaks stats; + VP8LGetEntropyUnrefined(population, length, &bit_entropy, &stats); + if (trivial_sym != NULL) { + *trivial_sym = (bit_entropy.nonzeros == 1) ? bit_entropy.nonzero_code + : VP8L_NON_TRIVIAL_SYM; + } + + return BitsEntropyRefine(&bit_entropy) + FinalHuffmanCost(&stats); +} + +// trivial_at_end is 1 if the two histograms only have one element that is +// non-zero: both the zero-th one, or both the last one. +static WEBP_INLINE double GetCombinedEntropy(const uint32_t* const X, + const uint32_t* const Y, + int length, int trivial_at_end) { + VP8LStreaks stats; + if (trivial_at_end) { + // This configuration is due to palettization that transforms an indexed + // pixel into 0xff000000 | (pixel << 8) in VP8LBundleColorMap. + // BitsEntropyRefine is 0 for histograms with only one non-zero value. + // Only FinalHuffmanCost needs to be evaluated. + memset(&stats, 0, sizeof(stats)); + // Deal with the non-zero value at index 0 or length-1. + stats.streaks[1][0] += 1; + // Deal with the following/previous zero streak. + stats.counts[0] += 1; + stats.streaks[0][1] += length - 1; + return FinalHuffmanCost(&stats); + } else { + VP8LBitEntropy bit_entropy; + VP8LGetCombinedEntropyUnrefined(X, Y, length, &bit_entropy, &stats); + + return BitsEntropyRefine(&bit_entropy) + FinalHuffmanCost(&stats); + } +} + +// Estimates the Entropy + Huffman + other block overhead size cost. +double VP8LHistogramEstimateBits(const VP8LHistogram* const p) { + return + PopulationCost( + p->literal_, VP8LHistogramNumCodes(p->palette_code_bits_), NULL) + + PopulationCost(p->red_, NUM_LITERAL_CODES, NULL) + + PopulationCost(p->blue_, NUM_LITERAL_CODES, NULL) + + PopulationCost(p->alpha_, NUM_LITERAL_CODES, NULL) + + PopulationCost(p->distance_, NUM_DISTANCE_CODES, NULL) + + VP8LExtraCost(p->literal_ + NUM_LITERAL_CODES, NUM_LENGTH_CODES) + + VP8LExtraCost(p->distance_, NUM_DISTANCE_CODES); +} + +// ----------------------------------------------------------------------------- +// Various histogram combine/cost-eval functions + +static int GetCombinedHistogramEntropy(const VP8LHistogram* const a, + const VP8LHistogram* const b, + double cost_threshold, + double* cost) { + const int palette_code_bits = a->palette_code_bits_; + int trivial_at_end = 0; + assert(a->palette_code_bits_ == b->palette_code_bits_); + *cost += GetCombinedEntropy(a->literal_, b->literal_, + VP8LHistogramNumCodes(palette_code_bits), 0); + *cost += VP8LExtraCostCombined(a->literal_ + NUM_LITERAL_CODES, + b->literal_ + NUM_LITERAL_CODES, + NUM_LENGTH_CODES); + if (*cost > cost_threshold) return 0; + + if (a->trivial_symbol_ != VP8L_NON_TRIVIAL_SYM && + a->trivial_symbol_ == b->trivial_symbol_) { + // A, R and B are all 0 or 0xff. + const uint32_t color_a = (a->trivial_symbol_ >> 24) & 0xff; + const uint32_t color_r = (a->trivial_symbol_ >> 16) & 0xff; + const uint32_t color_b = (a->trivial_symbol_ >> 0) & 0xff; + if ((color_a == 0 || color_a == 0xff) && + (color_r == 0 || color_r == 0xff) && + (color_b == 0 || color_b == 0xff)) { + trivial_at_end = 1; + } + } + + *cost += + GetCombinedEntropy(a->red_, b->red_, NUM_LITERAL_CODES, trivial_at_end); + if (*cost > cost_threshold) return 0; + + *cost += + GetCombinedEntropy(a->blue_, b->blue_, NUM_LITERAL_CODES, trivial_at_end); + if (*cost > cost_threshold) return 0; + + *cost += GetCombinedEntropy(a->alpha_, b->alpha_, NUM_LITERAL_CODES, + trivial_at_end); + if (*cost > cost_threshold) return 0; + + *cost += + GetCombinedEntropy(a->distance_, b->distance_, NUM_DISTANCE_CODES, 0); + *cost += + VP8LExtraCostCombined(a->distance_, b->distance_, NUM_DISTANCE_CODES); + if (*cost > cost_threshold) return 0; + + return 1; +} + +static WEBP_INLINE void HistogramAdd(const VP8LHistogram* const a, + const VP8LHistogram* const b, + VP8LHistogram* const out) { + VP8LHistogramAdd(a, b, out); + out->trivial_symbol_ = (a->trivial_symbol_ == b->trivial_symbol_) + ? a->trivial_symbol_ + : VP8L_NON_TRIVIAL_SYM; +} + +// Performs out = a + b, computing the cost C(a+b) - C(a) - C(b) while comparing +// to the threshold value 'cost_threshold'. The score returned is +// Score = C(a+b) - C(a) - C(b), where C(a) + C(b) is known and fixed. +// Since the previous score passed is 'cost_threshold', we only need to compare +// the partial cost against 'cost_threshold + C(a) + C(b)' to possibly bail-out +// early. +static double HistogramAddEval(const VP8LHistogram* const a, + const VP8LHistogram* const b, + VP8LHistogram* const out, + double cost_threshold) { + double cost = 0; + const double sum_cost = a->bit_cost_ + b->bit_cost_; + cost_threshold += sum_cost; + + if (GetCombinedHistogramEntropy(a, b, cost_threshold, &cost)) { + HistogramAdd(a, b, out); + out->bit_cost_ = cost; + out->palette_code_bits_ = a->palette_code_bits_; + } + + return cost - sum_cost; +} + +// Same as HistogramAddEval(), except that the resulting histogram +// is not stored. Only the cost C(a+b) - C(a) is evaluated. We omit +// the term C(b) which is constant over all the evaluations. +static double HistogramAddThresh(const VP8LHistogram* const a, + const VP8LHistogram* const b, + double cost_threshold) { + double cost = -a->bit_cost_; + GetCombinedHistogramEntropy(a, b, cost_threshold, &cost); + return cost; +} + +// ----------------------------------------------------------------------------- + +// The structure to keep track of cost range for the three dominant entropy +// symbols. +// TODO(skal): Evaluate if float can be used here instead of double for +// representing the entropy costs. +typedef struct { + double literal_max_; + double literal_min_; + double red_max_; + double red_min_; + double blue_max_; + double blue_min_; +} DominantCostRange; + +static void DominantCostRangeInit(DominantCostRange* const c) { + c->literal_max_ = 0.; + c->literal_min_ = MAX_COST; + c->red_max_ = 0.; + c->red_min_ = MAX_COST; + c->blue_max_ = 0.; + c->blue_min_ = MAX_COST; +} + +static void UpdateDominantCostRange( + const VP8LHistogram* const h, DominantCostRange* const c) { + if (c->literal_max_ < h->literal_cost_) c->literal_max_ = h->literal_cost_; + if (c->literal_min_ > h->literal_cost_) c->literal_min_ = h->literal_cost_; + if (c->red_max_ < h->red_cost_) c->red_max_ = h->red_cost_; + if (c->red_min_ > h->red_cost_) c->red_min_ = h->red_cost_; + if (c->blue_max_ < h->blue_cost_) c->blue_max_ = h->blue_cost_; + if (c->blue_min_ > h->blue_cost_) c->blue_min_ = h->blue_cost_; +} + +static void UpdateHistogramCost(VP8LHistogram* const h) { + uint32_t alpha_sym, red_sym, blue_sym; + const double alpha_cost = + PopulationCost(h->alpha_, NUM_LITERAL_CODES, &alpha_sym); + const double distance_cost = + PopulationCost(h->distance_, NUM_DISTANCE_CODES, NULL) + + VP8LExtraCost(h->distance_, NUM_DISTANCE_CODES); + const int num_codes = VP8LHistogramNumCodes(h->palette_code_bits_); + h->literal_cost_ = PopulationCost(h->literal_, num_codes, NULL) + + VP8LExtraCost(h->literal_ + NUM_LITERAL_CODES, + NUM_LENGTH_CODES); + h->red_cost_ = PopulationCost(h->red_, NUM_LITERAL_CODES, &red_sym); + h->blue_cost_ = PopulationCost(h->blue_, NUM_LITERAL_CODES, &blue_sym); + h->bit_cost_ = h->literal_cost_ + h->red_cost_ + h->blue_cost_ + + alpha_cost + distance_cost; + if ((alpha_sym | red_sym | blue_sym) == VP8L_NON_TRIVIAL_SYM) { + h->trivial_symbol_ = VP8L_NON_TRIVIAL_SYM; + } else { + h->trivial_symbol_ = + ((uint32_t)alpha_sym << 24) | (red_sym << 16) | (blue_sym << 0); + } +} + +static int GetBinIdForEntropy(double min, double max, double val) { + const double range = max - min; + if (range > 0.) { + const double delta = val - min; + return (int)((NUM_PARTITIONS - 1e-6) * delta / range); + } else { + return 0; + } +} + +static int GetHistoBinIndex(const VP8LHistogram* const h, + const DominantCostRange* const c, int low_effort) { + int bin_id = GetBinIdForEntropy(c->literal_min_, c->literal_max_, + h->literal_cost_); + assert(bin_id < NUM_PARTITIONS); + if (!low_effort) { + bin_id = bin_id * NUM_PARTITIONS + + GetBinIdForEntropy(c->red_min_, c->red_max_, h->red_cost_); + bin_id = bin_id * NUM_PARTITIONS + + GetBinIdForEntropy(c->blue_min_, c->blue_max_, h->blue_cost_); + assert(bin_id < BIN_SIZE); + } + return bin_id; +} + +// Construct the histograms from backward references. +static void HistogramBuild( + int xsize, int histo_bits, const VP8LBackwardRefs* const backward_refs, + VP8LHistogramSet* const image_histo) { + int x = 0, y = 0; + const int histo_xsize = VP8LSubSampleSize(xsize, histo_bits); + VP8LHistogram** const histograms = image_histo->histograms; + VP8LRefsCursor c = VP8LRefsCursorInit(backward_refs); + assert(histo_bits > 0); + while (VP8LRefsCursorOk(&c)) { + const PixOrCopy* const v = c.cur_pos; + const int ix = (y >> histo_bits) * histo_xsize + (x >> histo_bits); + VP8LHistogramAddSinglePixOrCopy(histograms[ix], v); + x += PixOrCopyLength(v); + while (x >= xsize) { + x -= xsize; + ++y; + } + VP8LRefsCursorNext(&c); + } +} + +// Copies the histograms and computes its bit_cost. +static void HistogramCopyAndAnalyze( + VP8LHistogramSet* const orig_histo, VP8LHistogramSet* const image_histo) { + int i; + const int histo_size = orig_histo->size; + VP8LHistogram** const orig_histograms = orig_histo->histograms; + VP8LHistogram** const histograms = image_histo->histograms; + for (i = 0; i < histo_size; ++i) { + VP8LHistogram* const histo = orig_histograms[i]; + UpdateHistogramCost(histo); + // Copy histograms from orig_histo[] to image_histo[]. + HistogramCopy(histo, histograms[i]); + } +} + +// Partition histograms to different entropy bins for three dominant (literal, +// red and blue) symbol costs and compute the histogram aggregate bit_cost. +static void HistogramAnalyzeEntropyBin(VP8LHistogramSet* const image_histo, + uint16_t* const bin_map, + int low_effort) { + int i; + VP8LHistogram** const histograms = image_histo->histograms; + const int histo_size = image_histo->size; + DominantCostRange cost_range; + DominantCostRangeInit(&cost_range); + + // Analyze the dominant (literal, red and blue) entropy costs. + for (i = 0; i < histo_size; ++i) { + UpdateDominantCostRange(histograms[i], &cost_range); + } + + // bin-hash histograms on three of the dominant (literal, red and blue) + // symbol costs and store the resulting bin_id for each histogram. + for (i = 0; i < histo_size; ++i) { + bin_map[i] = GetHistoBinIndex(histograms[i], &cost_range, low_effort); + } +} + +// Compact image_histo[] by merging some histograms with same bin_id together if +// it's advantageous. +static VP8LHistogram* HistogramCombineEntropyBin( + VP8LHistogramSet* const image_histo, + VP8LHistogram* cur_combo, + const uint16_t* const bin_map, int bin_map_size, int num_bins, + double combine_cost_factor, int low_effort) { + VP8LHistogram** const histograms = image_histo->histograms; + int idx; + // Work in-place: processed histograms are put at the beginning of + // image_histo[]. At the end, we just have to truncate the array. + int size = 0; + struct { + int16_t first; // position of the histogram that accumulates all + // histograms with the same bin_id + uint16_t num_combine_failures; // number of combine failures per bin_id + } bin_info[BIN_SIZE]; + + assert(num_bins <= BIN_SIZE); + for (idx = 0; idx < num_bins; ++idx) { + bin_info[idx].first = -1; + bin_info[idx].num_combine_failures = 0; + } + + for (idx = 0; idx < bin_map_size; ++idx) { + const int bin_id = bin_map[idx]; + const int first = bin_info[bin_id].first; + assert(size <= idx); + if (first == -1) { + // just move histogram #idx to its final position + histograms[size] = histograms[idx]; + bin_info[bin_id].first = size++; + } else if (low_effort) { + HistogramAdd(histograms[idx], histograms[first], histograms[first]); + } else { + // try to merge #idx into #first (both share the same bin_id) + const double bit_cost = histograms[idx]->bit_cost_; + const double bit_cost_thresh = -bit_cost * combine_cost_factor; + const double curr_cost_diff = + HistogramAddEval(histograms[first], histograms[idx], + cur_combo, bit_cost_thresh); + if (curr_cost_diff < bit_cost_thresh) { + // Try to merge two histograms only if the combo is a trivial one or + // the two candidate histograms are already non-trivial. + // For some images, 'try_combine' turns out to be false for a lot of + // histogram pairs. In that case, we fallback to combining + // histograms as usual to avoid increasing the header size. + const int try_combine = + (cur_combo->trivial_symbol_ != VP8L_NON_TRIVIAL_SYM) || + ((histograms[idx]->trivial_symbol_ == VP8L_NON_TRIVIAL_SYM) && + (histograms[first]->trivial_symbol_ == VP8L_NON_TRIVIAL_SYM)); + const int max_combine_failures = 32; + if (try_combine || + bin_info[bin_id].num_combine_failures >= max_combine_failures) { + // move the (better) merged histogram to its final slot + HistogramSwap(&cur_combo, &histograms[first]); + } else { + histograms[size++] = histograms[idx]; + ++bin_info[bin_id].num_combine_failures; + } + } else { + histograms[size++] = histograms[idx]; + } + } + } + image_histo->size = size; + if (low_effort) { + // for low_effort case, update the final cost when everything is merged + for (idx = 0; idx < size; ++idx) { + UpdateHistogramCost(histograms[idx]); + } + } + return cur_combo; +} + +static uint32_t MyRand(uint32_t* const seed) { + *seed = (*seed * 16807ull) & 0xffffffffu; + if (*seed == 0) { + *seed = 1; + } + return *seed; +} + +// ----------------------------------------------------------------------------- +// Histogram pairs priority queue + +// Pair of histograms. Negative idx1 value means that pair is out-of-date. +typedef struct { + int idx1; + int idx2; + double cost_diff; + double cost_combo; +} HistogramPair; + +typedef struct { + HistogramPair* queue; + int size; + int max_size; +} HistoQueue; + +static int HistoQueueInit(HistoQueue* const histo_queue, const int max_index) { + histo_queue->size = 0; + // max_index^2 for the queue size is safe. If you look at + // HistogramCombineGreedy, and imagine that UpdateQueueFront always pushes + // data to the queue, you insert at most: + // - max_index*(max_index-1)/2 (the first two for loops) + // - max_index - 1 in the last for loop at the first iteration of the while + // loop, max_index - 2 at the second iteration ... therefore + // max_index*(max_index-1)/2 overall too + histo_queue->max_size = max_index * max_index; + // We allocate max_size + 1 because the last element at index "size" is + // used as temporary data (and it could be up to max_size). + histo_queue->queue = (HistogramPair*)WebPSafeMalloc( + histo_queue->max_size + 1, sizeof(*histo_queue->queue)); + return histo_queue->queue != NULL; +} + +static void HistoQueueClear(HistoQueue* const histo_queue) { + assert(histo_queue != NULL); + WebPSafeFree(histo_queue->queue); +} + +static void SwapHistogramPairs(HistogramPair *p1, + HistogramPair *p2) { + const HistogramPair tmp = *p1; + *p1 = *p2; + *p2 = tmp; +} + +// Given a valid priority queue in range [0, queue_size) this function checks +// whether histo_queue[queue_size] should be accepted and swaps it with the +// front if it is smaller. Otherwise, it leaves it as is. +static void UpdateQueueFront(HistoQueue* const histo_queue) { + if (histo_queue->queue[histo_queue->size].cost_diff >= 0) return; + + if (histo_queue->queue[histo_queue->size].cost_diff < + histo_queue->queue[0].cost_diff) { + SwapHistogramPairs(histo_queue->queue, + histo_queue->queue + histo_queue->size); + } + ++histo_queue->size; + + // We cannot add more elements than the capacity. + // The allocation adds an extra element to the official capacity so that + // histo_queue->queue[histo_queue->max_size] is read/written within bound. + assert(histo_queue->size <= histo_queue->max_size); +} + +// ----------------------------------------------------------------------------- + +static void PreparePair(VP8LHistogram** histograms, int idx1, int idx2, + HistogramPair* const pair) { + VP8LHistogram* h1; + VP8LHistogram* h2; + double sum_cost; + + if (idx1 > idx2) { + const int tmp = idx2; + idx2 = idx1; + idx1 = tmp; + } + pair->idx1 = idx1; + pair->idx2 = idx2; + h1 = histograms[idx1]; + h2 = histograms[idx2]; + sum_cost = h1->bit_cost_ + h2->bit_cost_; + pair->cost_combo = 0.; + GetCombinedHistogramEntropy(h1, h2, sum_cost, &pair->cost_combo); + pair->cost_diff = pair->cost_combo - sum_cost; +} + +// Combines histograms by continuously choosing the one with the highest cost +// reduction. +static int HistogramCombineGreedy(VP8LHistogramSet* const image_histo) { + int ok = 0; + int image_histo_size = image_histo->size; + int i, j; + VP8LHistogram** const histograms = image_histo->histograms; + // Indexes of remaining histograms. + int* const clusters = + (int*)WebPSafeMalloc(image_histo_size, sizeof(*clusters)); + // Priority queue of histogram pairs. + HistoQueue histo_queue; + + if (!HistoQueueInit(&histo_queue, image_histo_size) || clusters == NULL) { + goto End; + } + + for (i = 0; i < image_histo_size; ++i) { + // Initialize clusters indexes. + clusters[i] = i; + for (j = i + 1; j < image_histo_size; ++j) { + // Initialize positions array. + PreparePair(histograms, i, j, &histo_queue.queue[histo_queue.size]); + UpdateQueueFront(&histo_queue); + } + } + + while (image_histo_size > 1 && histo_queue.size > 0) { + HistogramPair* copy_to; + const int idx1 = histo_queue.queue[0].idx1; + const int idx2 = histo_queue.queue[0].idx2; + HistogramAdd(histograms[idx2], histograms[idx1], histograms[idx1]); + histograms[idx1]->bit_cost_ = histo_queue.queue[0].cost_combo; + // Remove merged histogram. + for (i = 0; i + 1 < image_histo_size; ++i) { + if (clusters[i] >= idx2) { + clusters[i] = clusters[i + 1]; + } + } + --image_histo_size; + + // Remove pairs intersecting the just combined best pair. This will + // therefore pop the head of the queue. + copy_to = histo_queue.queue; + for (i = 0; i < histo_queue.size; ++i) { + HistogramPair* const p = histo_queue.queue + i; + if (p->idx1 == idx1 || p->idx2 == idx1 || + p->idx1 == idx2 || p->idx2 == idx2) { + // Do not copy the invalid pair. + continue; + } + if (p->cost_diff < histo_queue.queue[0].cost_diff) { + // Replace the top of the queue if we found better. + SwapHistogramPairs(histo_queue.queue, p); + } + SwapHistogramPairs(copy_to, p); + ++copy_to; + } + histo_queue.size = (int)(copy_to - histo_queue.queue); + + // Push new pairs formed with combined histogram to the queue. + for (i = 0; i < image_histo_size; ++i) { + if (clusters[i] != idx1) { + PreparePair(histograms, idx1, clusters[i], + &histo_queue.queue[histo_queue.size]); + UpdateQueueFront(&histo_queue); + } + } + } + // Move remaining histograms to the beginning of the array. + for (i = 0; i < image_histo_size; ++i) { + if (i != clusters[i]) { // swap the two histograms + HistogramSwap(&histograms[i], &histograms[clusters[i]]); + } + } + + image_histo->size = image_histo_size; + ok = 1; + + End: + WebPSafeFree(clusters); + HistoQueueClear(&histo_queue); + return ok; +} + +static void HistogramCombineStochastic(VP8LHistogramSet* const image_histo, + VP8LHistogram* tmp_histo, + VP8LHistogram* best_combo, + int quality, int min_cluster_size) { + int iter; + uint32_t seed = 0; + int tries_with_no_success = 0; + int image_histo_size = image_histo->size; + const int iter_mult = (quality < 25) ? 2 : 2 + (quality - 25) / 8; + const int outer_iters = image_histo_size * iter_mult; + const int num_pairs = image_histo_size / 2; + const int num_tries_no_success = outer_iters / 2; + int idx2_max = image_histo_size - 1; + int do_brute_dorce = 0; + VP8LHistogram** const histograms = image_histo->histograms; + + // Collapse similar histograms in 'image_histo'. + ++min_cluster_size; + for (iter = 0; + iter < outer_iters && image_histo_size >= min_cluster_size; + ++iter) { + double best_cost_diff = 0.; + int best_idx1 = -1, best_idx2 = 1; + int j; + int num_tries = + (num_pairs < image_histo_size) ? num_pairs : image_histo_size; + // Use a brute force approach if: + // - stochastic has not worked for a while and + // - if the number of iterations for brute force is less than the number of + // iterations if we never find a match ever again stochastically (hence + // num_tries times the number of remaining outer iterations). + do_brute_dorce = + (tries_with_no_success > 10) && + (idx2_max * (idx2_max + 1) < 2 * num_tries * (outer_iters - iter)); + if (do_brute_dorce) num_tries = idx2_max; + + seed += iter; + for (j = 0; j < num_tries; ++j) { + double curr_cost_diff; + // Choose two histograms at random and try to combine them. + uint32_t idx1, idx2; + if (do_brute_dorce) { + // Use a brute force approach. + idx1 = (uint32_t)j; + idx2 = (uint32_t)idx2_max; + } else { + const uint32_t tmp = (j & 7) + 1; + const uint32_t diff = + (tmp < 3) ? tmp : MyRand(&seed) % (image_histo_size - 1); + idx1 = MyRand(&seed) % image_histo_size; + idx2 = (idx1 + diff + 1) % image_histo_size; + if (idx1 == idx2) { + continue; + } + } + + // Calculate cost reduction on combining. + curr_cost_diff = HistogramAddEval(histograms[idx1], histograms[idx2], + tmp_histo, best_cost_diff); + if (curr_cost_diff < best_cost_diff) { // found a better pair? + HistogramSwap(&best_combo, &tmp_histo); + best_cost_diff = curr_cost_diff; + best_idx1 = idx1; + best_idx2 = idx2; + } + } + if (do_brute_dorce) --idx2_max; + + if (best_idx1 >= 0) { + HistogramSwap(&best_combo, &histograms[best_idx1]); + // swap best_idx2 slot with last one (which is now unused) + --image_histo_size; + if (idx2_max >= image_histo_size) idx2_max = image_histo_size - 1; + if (best_idx2 != image_histo_size) { + HistogramSwap(&histograms[image_histo_size], &histograms[best_idx2]); + histograms[image_histo_size] = NULL; + } + tries_with_no_success = 0; + } + if (++tries_with_no_success >= num_tries_no_success || idx2_max == 0) { + break; + } + } + image_histo->size = image_histo_size; +} + +// ----------------------------------------------------------------------------- +// Histogram refinement + +// Find the best 'out' histogram for each of the 'in' histograms. +// Note: we assume that out[]->bit_cost_ is already up-to-date. +static void HistogramRemap(const VP8LHistogramSet* const in, + const VP8LHistogramSet* const out, + uint16_t* const symbols) { + int i; + VP8LHistogram** const in_histo = in->histograms; + VP8LHistogram** const out_histo = out->histograms; + const int in_size = in->size; + const int out_size = out->size; + if (out_size > 1) { + for (i = 0; i < in_size; ++i) { + int best_out = 0; + double best_bits = MAX_COST; + int k; + for (k = 0; k < out_size; ++k) { + const double cur_bits = + HistogramAddThresh(out_histo[k], in_histo[i], best_bits); + if (k == 0 || cur_bits < best_bits) { + best_bits = cur_bits; + best_out = k; + } + } + symbols[i] = best_out; + } + } else { + assert(out_size == 1); + for (i = 0; i < in_size; ++i) { + symbols[i] = 0; + } + } + + // Recompute each out based on raw and symbols. + for (i = 0; i < out_size; ++i) { + HistogramClear(out_histo[i]); + } + + for (i = 0; i < in_size; ++i) { + const int idx = symbols[i]; + HistogramAdd(in_histo[i], out_histo[idx], out_histo[idx]); + } +} + +static double GetCombineCostFactor(int histo_size, int quality) { + double combine_cost_factor = 0.16; + if (quality < 90) { + if (histo_size > 256) combine_cost_factor /= 2.; + if (histo_size > 512) combine_cost_factor /= 2.; + if (histo_size > 1024) combine_cost_factor /= 2.; + if (quality <= 50) combine_cost_factor /= 2.; + } + return combine_cost_factor; +} + +int VP8LGetHistoImageSymbols(int xsize, int ysize, + const VP8LBackwardRefs* const refs, + int quality, int low_effort, + int histo_bits, int cache_bits, + VP8LHistogramSet* const image_histo, + VP8LHistogramSet* const tmp_histos, + uint16_t* const histogram_symbols) { + int ok = 0; + const int histo_xsize = histo_bits ? VP8LSubSampleSize(xsize, histo_bits) : 1; + const int histo_ysize = histo_bits ? VP8LSubSampleSize(ysize, histo_bits) : 1; + const int image_histo_raw_size = histo_xsize * histo_ysize; + VP8LHistogramSet* const orig_histo = + VP8LAllocateHistogramSet(image_histo_raw_size, cache_bits); + VP8LHistogram* cur_combo; + // Don't attempt linear bin-partition heuristic for + // histograms of small sizes (as bin_map will be very sparse) and + // maximum quality q==100 (to preserve the compression gains at that level). + const int entropy_combine_num_bins = low_effort ? NUM_PARTITIONS : BIN_SIZE; + const int entropy_combine = + (orig_histo->size > entropy_combine_num_bins * 2) && (quality < 100); + + if (orig_histo == NULL) goto Error; + + // Construct the histograms from backward references. + HistogramBuild(xsize, histo_bits, refs, orig_histo); + // Copies the histograms and computes its bit_cost. + HistogramCopyAndAnalyze(orig_histo, image_histo); + + cur_combo = tmp_histos->histograms[1]; // pick up working slot + if (entropy_combine) { + const int bin_map_size = orig_histo->size; + // Reuse histogram_symbols storage. By definition, it's guaranteed to be ok. + uint16_t* const bin_map = histogram_symbols; + const double combine_cost_factor = + GetCombineCostFactor(image_histo_raw_size, quality); + + HistogramAnalyzeEntropyBin(orig_histo, bin_map, low_effort); + // Collapse histograms with similar entropy. + cur_combo = HistogramCombineEntropyBin(image_histo, cur_combo, + bin_map, bin_map_size, + entropy_combine_num_bins, + combine_cost_factor, low_effort); + } + + // Don't combine the histograms using stochastic and greedy heuristics for + // low-effort compression mode. + if (!low_effort || !entropy_combine) { + const float x = quality / 100.f; + // cubic ramp between 1 and MAX_HISTO_GREEDY: + const int threshold_size = (int)(1 + (x * x * x) * (MAX_HISTO_GREEDY - 1)); + HistogramCombineStochastic(image_histo, tmp_histos->histograms[0], + cur_combo, quality, threshold_size); + if ((image_histo->size <= threshold_size) && + !HistogramCombineGreedy(image_histo)) { + goto Error; + } + } + + // TODO(vikasa): Optimize HistogramRemap for low-effort compression mode also. + // Find the optimal map from original histograms to the final ones. + HistogramRemap(orig_histo, image_histo, histogram_symbols); + + ok = 1; + + Error: + VP8LFreeHistogramSet(orig_histo); + return ok; +} diff --git a/3rdparty/libwebp/enc/histogram.h b/3rdparty/libwebp/enc/histogram_enc.h similarity index 57% rename from 3rdparty/libwebp/enc/histogram.h rename to 3rdparty/libwebp/enc/histogram_enc.h index 583b5a4f80..a9d258a166 100644 --- a/3rdparty/libwebp/enc/histogram.h +++ b/3rdparty/libwebp/enc/histogram_enc.h @@ -14,36 +14,40 @@ #ifndef WEBP_ENC_HISTOGRAM_H_ #define WEBP_ENC_HISTOGRAM_H_ -#include -#include -#include -#include #include -#include "./backward_references.h" +#include "./backward_references_enc.h" #include "../webp/format_constants.h" #include "../webp/types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif +// Not a trivial literal symbol. +#define VP8L_NON_TRIVIAL_SYM (0xffffffff) + // A simple container for histograms of data. typedef struct { // literal_ contains green literal, palette-code and // copy-length-prefix histogram - int literal_[PIX_OR_COPY_CODES_MAX]; - int red_[256]; - int blue_[256]; - int alpha_[256]; + uint32_t* literal_; // Pointer to the allocated buffer for literal. + uint32_t red_[NUM_LITERAL_CODES]; + uint32_t blue_[NUM_LITERAL_CODES]; + uint32_t alpha_[NUM_LITERAL_CODES]; // Backward reference prefix-code histogram. - int distance_[NUM_DISTANCE_CODES]; + uint32_t distance_[NUM_DISTANCE_CODES]; int palette_code_bits_; - double bit_cost_; // cached value of VP8LHistogramEstimateBits(this) + uint32_t trivial_symbol_; // True, if histograms for Red, Blue & Alpha + // literal symbols are single valued. + double bit_cost_; // cached value of bit cost. + double literal_cost_; // Cached values of dominant entropy costs: + double red_cost_; // literal, red & blue. + double blue_cost_; } VP8LHistogram; // Collection of histograms with fixed capacity, allocated as one -// big memory chunk. Can be destroyed by simply calling 'free()'. +// big memory chunk. Can be destroyed by calling WebPSafeFree(). typedef struct { int size; // number of slots currently in use int max_size; // maximum capacity @@ -59,6 +63,9 @@ void VP8LHistogramCreate(VP8LHistogram* const p, const VP8LBackwardRefs* const refs, int palette_code_bits); +// Return the size of the histogram for a given palette_code_bits. +int VP8LGetHistogramSize(int palette_code_bits); + // Set the palette_code_bits and reset the stats. void VP8LHistogramInit(VP8LHistogram* const p, int palette_code_bits); @@ -66,35 +73,50 @@ void VP8LHistogramInit(VP8LHistogram* const p, int palette_code_bits); void VP8LHistogramStoreRefs(const VP8LBackwardRefs* const refs, VP8LHistogram* const histo); +// Free the memory allocated for the histogram. +void VP8LFreeHistogram(VP8LHistogram* const histo); + +// Free the memory allocated for the histogram set. +void VP8LFreeHistogramSet(VP8LHistogramSet* const histo); + // Allocate an array of pointer to histograms, allocated and initialized // using 'cache_bits'. Return NULL in case of memory error. VP8LHistogramSet* VP8LAllocateHistogramSet(int size, int cache_bits); +// Allocate and initialize histogram object with specified 'cache_bits'. +// Returns NULL in case of memory error. +// Special case of VP8LAllocateHistogramSet, with size equals 1. +VP8LHistogram* VP8LAllocateHistogram(int cache_bits); + // Accumulate a token 'v' into a histogram. void VP8LHistogramAddSinglePixOrCopy(VP8LHistogram* const histo, const PixOrCopy* const v); -// Estimate how many bits the combined entropy of literals and distance -// approximately maps to. -double VP8LHistogramEstimateBits(const VP8LHistogram* const p); - -// This function estimates the cost in bits excluding the bits needed to -// represent the entropy code itself. -double VP8LHistogramEstimateBitsBulk(const VP8LHistogram* const p); - -static WEBP_INLINE int VP8LHistogramNumCodes(const VP8LHistogram* const p) { - return 256 + NUM_LENGTH_CODES + - ((p->palette_code_bits_ > 0) ? (1 << p->palette_code_bits_) : 0); +static WEBP_INLINE int VP8LHistogramNumCodes(int palette_code_bits) { + return NUM_LITERAL_CODES + NUM_LENGTH_CODES + + ((palette_code_bits > 0) ? (1 << palette_code_bits) : 0); } // Builds the histogram image. int VP8LGetHistoImageSymbols(int xsize, int ysize, const VP8LBackwardRefs* const refs, - int quality, int histogram_bits, int cache_bits, + int quality, int low_effort, + int histogram_bits, int cache_bits, VP8LHistogramSet* const image_in, + VP8LHistogramSet* const tmp_histos, uint16_t* const histogram_symbols); -#if defined(__cplusplus) || defined(c_plusplus) +// Returns the entropy for the symbols in the input array. +// Also sets trivial_symbol to the code value, if the array has only one code +// value. Otherwise, set it to VP8L_NON_TRIVIAL_SYM. +double VP8LBitsEntropy(const uint32_t* const array, int n, + uint32_t* const trivial_symbol); + +// Estimate how many bits the combined entropy of literals and distance +// approximately maps to. +double VP8LHistogramEstimateBits(const VP8LHistogram* const p); + +#ifdef __cplusplus } #endif diff --git a/3rdparty/libwebp/enc/iterator.c b/3rdparty/libwebp/enc/iterator_enc.c similarity index 68% rename from 3rdparty/libwebp/enc/iterator.c rename to 3rdparty/libwebp/enc/iterator_enc.c index 0746659046..e48d30bd31 100644 --- a/3rdparty/libwebp/enc/iterator.c +++ b/3rdparty/libwebp/enc/iterator_enc.c @@ -13,23 +13,18 @@ #include -#include "./vp8enci.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./vp8i_enc.h" //------------------------------------------------------------------------------ // VP8Iterator //------------------------------------------------------------------------------ static void InitLeft(VP8EncIterator* const it) { - const VP8Encoder* const enc = it->enc_; - enc->y_left_[-1] = enc->u_left_[-1] = enc->v_left_[-1] = + it->y_left_[-1] = it->u_left_[-1] = it->v_left_[-1] = (it->y_ > 0) ? 129 : 127; - memset(enc->y_left_, 129, 16); - memset(enc->u_left_, 129, 8); - memset(enc->v_left_, 129, 8); + memset(it->y_left_, 129, 16); + memset(it->u_left_, 129, 8); + memset(it->v_left_, 129, 8); it->left_nz_[8] = 0; } @@ -40,43 +35,57 @@ static void InitTop(VP8EncIterator* const it) { memset(enc->nz_, 0, enc->mb_w_ * sizeof(*enc->nz_)); } -void VP8IteratorReset(VP8EncIterator* const it) { +void VP8IteratorSetRow(VP8EncIterator* const it, int y) { VP8Encoder* const enc = it->enc_; it->x_ = 0; - it->y_ = 0; - it->y_offset_ = 0; - it->uv_offset_ = 0; - it->mb_ = enc->mb_info_; - it->preds_ = enc->preds_; + it->y_ = y; + it->bw_ = &enc->parts_[y & (enc->num_parts_ - 1)]; + it->preds_ = enc->preds_ + y * 4 * enc->preds_w_; it->nz_ = enc->nz_; - it->bw_ = &enc->parts_[0]; - it->done_ = enc->mb_w_* enc->mb_h_; - InitTop(it); + it->mb_ = enc->mb_info_ + y * enc->mb_w_; + it->y_top_ = enc->y_top_; + it->uv_top_ = enc->uv_top_; InitLeft(it); +} + +void VP8IteratorReset(VP8EncIterator* const it) { + VP8Encoder* const enc = it->enc_; + VP8IteratorSetRow(it, 0); + VP8IteratorSetCountDown(it, enc->mb_w_ * enc->mb_h_); // default + InitTop(it); memset(it->bit_count_, 0, sizeof(it->bit_count_)); it->do_trellis_ = 0; } +void VP8IteratorSetCountDown(VP8EncIterator* const it, int count_down) { + it->count_down_ = it->count_down0_ = count_down; +} + +int VP8IteratorIsDone(const VP8EncIterator* const it) { + return (it->count_down_ <= 0); +} + void VP8IteratorInit(VP8Encoder* const enc, VP8EncIterator* const it) { it->enc_ = enc; - it->y_stride_ = enc->pic_->y_stride; - it->uv_stride_ = enc->pic_->uv_stride; - // TODO(later): for multithreading, these should be owned by 'it'. - it->yuv_in_ = enc->yuv_in_; - it->yuv_out_ = enc->yuv_out_; - it->yuv_out2_ = enc->yuv_out2_; - it->yuv_p_ = enc->yuv_p_; + it->yuv_in_ = (uint8_t*)WEBP_ALIGN(it->yuv_mem_); + it->yuv_out_ = it->yuv_in_ + YUV_SIZE_ENC; + it->yuv_out2_ = it->yuv_out_ + YUV_SIZE_ENC; + it->yuv_p_ = it->yuv_out2_ + YUV_SIZE_ENC; it->lf_stats_ = enc->lf_stats_; it->percent0_ = enc->percent_; + it->y_left_ = (uint8_t*)WEBP_ALIGN(it->yuv_left_mem_ + 1); + it->u_left_ = it->y_left_ + 16 + 16; + it->v_left_ = it->u_left_ + 16; VP8IteratorReset(it); } int VP8IteratorProgress(const VP8EncIterator* const it, int delta) { VP8Encoder* const enc = it->enc_; - if (delta && enc->pic_->progress_hook) { - const int percent = (enc->mb_h_ <= 1) + if (delta && enc->pic_->progress_hook != NULL) { + const int done = it->count_down0_ - it->count_down_; + const int percent = (it->count_down0_ <= 0) ? it->percent0_ - : it->percent0_ + delta * it->y_ / (enc->mb_h_ - 1); + : it->percent0_ + delta * done / it->count_down0_; return WebPReportProgress(enc->pic_, percent, &enc->percent_); } return 1; @@ -86,6 +95,8 @@ int VP8IteratorProgress(const VP8EncIterator* const it, int delta) { // Import the source samples into the cache. Takes care of replicating // boundary pixels if necessary. +static WEBP_INLINE int MinSize(int a, int b) { return (a < b) ? a : b; } + static void ImportBlock(const uint8_t* src, int src_stride, uint8_t* dst, int w, int h, int size) { int i; @@ -103,30 +114,55 @@ static void ImportBlock(const uint8_t* src, int src_stride, } } -void VP8IteratorImport(const VP8EncIterator* const it) { +static void ImportLine(const uint8_t* src, int src_stride, + uint8_t* dst, int len, int total_len) { + int i; + for (i = 0; i < len; ++i, src += src_stride) dst[i] = *src; + for (; i < total_len; ++i) dst[i] = dst[len - 1]; +} + +void VP8IteratorImport(VP8EncIterator* const it, uint8_t* tmp_32) { const VP8Encoder* const enc = it->enc_; const int x = it->x_, y = it->y_; const WebPPicture* const pic = enc->pic_; - const uint8_t* const ysrc = pic->y + (y * pic->y_stride + x) * 16; + const uint8_t* const ysrc = pic->y + (y * pic->y_stride + x) * 16; const uint8_t* const usrc = pic->u + (y * pic->uv_stride + x) * 8; const uint8_t* const vsrc = pic->v + (y * pic->uv_stride + x) * 8; - uint8_t* const ydst = it->yuv_in_ + Y_OFF; - uint8_t* const udst = it->yuv_in_ + U_OFF; - uint8_t* const vdst = it->yuv_in_ + V_OFF; - int w = (pic->width - x * 16); - int h = (pic->height - y * 16); + const int w = MinSize(pic->width - x * 16, 16); + const int h = MinSize(pic->height - y * 16, 16); + const int uv_w = (w + 1) >> 1; + const int uv_h = (h + 1) >> 1; - if (w > 16) w = 16; - if (h > 16) h = 16; + ImportBlock(ysrc, pic->y_stride, it->yuv_in_ + Y_OFF_ENC, w, h, 16); + ImportBlock(usrc, pic->uv_stride, it->yuv_in_ + U_OFF_ENC, uv_w, uv_h, 8); + ImportBlock(vsrc, pic->uv_stride, it->yuv_in_ + V_OFF_ENC, uv_w, uv_h, 8); - // Luma plane - ImportBlock(ysrc, pic->y_stride, ydst, w, h, 16); + if (tmp_32 == NULL) return; - { // U/V planes - const int uv_w = (w + 1) >> 1; - const int uv_h = (h + 1) >> 1; - ImportBlock(usrc, pic->uv_stride, udst, uv_w, uv_h, 8); - ImportBlock(vsrc, pic->uv_stride, vdst, uv_w, uv_h, 8); + // Import source (uncompressed) samples into boundary. + if (x == 0) { + InitLeft(it); + } else { + if (y == 0) { + it->y_left_[-1] = it->u_left_[-1] = it->v_left_[-1] = 127; + } else { + it->y_left_[-1] = ysrc[- 1 - pic->y_stride]; + it->u_left_[-1] = usrc[- 1 - pic->uv_stride]; + it->v_left_[-1] = vsrc[- 1 - pic->uv_stride]; + } + ImportLine(ysrc - 1, pic->y_stride, it->y_left_, h, 16); + ImportLine(usrc - 1, pic->uv_stride, it->u_left_, uv_h, 8); + ImportLine(vsrc - 1, pic->uv_stride, it->v_left_, uv_h, 8); + } + + it->y_top_ = tmp_32 + 0; + it->uv_top_ = tmp_32 + 16; + if (y == 0) { + memset(tmp_32, 127, 32 * sizeof(*tmp_32)); + } else { + ImportLine(ysrc - pic->y_stride, 1, tmp_32, w, 16); + ImportLine(usrc - pic->uv_stride, 1, tmp_32 + 16, uv_w, 8); + ImportLine(vsrc - pic->uv_stride, 1, tmp_32 + 16 + 8, uv_w, 8); } } @@ -146,9 +182,9 @@ void VP8IteratorExport(const VP8EncIterator* const it) { const VP8Encoder* const enc = it->enc_; if (enc->config_->show_compressed) { const int x = it->x_, y = it->y_; - const uint8_t* const ysrc = it->yuv_out_ + Y_OFF; - const uint8_t* const usrc = it->yuv_out_ + U_OFF; - const uint8_t* const vsrc = it->yuv_out_ + V_OFF; + const uint8_t* const ysrc = it->yuv_out_ + Y_OFF_ENC; + const uint8_t* const usrc = it->yuv_out_ + U_OFF_ENC; + const uint8_t* const vsrc = it->yuv_out_ + V_OFF_ENC; const WebPPicture* const pic = enc->pic_; uint8_t* const ydst = pic->y + (y * pic->y_stride + x) * 16; uint8_t* const udst = pic->u + (y * pic->uv_stride + x) * 8; @@ -242,48 +278,44 @@ void VP8IteratorBytesToNz(VP8EncIterator* const it) { #undef BIT //------------------------------------------------------------------------------ -// Advance to the next position, doing the bookeeping. +// Advance to the next position, doing the bookkeeping. -int VP8IteratorNext(VP8EncIterator* const it, - const uint8_t* const block_to_save) { +void VP8IteratorSaveBoundary(VP8EncIterator* const it) { VP8Encoder* const enc = it->enc_; - if (block_to_save) { - const int x = it->x_, y = it->y_; - const uint8_t* const ysrc = block_to_save + Y_OFF; - const uint8_t* const usrc = block_to_save + U_OFF; - if (x < enc->mb_w_ - 1) { // left - int i; - for (i = 0; i < 16; ++i) { - enc->y_left_[i] = ysrc[15 + i * BPS]; - } - for (i = 0; i < 8; ++i) { - enc->u_left_[i] = usrc[7 + i * BPS]; - enc->v_left_[i] = usrc[15 + i * BPS]; - } - // top-left (before 'top'!) - enc->y_left_[-1] = enc->y_top_[x * 16 + 15]; - enc->u_left_[-1] = enc->uv_top_[x * 16 + 0 + 7]; - enc->v_left_[-1] = enc->uv_top_[x * 16 + 8 + 7]; + const int x = it->x_, y = it->y_; + const uint8_t* const ysrc = it->yuv_out_ + Y_OFF_ENC; + const uint8_t* const uvsrc = it->yuv_out_ + U_OFF_ENC; + if (x < enc->mb_w_ - 1) { // left + int i; + for (i = 0; i < 16; ++i) { + it->y_left_[i] = ysrc[15 + i * BPS]; } - if (y < enc->mb_h_ - 1) { // top - memcpy(enc->y_top_ + x * 16, ysrc + 15 * BPS, 16); - memcpy(enc->uv_top_ + x * 16, usrc + 7 * BPS, 8 + 8); + for (i = 0; i < 8; ++i) { + it->u_left_[i] = uvsrc[7 + i * BPS]; + it->v_left_[i] = uvsrc[15 + i * BPS]; } + // top-left (before 'top'!) + it->y_left_[-1] = it->y_top_[15]; + it->u_left_[-1] = it->uv_top_[0 + 7]; + it->v_left_[-1] = it->uv_top_[8 + 7]; } + if (y < enc->mb_h_ - 1) { // top + memcpy(it->y_top_, ysrc + 15 * BPS, 16); + memcpy(it->uv_top_, uvsrc + 7 * BPS, 8 + 8); + } +} - it->mb_++; - it->preds_ += 4; - it->nz_++; - it->x_++; - if (it->x_ == enc->mb_w_) { - it->x_ = 0; - it->y_++; - it->bw_ = &enc->parts_[it->y_ & (enc->num_parts_ - 1)]; - it->preds_ = enc->preds_ + it->y_ * 4 * enc->preds_w_; - it->nz_ = enc->nz_; - InitLeft(it); +int VP8IteratorNext(VP8EncIterator* const it) { + if (++it->x_ == it->enc_->mb_w_) { + VP8IteratorSetRow(it, ++it->y_); + } else { + it->preds_ += 4; + it->mb_ += 1; + it->nz_ += 1; + it->y_top_ += 16; + it->uv_top_ += 16; } - return (0 < --it->done_); + return (0 < --it->count_down_); } //------------------------------------------------------------------------------ @@ -370,15 +402,15 @@ void VP8IteratorStartI4(VP8EncIterator* const it) { // Import the boundary samples for (i = 0; i < 17; ++i) { // left - it->i4_boundary_[i] = enc->y_left_[15 - i]; + it->i4_boundary_[i] = it->y_left_[15 - i]; } for (i = 0; i < 16; ++i) { // top - it->i4_boundary_[17 + i] = enc->y_top_[it->x_ * 16 + i]; + it->i4_boundary_[17 + i] = it->y_top_[i]; } // top-right samples have a special case on the far right of the picture if (it->x_ < enc->mb_w_ - 1) { for (i = 16; i < 16 + 4; ++i) { - it->i4_boundary_[17 + i] = enc->y_top_[it->x_ * 16 + i]; + it->i4_boundary_[17 + i] = it->y_top_[i]; } } else { // else, replicate the last valid pixel four times for (i = 16; i < 16 + 4; ++i) { @@ -419,6 +451,3 @@ int VP8IteratorRotateI4(VP8EncIterator* const it, //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/layer.c b/3rdparty/libwebp/enc/layer.c deleted file mode 100644 index fa89660994..0000000000 --- a/3rdparty/libwebp/enc/layer.c +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Enhancement layer (for YUV444/422) -// -// Author: Skal (pascal.massimino@gmail.com) - -#include - -#include "./vp8enci.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -//------------------------------------------------------------------------------ - -void VP8EncInitLayer(VP8Encoder* const enc) { - enc->use_layer_ = (enc->pic_->u0 != NULL); - enc->layer_data_size_ = 0; - enc->layer_data_ = NULL; - if (enc->use_layer_) { - VP8BitWriterInit(&enc->layer_bw_, enc->mb_w_ * enc->mb_h_ * 3); - } -} - -void VP8EncCodeLayerBlock(VP8EncIterator* it) { - (void)it; // remove a warning -} - -int VP8EncFinishLayer(VP8Encoder* const enc) { - if (enc->use_layer_) { - enc->layer_data_ = VP8BitWriterFinish(&enc->layer_bw_); - enc->layer_data_size_ = VP8BitWriterSize(&enc->layer_bw_); - } - return 1; -} - -void VP8EncDeleteLayer(VP8Encoder* enc) { - free(enc->layer_data_); -} - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/near_lossless_enc.c b/3rdparty/libwebp/enc/near_lossless_enc.c new file mode 100644 index 0000000000..2bd03ab20d --- /dev/null +++ b/3rdparty/libwebp/enc/near_lossless_enc.c @@ -0,0 +1,122 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Near-lossless image preprocessing adjusts pixel values to help +// compressibility with a guarantee of maximum deviation between original and +// resulting pixel values. +// +// Author: Jyrki Alakuijala (jyrki@google.com) +// Converted to C by Aleksander Kramarz (akramarz@google.com) + +#include +#include + +#include "../dsp/lossless_common.h" +#include "../utils/utils.h" +#include "./vp8i_enc.h" + +#define MIN_DIM_FOR_NEAR_LOSSLESS 64 +#define MAX_LIMIT_BITS 5 + +// Quantizes the value up or down to a multiple of 1<> 1) + ((a >> bits) & 1); + assert(bits > 0); + if (biased > 0xff) return 0xff; + return biased & ~mask; +} + +// Applies FindClosestDiscretized to all channels of pixel. +static uint32_t ClosestDiscretizedArgb(uint32_t a, int bits) { + return + (FindClosestDiscretized(a >> 24, bits) << 24) | + (FindClosestDiscretized((a >> 16) & 0xff, bits) << 16) | + (FindClosestDiscretized((a >> 8) & 0xff, bits) << 8) | + (FindClosestDiscretized(a & 0xff, bits)); +} + +// Checks if distance between corresponding channel values of pixels a and b +// is within the given limit. +static int IsNear(uint32_t a, uint32_t b, int limit) { + int k; + for (k = 0; k < 4; ++k) { + const int delta = + (int)((a >> (k * 8)) & 0xff) - (int)((b >> (k * 8)) & 0xff); + if (delta >= limit || delta <= -limit) { + return 0; + } + } + return 1; +} + +static int IsSmooth(const uint32_t* const prev_row, + const uint32_t* const curr_row, + const uint32_t* const next_row, + int ix, int limit) { + // Check that all pixels in 4-connected neighborhood are smooth. + return (IsNear(curr_row[ix], curr_row[ix - 1], limit) && + IsNear(curr_row[ix], curr_row[ix + 1], limit) && + IsNear(curr_row[ix], prev_row[ix], limit) && + IsNear(curr_row[ix], next_row[ix], limit)); +} + +// Adjusts pixel values of image with given maximum error. +static void NearLossless(int xsize, int ysize, uint32_t* argb, + int limit_bits, uint32_t* copy_buffer) { + int x, y; + const int limit = 1 << limit_bits; + uint32_t* prev_row = copy_buffer; + uint32_t* curr_row = prev_row + xsize; + uint32_t* next_row = curr_row + xsize; + memcpy(copy_buffer, argb, xsize * 2 * sizeof(argb[0])); + + for (y = 1; y < ysize - 1; ++y) { + uint32_t* const curr_argb_row = argb + y * xsize; + uint32_t* const next_argb_row = curr_argb_row + xsize; + memcpy(next_row, next_argb_row, xsize * sizeof(argb[0])); + for (x = 1; x < xsize - 1; ++x) { + if (!IsSmooth(prev_row, curr_row, next_row, x, limit)) { + curr_argb_row[x] = ClosestDiscretizedArgb(curr_row[x], limit_bits); + } + } + { + // Three-way swap. + uint32_t* const temp = prev_row; + prev_row = curr_row; + curr_row = next_row; + next_row = temp; + } + } +} + +int VP8ApplyNearLossless(int xsize, int ysize, uint32_t* argb, int quality) { + int i; + uint32_t* const copy_buffer = + (uint32_t*)WebPSafeMalloc(xsize * 3, sizeof(*copy_buffer)); + const int limit_bits = VP8LNearLosslessBits(quality); + assert(argb != NULL); + assert(limit_bits >= 0); + assert(limit_bits <= MAX_LIMIT_BITS); + if (copy_buffer == NULL) { + return 0; + } + // For small icon images, don't attempt to apply near-lossless compression. + if (xsize < MIN_DIM_FOR_NEAR_LOSSLESS && ysize < MIN_DIM_FOR_NEAR_LOSSLESS) { + WebPSafeFree(copy_buffer); + return 1; + } + + for (i = limit_bits; i != 0; --i) { + NearLossless(xsize, ysize, argb, i, copy_buffer); + } + WebPSafeFree(copy_buffer); + return 1; +} diff --git a/3rdparty/libwebp/enc/picture.c b/3rdparty/libwebp/enc/picture.c deleted file mode 100644 index 5aaa385d47..0000000000 --- a/3rdparty/libwebp/enc/picture.c +++ /dev/null @@ -1,1115 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// WebPPicture utils: colorspace conversion, crop, ... -// -// Author: Skal (pascal.massimino@gmail.com) - -#include -#include -#include - -#include "./vp8enci.h" -#include "../utils/rescaler.h" -#include "../utils/utils.h" -#include "../dsp/dsp.h" -#include "../dsp/yuv.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#define HALVE(x) (((x) + 1) >> 1) -#define IS_YUV_CSP(csp, YUV_CSP) (((csp) & WEBP_CSP_UV_MASK) == (YUV_CSP)) - -static const union { - uint32_t argb; - uint8_t bytes[4]; -} test_endian = { 0xff000000u }; -#define ALPHA_IS_LAST (test_endian.bytes[3] == 0xff) - -//------------------------------------------------------------------------------ -// WebPPicture -//------------------------------------------------------------------------------ - -int WebPPictureAlloc(WebPPicture* picture) { - if (picture != NULL) { - const WebPEncCSP uv_csp = picture->colorspace & WEBP_CSP_UV_MASK; - const int has_alpha = picture->colorspace & WEBP_CSP_ALPHA_BIT; - const int width = picture->width; - const int height = picture->height; - - if (!picture->use_argb) { - const int y_stride = width; - const int uv_width = HALVE(width); - const int uv_height = HALVE(height); - const int uv_stride = uv_width; - int uv0_stride = 0; - int a_width, a_stride; - uint64_t y_size, uv_size, uv0_size, a_size, total_size; - uint8_t* mem; - - // U/V - switch (uv_csp) { - case WEBP_YUV420: - break; -#ifdef WEBP_EXPERIMENTAL_FEATURES - case WEBP_YUV400: // for now, we'll just reset the U/V samples - break; - case WEBP_YUV422: - uv0_stride = uv_width; - break; - case WEBP_YUV444: - uv0_stride = width; - break; -#endif - default: - return 0; - } - uv0_size = height * uv0_stride; - - // alpha - a_width = has_alpha ? width : 0; - a_stride = a_width; - y_size = (uint64_t)y_stride * height; - uv_size = (uint64_t)uv_stride * uv_height; - a_size = (uint64_t)a_stride * height; - - total_size = y_size + a_size + 2 * uv_size + 2 * uv0_size; - - // Security and validation checks - if (width <= 0 || height <= 0 || // luma/alpha param error - uv_width < 0 || uv_height < 0) { // u/v param error - return 0; - } - // Clear previous buffer and allocate a new one. - WebPPictureFree(picture); // erase previous buffer - mem = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*mem)); - if (mem == NULL) return 0; - - // From now on, we're in the clear, we can no longer fail... - picture->memory_ = (void*)mem; - picture->y_stride = y_stride; - picture->uv_stride = uv_stride; - picture->a_stride = a_stride; - picture->uv0_stride = uv0_stride; - // TODO(skal): we could align the y/u/v planes and adjust stride. - picture->y = mem; - mem += y_size; - - picture->u = mem; - mem += uv_size; - picture->v = mem; - mem += uv_size; - - if (a_size) { - picture->a = mem; - mem += a_size; - } - if (uv0_size) { - picture->u0 = mem; - mem += uv0_size; - picture->v0 = mem; - mem += uv0_size; - } - } else { - void* memory; - const uint64_t argb_size = (uint64_t)width * height; - if (width <= 0 || height <= 0) { - return 0; - } - // Clear previous buffer and allocate a new one. - WebPPictureFree(picture); // erase previous buffer - memory = WebPSafeMalloc(argb_size, sizeof(*picture->argb)); - if (memory == NULL) return 0; - - // TODO(skal): align plane to cache line? - picture->memory_argb_ = memory; - picture->argb = (uint32_t*)memory; - picture->argb_stride = width; - } - } - return 1; -} - -// Remove reference to the ARGB buffer (doesn't free anything). -static void PictureResetARGB(WebPPicture* const picture) { - picture->memory_argb_ = NULL; - picture->argb = NULL; - picture->argb_stride = 0; -} - -// Remove reference to the YUVA buffer (doesn't free anything). -static void PictureResetYUVA(WebPPicture* const picture) { - picture->memory_ = NULL; - picture->y = picture->u = picture->v = picture->a = NULL; - picture->u0 = picture->v0 = NULL; - picture->y_stride = picture->uv_stride = 0; - picture->a_stride = 0; - picture->uv0_stride = 0; -} - -// Grab the 'specs' (writer, *opaque, width, height...) from 'src' and copy them -// into 'dst'. Mark 'dst' as not owning any memory. -static void WebPPictureGrabSpecs(const WebPPicture* const src, - WebPPicture* const dst) { - assert(src != NULL && dst != NULL); - *dst = *src; - PictureResetYUVA(dst); - PictureResetARGB(dst); -} - -// Allocate a new argb buffer, discarding any existing one and preserving -// the other YUV(A) buffer. -static int PictureAllocARGB(WebPPicture* const picture) { - WebPPicture tmp; - free(picture->memory_argb_); - PictureResetARGB(picture); - picture->use_argb = 1; - WebPPictureGrabSpecs(picture, &tmp); - if (!WebPPictureAlloc(&tmp)) { - return WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY); - } - picture->memory_argb_ = tmp.memory_argb_; - picture->argb = tmp.argb; - picture->argb_stride = tmp.argb_stride; - return 1; -} - -// Release memory owned by 'picture' (both YUV and ARGB buffers). -void WebPPictureFree(WebPPicture* picture) { - if (picture != NULL) { - free(picture->memory_); - free(picture->memory_argb_); - PictureResetYUVA(picture); - PictureResetARGB(picture); - } -} - -//------------------------------------------------------------------------------ -// Picture copying - -// Not worth moving to dsp/enc.c (only used here). -static void CopyPlane(const uint8_t* src, int src_stride, - uint8_t* dst, int dst_stride, int width, int height) { - while (height-- > 0) { - memcpy(dst, src, width); - src += src_stride; - dst += dst_stride; - } -} - -// Adjust top-left corner to chroma sample position. -static void SnapTopLeftPosition(const WebPPicture* const pic, - int* const left, int* const top) { - if (!pic->use_argb) { - const int is_yuv422 = IS_YUV_CSP(pic->colorspace, WEBP_YUV422); - if (IS_YUV_CSP(pic->colorspace, WEBP_YUV420) || is_yuv422) { - *left &= ~1; - if (!is_yuv422) *top &= ~1; - } - } -} - -// Adjust top-left corner and verify that the sub-rectangle is valid. -static int AdjustAndCheckRectangle(const WebPPicture* const pic, - int* const left, int* const top, - int width, int height) { - SnapTopLeftPosition(pic, left, top); - if ((*left) < 0 || (*top) < 0) return 0; - if (width <= 0 || height <= 0) return 0; - if ((*left) + width > pic->width) return 0; - if ((*top) + height > pic->height) return 0; - return 1; -} - -int WebPPictureCopy(const WebPPicture* src, WebPPicture* dst) { - if (src == NULL || dst == NULL) return 0; - if (src == dst) return 1; - - WebPPictureGrabSpecs(src, dst); - if (!WebPPictureAlloc(dst)) return 0; - - if (!src->use_argb) { - CopyPlane(src->y, src->y_stride, - dst->y, dst->y_stride, dst->width, dst->height); - CopyPlane(src->u, src->uv_stride, - dst->u, dst->uv_stride, HALVE(dst->width), HALVE(dst->height)); - CopyPlane(src->v, src->uv_stride, - dst->v, dst->uv_stride, HALVE(dst->width), HALVE(dst->height)); - if (dst->a != NULL) { - CopyPlane(src->a, src->a_stride, - dst->a, dst->a_stride, dst->width, dst->height); - } -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (dst->u0 != NULL) { - int uv0_width = src->width; - if (IS_YUV_CSP(dst->colorspace, WEBP_YUV422)) { - uv0_width = HALVE(uv0_width); - } - CopyPlane(src->u0, src->uv0_stride, - dst->u0, dst->uv0_stride, uv0_width, dst->height); - CopyPlane(src->v0, src->uv0_stride, - dst->v0, dst->uv0_stride, uv0_width, dst->height); - } -#endif - } else { - CopyPlane((const uint8_t*)src->argb, 4 * src->argb_stride, - (uint8_t*)dst->argb, 4 * dst->argb_stride, - 4 * dst->width, dst->height); - } - return 1; -} - -int WebPPictureIsView(const WebPPicture* picture) { - if (picture == NULL) return 0; - if (picture->use_argb) { - return (picture->memory_argb_ == NULL); - } - return (picture->memory_ == NULL); -} - -int WebPPictureView(const WebPPicture* src, - int left, int top, int width, int height, - WebPPicture* dst) { - if (src == NULL || dst == NULL) return 0; - - // verify rectangle position. - if (!AdjustAndCheckRectangle(src, &left, &top, width, height)) return 0; - - if (src != dst) { // beware of aliasing! We don't want to leak 'memory_'. - WebPPictureGrabSpecs(src, dst); - } - dst->width = width; - dst->height = height; - if (!src->use_argb) { - dst->y = src->y + top * src->y_stride + left; - dst->u = src->u + (top >> 1) * src->uv_stride + (left >> 1); - dst->v = src->v + (top >> 1) * src->uv_stride + (left >> 1); - dst->y_stride = src->y_stride; - dst->uv_stride = src->uv_stride; - if (src->a != NULL) { - dst->a = src->a + top * src->a_stride + left; - dst->a_stride = src->a_stride; - } -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (src->u0 != NULL) { - const int left_pos = - IS_YUV_CSP(dst->colorspace, WEBP_YUV422) ? (left >> 1) : left; - dst->u0 = src->u0 + top * src->uv0_stride + left_pos; - dst->v0 = src->v0 + top * src->uv0_stride + left_pos; - dst->uv0_stride = src->uv0_stride; - } -#endif - } else { - dst->argb = src->argb + top * src->argb_stride + left; - dst->argb_stride = src->argb_stride; - } - return 1; -} - -//------------------------------------------------------------------------------ -// Picture cropping - -int WebPPictureCrop(WebPPicture* pic, - int left, int top, int width, int height) { - WebPPicture tmp; - - if (pic == NULL) return 0; - if (!AdjustAndCheckRectangle(pic, &left, &top, width, height)) return 0; - - WebPPictureGrabSpecs(pic, &tmp); - tmp.width = width; - tmp.height = height; - if (!WebPPictureAlloc(&tmp)) return 0; - - if (!pic->use_argb) { - const int y_offset = top * pic->y_stride + left; - const int uv_offset = (top / 2) * pic->uv_stride + left / 2; - CopyPlane(pic->y + y_offset, pic->y_stride, - tmp.y, tmp.y_stride, width, height); - CopyPlane(pic->u + uv_offset, pic->uv_stride, - tmp.u, tmp.uv_stride, HALVE(width), HALVE(height)); - CopyPlane(pic->v + uv_offset, pic->uv_stride, - tmp.v, tmp.uv_stride, HALVE(width), HALVE(height)); - - if (tmp.a != NULL) { - const int a_offset = top * pic->a_stride + left; - CopyPlane(pic->a + a_offset, pic->a_stride, - tmp.a, tmp.a_stride, width, height); - } -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (tmp.u0 != NULL) { - int w = width; - int left_pos = left; - if (IS_YUV_CSP(tmp.colorspace, WEBP_YUV422)) { - w = HALVE(w); - left_pos = HALVE(left_pos); - } - CopyPlane(pic->u0 + top * pic->uv0_stride + left_pos, pic->uv0_stride, - tmp.u0, tmp.uv0_stride, w, height); - CopyPlane(pic->v0 + top * pic->uv0_stride + left_pos, pic->uv0_stride, - tmp.v0, tmp.uv0_stride, w, height); - } -#endif - } else { - const uint8_t* const src = - (const uint8_t*)(pic->argb + top * pic->argb_stride + left); - CopyPlane(src, pic->argb_stride * 4, - (uint8_t*)tmp.argb, tmp.argb_stride * 4, - width * 4, height); - } - WebPPictureFree(pic); - *pic = tmp; - return 1; -} - -//------------------------------------------------------------------------------ -// Simple picture rescaler - -static void RescalePlane(const uint8_t* src, - int src_width, int src_height, int src_stride, - uint8_t* dst, - int dst_width, int dst_height, int dst_stride, - int32_t* const work, - int num_channels) { - WebPRescaler rescaler; - int y = 0; - WebPRescalerInit(&rescaler, src_width, src_height, - dst, dst_width, dst_height, dst_stride, - num_channels, - src_width, dst_width, - src_height, dst_height, - work); - memset(work, 0, 2 * dst_width * num_channels * sizeof(*work)); - while (y < src_height) { - y += WebPRescalerImport(&rescaler, src_height - y, - src + y * src_stride, src_stride); - WebPRescalerExport(&rescaler); - } -} - -int WebPPictureRescale(WebPPicture* pic, int width, int height) { - WebPPicture tmp; - int prev_width, prev_height; - int32_t* work; - - if (pic == NULL) return 0; - prev_width = pic->width; - prev_height = pic->height; - // if width is unspecified, scale original proportionally to height ratio. - if (width == 0) { - width = (prev_width * height + prev_height / 2) / prev_height; - } - // if height is unspecified, scale original proportionally to width ratio. - if (height == 0) { - height = (prev_height * width + prev_width / 2) / prev_width; - } - // Check if the overall dimensions still make sense. - if (width <= 0 || height <= 0) return 0; - - WebPPictureGrabSpecs(pic, &tmp); - tmp.width = width; - tmp.height = height; - if (!WebPPictureAlloc(&tmp)) return 0; - - if (!pic->use_argb) { - work = (int32_t*)WebPSafeMalloc(2ULL * width, sizeof(*work)); - if (work == NULL) { - WebPPictureFree(&tmp); - return 0; - } - - RescalePlane(pic->y, prev_width, prev_height, pic->y_stride, - tmp.y, width, height, tmp.y_stride, work, 1); - RescalePlane(pic->u, - HALVE(prev_width), HALVE(prev_height), pic->uv_stride, - tmp.u, - HALVE(width), HALVE(height), tmp.uv_stride, work, 1); - RescalePlane(pic->v, - HALVE(prev_width), HALVE(prev_height), pic->uv_stride, - tmp.v, - HALVE(width), HALVE(height), tmp.uv_stride, work, 1); - - if (tmp.a != NULL) { - RescalePlane(pic->a, prev_width, prev_height, pic->a_stride, - tmp.a, width, height, tmp.a_stride, work, 1); - } -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (tmp.u0 != NULL) { - const int s = IS_YUV_CSP(tmp.colorspace, WEBP_YUV422) ? 2 : 1; - RescalePlane( - pic->u0, (prev_width + s / 2) / s, prev_height, pic->uv0_stride, - tmp.u0, (width + s / 2) / s, height, tmp.uv0_stride, work, 1); - RescalePlane( - pic->v0, (prev_width + s / 2) / s, prev_height, pic->uv0_stride, - tmp.v0, (width + s / 2) / s, height, tmp.uv0_stride, work, 1); - } -#endif - } else { - work = (int32_t*)WebPSafeMalloc(2ULL * width * 4, sizeof(*work)); - if (work == NULL) { - WebPPictureFree(&tmp); - return 0; - } - - RescalePlane((const uint8_t*)pic->argb, prev_width, prev_height, - pic->argb_stride * 4, - (uint8_t*)tmp.argb, width, height, - tmp.argb_stride * 4, - work, 4); - } - WebPPictureFree(pic); - free(work); - *pic = tmp; - return 1; -} - -//------------------------------------------------------------------------------ -// WebPMemoryWriter: Write-to-memory - -void WebPMemoryWriterInit(WebPMemoryWriter* writer) { - writer->mem = NULL; - writer->size = 0; - writer->max_size = 0; -} - -int WebPMemoryWrite(const uint8_t* data, size_t data_size, - const WebPPicture* picture) { - WebPMemoryWriter* const w = (WebPMemoryWriter*)picture->custom_ptr; - uint64_t next_size; - if (w == NULL) { - return 1; - } - next_size = (uint64_t)w->size + data_size; - if (next_size > w->max_size) { - uint8_t* new_mem; - uint64_t next_max_size = 2ULL * w->max_size; - if (next_max_size < next_size) next_max_size = next_size; - if (next_max_size < 8192ULL) next_max_size = 8192ULL; - new_mem = (uint8_t*)WebPSafeMalloc(next_max_size, 1); - if (new_mem == NULL) { - return 0; - } - if (w->size > 0) { - memcpy(new_mem, w->mem, w->size); - } - free(w->mem); - w->mem = new_mem; - // down-cast is ok, thanks to WebPSafeMalloc - w->max_size = (size_t)next_max_size; - } - if (data_size > 0) { - memcpy(w->mem + w->size, data, data_size); - w->size += data_size; - } - return 1; -} - -//------------------------------------------------------------------------------ -// Detection of non-trivial transparency - -// Returns true if alpha[] has non-0xff values. -static int CheckNonOpaque(const uint8_t* alpha, int width, int height, - int x_step, int y_step) { - if (alpha == NULL) return 0; - while (height-- > 0) { - int x; - for (x = 0; x < width * x_step; x += x_step) { - if (alpha[x] != 0xff) return 1; // TODO(skal): check 4/8 bytes at a time. - } - alpha += y_step; - } - return 0; -} - -// Checking for the presence of non-opaque alpha. -int WebPPictureHasTransparency(const WebPPicture* picture) { - if (picture == NULL) return 0; - if (!picture->use_argb) { - return CheckNonOpaque(picture->a, picture->width, picture->height, - 1, picture->a_stride); - } else { - int x, y; - const uint32_t* argb = picture->argb; - if (argb == NULL) return 0; - for (y = 0; y < picture->height; ++y) { - for (x = 0; x < picture->width; ++x) { - if (argb[x] < 0xff000000u) return 1; // test any alpha values != 0xff - } - argb += picture->argb_stride; - } - } - return 0; -} - -//------------------------------------------------------------------------------ -// RGB -> YUV conversion - -// TODO: we can do better than simply 2x2 averaging on U/V samples. -#define SUM4(ptr) ((ptr)[0] + (ptr)[step] + \ - (ptr)[rgb_stride] + (ptr)[rgb_stride + step]) -#define SUM2H(ptr) (2 * (ptr)[0] + 2 * (ptr)[step]) -#define SUM2V(ptr) (2 * (ptr)[0] + 2 * (ptr)[rgb_stride]) -#define SUM1(ptr) (4 * (ptr)[0]) -#define RGB_TO_UV(x, y, SUM) { \ - const int src = (2 * (step * (x) + (y) * rgb_stride)); \ - const int dst = (x) + (y) * picture->uv_stride; \ - const int r = SUM(r_ptr + src); \ - const int g = SUM(g_ptr + src); \ - const int b = SUM(b_ptr + src); \ - picture->u[dst] = VP8RGBToU(r, g, b); \ - picture->v[dst] = VP8RGBToV(r, g, b); \ -} - -#define RGB_TO_UV0(x_in, x_out, y, SUM) { \ - const int src = (step * (x_in) + (y) * rgb_stride); \ - const int dst = (x_out) + (y) * picture->uv0_stride; \ - const int r = SUM(r_ptr + src); \ - const int g = SUM(g_ptr + src); \ - const int b = SUM(b_ptr + src); \ - picture->u0[dst] = VP8RGBToU(r, g, b); \ - picture->v0[dst] = VP8RGBToV(r, g, b); \ -} - -static void MakeGray(WebPPicture* const picture) { - int y; - const int uv_width = HALVE(picture->width); - const int uv_height = HALVE(picture->height); - for (y = 0; y < uv_height; ++y) { - memset(picture->u + y * picture->uv_stride, 128, uv_width); - memset(picture->v + y * picture->uv_stride, 128, uv_width); - } -} - -static int ImportYUVAFromRGBA(const uint8_t* const r_ptr, - const uint8_t* const g_ptr, - const uint8_t* const b_ptr, - const uint8_t* const a_ptr, - int step, // bytes per pixel - int rgb_stride, // bytes per scanline - WebPPicture* const picture) { - const WebPEncCSP uv_csp = picture->colorspace & WEBP_CSP_UV_MASK; - int x, y; - const int width = picture->width; - const int height = picture->height; - const int has_alpha = CheckNonOpaque(a_ptr, width, height, step, rgb_stride); - - picture->colorspace = uv_csp; - picture->use_argb = 0; - if (has_alpha) { - picture->colorspace |= WEBP_CSP_ALPHA_BIT; - } - if (!WebPPictureAlloc(picture)) return 0; - - // Import luma plane - for (y = 0; y < height; ++y) { - for (x = 0; x < width; ++x) { - const int offset = step * x + y * rgb_stride; - picture->y[x + y * picture->y_stride] = - VP8RGBToY(r_ptr[offset], g_ptr[offset], b_ptr[offset]); - } - } - - // Downsample U/V plane - if (uv_csp != WEBP_YUV400) { - for (y = 0; y < (height >> 1); ++y) { - for (x = 0; x < (width >> 1); ++x) { - RGB_TO_UV(x, y, SUM4); - } - if (width & 1) { - RGB_TO_UV(x, y, SUM2V); - } - } - if (height & 1) { - for (x = 0; x < (width >> 1); ++x) { - RGB_TO_UV(x, y, SUM2H); - } - if (width & 1) { - RGB_TO_UV(x, y, SUM1); - } - } - -#ifdef WEBP_EXPERIMENTAL_FEATURES - // Store original U/V samples too - if (uv_csp == WEBP_YUV422) { - for (y = 0; y < height; ++y) { - for (x = 0; x < (width >> 1); ++x) { - RGB_TO_UV0(2 * x, x, y, SUM2H); - } - if (width & 1) { - RGB_TO_UV0(2 * x, x, y, SUM1); - } - } - } else if (uv_csp == WEBP_YUV444) { - for (y = 0; y < height; ++y) { - for (x = 0; x < width; ++x) { - RGB_TO_UV0(x, x, y, SUM1); - } - } - } -#endif - } else { - MakeGray(picture); - } - - if (has_alpha) { - assert(step >= 4); - for (y = 0; y < height; ++y) { - for (x = 0; x < width; ++x) { - picture->a[x + y * picture->a_stride] = - a_ptr[step * x + y * rgb_stride]; - } - } - } - return 1; -} - -static int Import(WebPPicture* const picture, - const uint8_t* const rgb, int rgb_stride, - int step, int swap_rb, int import_alpha) { - const uint8_t* const r_ptr = rgb + (swap_rb ? 2 : 0); - const uint8_t* const g_ptr = rgb + 1; - const uint8_t* const b_ptr = rgb + (swap_rb ? 0 : 2); - const uint8_t* const a_ptr = import_alpha ? rgb + 3 : NULL; - const int width = picture->width; - const int height = picture->height; - - if (!picture->use_argb) { - return ImportYUVAFromRGBA(r_ptr, g_ptr, b_ptr, a_ptr, step, rgb_stride, - picture); - } - if (import_alpha) { - picture->colorspace |= WEBP_CSP_ALPHA_BIT; - } else { - picture->colorspace &= ~WEBP_CSP_ALPHA_BIT; - } - if (!WebPPictureAlloc(picture)) return 0; - - if (!import_alpha) { - int x, y; - for (y = 0; y < height; ++y) { - for (x = 0; x < width; ++x) { - const int offset = step * x + y * rgb_stride; - const uint32_t argb = - 0xff000000u | - (r_ptr[offset] << 16) | - (g_ptr[offset] << 8) | - (b_ptr[offset]); - picture->argb[x + y * picture->argb_stride] = argb; - } - } - } else { - int x, y; - assert(step >= 4); - for (y = 0; y < height; ++y) { - for (x = 0; x < width; ++x) { - const int offset = step * x + y * rgb_stride; - const uint32_t argb = ((uint32_t)a_ptr[offset] << 24) | - (r_ptr[offset] << 16) | - (g_ptr[offset] << 8) | - (b_ptr[offset]); - picture->argb[x + y * picture->argb_stride] = argb; - } - } - } - return 1; -} -#undef SUM4 -#undef SUM2V -#undef SUM2H -#undef SUM1 -#undef RGB_TO_UV - -int WebPPictureImportRGB(WebPPicture* picture, - const uint8_t* rgb, int rgb_stride) { - return Import(picture, rgb, rgb_stride, 3, 0, 0); -} - -int WebPPictureImportBGR(WebPPicture* picture, - const uint8_t* rgb, int rgb_stride) { - return Import(picture, rgb, rgb_stride, 3, 1, 0); -} - -int WebPPictureImportRGBA(WebPPicture* picture, - const uint8_t* rgba, int rgba_stride) { - return Import(picture, rgba, rgba_stride, 4, 0, 1); -} - -int WebPPictureImportBGRA(WebPPicture* picture, - const uint8_t* rgba, int rgba_stride) { - return Import(picture, rgba, rgba_stride, 4, 1, 1); -} - -int WebPPictureImportRGBX(WebPPicture* picture, - const uint8_t* rgba, int rgba_stride) { - return Import(picture, rgba, rgba_stride, 4, 0, 0); -} - -int WebPPictureImportBGRX(WebPPicture* picture, - const uint8_t* rgba, int rgba_stride) { - return Import(picture, rgba, rgba_stride, 4, 1, 0); -} - -//------------------------------------------------------------------------------ -// Automatic YUV <-> ARGB conversions. - -int WebPPictureYUVAToARGB(WebPPicture* picture) { - if (picture == NULL) return 0; - if (picture->memory_ == NULL || picture->y == NULL || - picture->u == NULL || picture->v == NULL) { - return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER); - } - if ((picture->colorspace & WEBP_CSP_ALPHA_BIT) && picture->a == NULL) { - return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER); - } - if ((picture->colorspace & WEBP_CSP_UV_MASK) != WEBP_YUV420) { - return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION); - } - // Allocate a new argb buffer (discarding the previous one). - if (!PictureAllocARGB(picture)) return 0; - - // Convert - { - int y; - const int width = picture->width; - const int height = picture->height; - const int argb_stride = 4 * picture->argb_stride; - uint8_t* dst = (uint8_t*)picture->argb; - const uint8_t *cur_u = picture->u, *cur_v = picture->v, *cur_y = picture->y; - WebPUpsampleLinePairFunc upsample = WebPGetLinePairConverter(ALPHA_IS_LAST); - - // First row, with replicated top samples. - upsample(NULL, cur_y, cur_u, cur_v, cur_u, cur_v, NULL, dst, width); - cur_y += picture->y_stride; - dst += argb_stride; - // Center rows. - for (y = 1; y + 1 < height; y += 2) { - const uint8_t* const top_u = cur_u; - const uint8_t* const top_v = cur_v; - cur_u += picture->uv_stride; - cur_v += picture->uv_stride; - upsample(cur_y, cur_y + picture->y_stride, top_u, top_v, cur_u, cur_v, - dst, dst + argb_stride, width); - cur_y += 2 * picture->y_stride; - dst += 2 * argb_stride; - } - // Last row (if needed), with replicated bottom samples. - if (height > 1 && !(height & 1)) { - upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, width); - } - // Insert alpha values if needed, in replacement for the default 0xff ones. - if (picture->colorspace & WEBP_CSP_ALPHA_BIT) { - for (y = 0; y < height; ++y) { - uint32_t* const argb_dst = picture->argb + y * picture->argb_stride; - const uint8_t* const src = picture->a + y * picture->a_stride; - int x; - for (x = 0; x < width; ++x) { - argb_dst[x] = (argb_dst[x] & 0x00ffffffu) | ((uint32_t)src[x] << 24); - } - } - } - } - return 1; -} - -int WebPPictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace) { - if (picture == NULL) return 0; - if (picture->argb == NULL) { - return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER); - } else { - const uint8_t* const argb = (const uint8_t*)picture->argb; - const uint8_t* const r = ALPHA_IS_LAST ? argb + 2 : argb + 1; - const uint8_t* const g = ALPHA_IS_LAST ? argb + 1 : argb + 2; - const uint8_t* const b = ALPHA_IS_LAST ? argb + 0 : argb + 3; - const uint8_t* const a = ALPHA_IS_LAST ? argb + 3 : argb + 0; - // We work on a tmp copy of 'picture', because ImportYUVAFromRGBA() - // would be calling WebPPictureFree(picture) otherwise. - WebPPicture tmp = *picture; - PictureResetARGB(&tmp); // reset ARGB buffer so that it's not free()'d. - tmp.use_argb = 0; - tmp.colorspace = colorspace & WEBP_CSP_UV_MASK; - if (!ImportYUVAFromRGBA(r, g, b, a, 4, 4 * picture->argb_stride, &tmp)) { - return WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY); - } - // Copy back the YUV specs into 'picture'. - tmp.argb = picture->argb; - tmp.argb_stride = picture->argb_stride; - tmp.memory_argb_ = picture->memory_argb_; - *picture = tmp; - } - return 1; -} - -//------------------------------------------------------------------------------ -// Helper: clean up fully transparent area to help compressibility. - -#define SIZE 8 -#define SIZE2 (SIZE / 2) -static int is_transparent_area(const uint8_t* ptr, int stride, int size) { - int y, x; - for (y = 0; y < size; ++y) { - for (x = 0; x < size; ++x) { - if (ptr[x]) { - return 0; - } - } - ptr += stride; - } - return 1; -} - -static WEBP_INLINE void flatten(uint8_t* ptr, int v, int stride, int size) { - int y; - for (y = 0; y < size; ++y) { - memset(ptr, v, size); - ptr += stride; - } -} - -void WebPCleanupTransparentArea(WebPPicture* pic) { - int x, y, w, h; - const uint8_t* a_ptr; - int values[3] = { 0 }; - - if (pic == NULL) return; - - a_ptr = pic->a; - if (a_ptr == NULL) return; // nothing to do - - w = pic->width / SIZE; - h = pic->height / SIZE; - for (y = 0; y < h; ++y) { - int need_reset = 1; - for (x = 0; x < w; ++x) { - const int off_a = (y * pic->a_stride + x) * SIZE; - const int off_y = (y * pic->y_stride + x) * SIZE; - const int off_uv = (y * pic->uv_stride + x) * SIZE2; - if (is_transparent_area(a_ptr + off_a, pic->a_stride, SIZE)) { - if (need_reset) { - values[0] = pic->y[off_y]; - values[1] = pic->u[off_uv]; - values[2] = pic->v[off_uv]; - need_reset = 0; - } - flatten(pic->y + off_y, values[0], pic->y_stride, SIZE); - flatten(pic->u + off_uv, values[1], pic->uv_stride, SIZE2); - flatten(pic->v + off_uv, values[2], pic->uv_stride, SIZE2); - } else { - need_reset = 1; - } - } - // ignore the left-overs on right/bottom - } -} - -#undef SIZE -#undef SIZE2 - -//------------------------------------------------------------------------------ -// local-min distortion -// -// For every pixel in the *reference* picture, we search for the local best -// match in the compressed image. This is not a symmetrical measure. - -// search radius. Shouldn't be too large. -#define RADIUS 2 - -static float AccumulateLSIM(const uint8_t* src, int src_stride, - const uint8_t* ref, int ref_stride, - int w, int h) { - int x, y; - double total_sse = 0.; - for (y = 0; y < h; ++y) { - const int y_0 = (y - RADIUS < 0) ? 0 : y - RADIUS; - const int y_1 = (y + RADIUS + 1 >= h) ? h : y + RADIUS + 1; - for (x = 0; x < w; ++x) { - const int x_0 = (x - RADIUS < 0) ? 0 : x - RADIUS; - const int x_1 = (x + RADIUS + 1 >= w) ? w : x + RADIUS + 1; - double best_sse = 255. * 255.; - const double value = (double)ref[y * ref_stride + x]; - int i, j; - for (j = y_0; j < y_1; ++j) { - const uint8_t* s = src + j * src_stride; - for (i = x_0; i < x_1; ++i) { - const double sse = (double)(s[i] - value) * (s[i] - value); - if (sse < best_sse) best_sse = sse; - } - } - total_sse += best_sse; - } - } - return (float)total_sse; -} -#undef RADIUS - -//------------------------------------------------------------------------------ -// Distortion - -// Max value returned in case of exact similarity. -static const double kMinDistortion_dB = 99.; -static float GetPSNR(const double v) { - return (float)((v > 0.) ? -4.3429448 * log(v / (255 * 255.)) - : kMinDistortion_dB); -} - -int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref, - int type, float result[5]) { - DistoStats stats[5]; - int has_alpha; - int uv_w, uv_h; - - if (src == NULL || ref == NULL || - src->width != ref->width || src->height != ref->height || - src->y == NULL || ref->y == NULL || - src->u == NULL || ref->u == NULL || - src->v == NULL || ref->v == NULL || - result == NULL) { - return 0; - } - // TODO(skal): provide distortion for ARGB too. - if (src->use_argb == 1 || src->use_argb != ref->use_argb) { - return 0; - } - - has_alpha = !!(src->colorspace & WEBP_CSP_ALPHA_BIT); - if (has_alpha != !!(ref->colorspace & WEBP_CSP_ALPHA_BIT) || - (has_alpha && (src->a == NULL || ref->a == NULL))) { - return 0; - } - - memset(stats, 0, sizeof(stats)); - - uv_w = HALVE(src->width); - uv_h = HALVE(src->height); - if (type >= 2) { - float sse[4]; - sse[0] = AccumulateLSIM(src->y, src->y_stride, - ref->y, ref->y_stride, src->width, src->height); - sse[1] = AccumulateLSIM(src->u, src->uv_stride, - ref->u, ref->uv_stride, uv_w, uv_h); - sse[2] = AccumulateLSIM(src->v, src->uv_stride, - ref->v, ref->uv_stride, uv_w, uv_h); - sse[3] = has_alpha ? AccumulateLSIM(src->a, src->a_stride, - ref->a, ref->a_stride, - src->width, src->height) - : 0.f; - result[0] = GetPSNR(sse[0] / (src->width * src->height)); - result[1] = GetPSNR(sse[1] / (uv_w * uv_h)); - result[2] = GetPSNR(sse[2] / (uv_w * uv_h)); - result[3] = GetPSNR(sse[3] / (src->width * src->height)); - { - double total_sse = sse[0] + sse[1] + sse[2]; - int total_pixels = src->width * src->height + 2 * uv_w * uv_h; - if (has_alpha) { - total_pixels += src->width * src->height; - total_sse += sse[3]; - } - result[4] = GetPSNR(total_sse / total_pixels); - } - } else { - int c; - VP8SSIMAccumulatePlane(src->y, src->y_stride, - ref->y, ref->y_stride, - src->width, src->height, &stats[0]); - VP8SSIMAccumulatePlane(src->u, src->uv_stride, - ref->u, ref->uv_stride, - uv_w, uv_h, &stats[1]); - VP8SSIMAccumulatePlane(src->v, src->uv_stride, - ref->v, ref->uv_stride, - uv_w, uv_h, &stats[2]); - if (has_alpha) { - VP8SSIMAccumulatePlane(src->a, src->a_stride, - ref->a, ref->a_stride, - src->width, src->height, &stats[3]); - } - for (c = 0; c <= 4; ++c) { - if (type == 1) { - const double v = VP8SSIMGet(&stats[c]); - result[c] = (float)((v < 1.) ? -10.0 * log10(1. - v) - : kMinDistortion_dB); - } else { - const double v = VP8SSIMGetSquaredError(&stats[c]); - result[c] = GetPSNR(v); - } - // Accumulate forward - if (c < 4) VP8SSIMAddStats(&stats[c], &stats[4]); - } - } - return 1; -} - -//------------------------------------------------------------------------------ -// Simplest high-level calls: - -typedef int (*Importer)(WebPPicture* const, const uint8_t* const, int); - -static size_t Encode(const uint8_t* rgba, int width, int height, int stride, - Importer import, float quality_factor, int lossless, - uint8_t** output) { - WebPPicture pic; - WebPConfig config; - WebPMemoryWriter wrt; - int ok; - - if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality_factor) || - !WebPPictureInit(&pic)) { - return 0; // shouldn't happen, except if system installation is broken - } - - config.lossless = !!lossless; - pic.use_argb = !!lossless; - pic.width = width; - pic.height = height; - pic.writer = WebPMemoryWrite; - pic.custom_ptr = &wrt; - WebPMemoryWriterInit(&wrt); - - ok = import(&pic, rgba, stride) && WebPEncode(&config, &pic); - WebPPictureFree(&pic); - if (!ok) { - free(wrt.mem); - *output = NULL; - return 0; - } - *output = wrt.mem; - return wrt.size; -} - -#define ENCODE_FUNC(NAME, IMPORTER) \ -size_t NAME(const uint8_t* in, int w, int h, int bps, float q, \ - uint8_t** out) { \ - return Encode(in, w, h, bps, IMPORTER, q, 0, out); \ -} - -ENCODE_FUNC(WebPEncodeRGB, WebPPictureImportRGB); -ENCODE_FUNC(WebPEncodeBGR, WebPPictureImportBGR); -ENCODE_FUNC(WebPEncodeRGBA, WebPPictureImportRGBA); -ENCODE_FUNC(WebPEncodeBGRA, WebPPictureImportBGRA); - -#undef ENCODE_FUNC - -#define LOSSLESS_DEFAULT_QUALITY 70. -#define LOSSLESS_ENCODE_FUNC(NAME, IMPORTER) \ -size_t NAME(const uint8_t* in, int w, int h, int bps, uint8_t** out) { \ - return Encode(in, w, h, bps, IMPORTER, LOSSLESS_DEFAULT_QUALITY, 1, out); \ -} - -LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGB, WebPPictureImportRGB); -LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessBGR, WebPPictureImportBGR); -LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGBA, WebPPictureImportRGBA); -LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessBGRA, WebPPictureImportBGRA); - -#undef LOSSLESS_ENCODE_FUNC - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/picture_csp_enc.c b/3rdparty/libwebp/enc/picture_csp_enc.c new file mode 100644 index 0000000000..e5d1c75a66 --- /dev/null +++ b/3rdparty/libwebp/enc/picture_csp_enc.c @@ -0,0 +1,1175 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// WebPPicture utils for colorspace conversion +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include + +#include "./vp8i_enc.h" +#include "../utils/random_utils.h" +#include "../utils/utils.h" +#include "../dsp/yuv.h" + +// Uncomment to disable gamma-compression during RGB->U/V averaging +#define USE_GAMMA_COMPRESSION + +// If defined, use table to compute x / alpha. +#define USE_INVERSE_ALPHA_TABLE + +static const union { + uint32_t argb; + uint8_t bytes[4]; +} test_endian = { 0xff000000u }; +#define ALPHA_IS_LAST (test_endian.bytes[3] == 0xff) + +//------------------------------------------------------------------------------ +// Detection of non-trivial transparency + +// Returns true if alpha[] has non-0xff values. +static int CheckNonOpaque(const uint8_t* alpha, int width, int height, + int x_step, int y_step) { + if (alpha == NULL) return 0; + while (height-- > 0) { + int x; + for (x = 0; x < width * x_step; x += x_step) { + if (alpha[x] != 0xff) return 1; // TODO(skal): check 4/8 bytes at a time. + } + alpha += y_step; + } + return 0; +} + +// Checking for the presence of non-opaque alpha. +int WebPPictureHasTransparency(const WebPPicture* picture) { + if (picture == NULL) return 0; + if (!picture->use_argb) { + return CheckNonOpaque(picture->a, picture->width, picture->height, + 1, picture->a_stride); + } else { + int x, y; + const uint32_t* argb = picture->argb; + if (argb == NULL) return 0; + for (y = 0; y < picture->height; ++y) { + for (x = 0; x < picture->width; ++x) { + if (argb[x] < 0xff000000u) return 1; // test any alpha values != 0xff + } + argb += picture->argb_stride; + } + } + return 0; +} + +//------------------------------------------------------------------------------ +// Code for gamma correction + +#if defined(USE_GAMMA_COMPRESSION) + +// gamma-compensates loss of resolution during chroma subsampling +#define kGamma 0.80 // for now we use a different gamma value than kGammaF +#define kGammaFix 12 // fixed-point precision for linear values +#define kGammaScale ((1 << kGammaFix) - 1) +#define kGammaTabFix 7 // fixed-point fractional bits precision +#define kGammaTabScale (1 << kGammaTabFix) +#define kGammaTabRounder (kGammaTabScale >> 1) +#define kGammaTabSize (1 << (kGammaFix - kGammaTabFix)) + +static int kLinearToGammaTab[kGammaTabSize + 1]; +static uint16_t kGammaToLinearTab[256]; +static volatile int kGammaTablesOk = 0; + +static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTables(void) { + if (!kGammaTablesOk) { + int v; + const double scale = (double)(1 << kGammaTabFix) / kGammaScale; + const double norm = 1. / 255.; + for (v = 0; v <= 255; ++v) { + kGammaToLinearTab[v] = + (uint16_t)(pow(norm * v, kGamma) * kGammaScale + .5); + } + for (v = 0; v <= kGammaTabSize; ++v) { + kLinearToGammaTab[v] = (int)(255. * pow(scale * v, 1. / kGamma) + .5); + } + kGammaTablesOk = 1; + } +} + +static WEBP_INLINE uint32_t GammaToLinear(uint8_t v) { + return kGammaToLinearTab[v]; +} + +static WEBP_INLINE int Interpolate(int v) { + const int tab_pos = v >> (kGammaTabFix + 2); // integer part + const int x = v & ((kGammaTabScale << 2) - 1); // fractional part + const int v0 = kLinearToGammaTab[tab_pos]; + const int v1 = kLinearToGammaTab[tab_pos + 1]; + const int y = v1 * x + v0 * ((kGammaTabScale << 2) - x); // interpolate + assert(tab_pos + 1 < kGammaTabSize + 1); + return y; +} + +// Convert a linear value 'v' to YUV_FIX+2 fixed-point precision +// U/V value, suitable for RGBToU/V calls. +static WEBP_INLINE int LinearToGamma(uint32_t base_value, int shift) { + const int y = Interpolate(base_value << shift); // final uplifted value + return (y + kGammaTabRounder) >> kGammaTabFix; // descale +} + +#else + +static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTables(void) {} +static WEBP_INLINE uint32_t GammaToLinear(uint8_t v) { return v; } +static WEBP_INLINE int LinearToGamma(uint32_t base_value, int shift) { + return (int)(base_value << shift); +} + +#endif // USE_GAMMA_COMPRESSION + +//------------------------------------------------------------------------------ +// RGB -> YUV conversion + +static int RGBToY(int r, int g, int b, VP8Random* const rg) { + return (rg == NULL) ? VP8RGBToY(r, g, b, YUV_HALF) + : VP8RGBToY(r, g, b, VP8RandomBits(rg, YUV_FIX)); +} + +static int RGBToU(int r, int g, int b, VP8Random* const rg) { + return (rg == NULL) ? VP8RGBToU(r, g, b, YUV_HALF << 2) + : VP8RGBToU(r, g, b, VP8RandomBits(rg, YUV_FIX + 2)); +} + +static int RGBToV(int r, int g, int b, VP8Random* const rg) { + return (rg == NULL) ? VP8RGBToV(r, g, b, YUV_HALF << 2) + : VP8RGBToV(r, g, b, VP8RandomBits(rg, YUV_FIX + 2)); +} + +//------------------------------------------------------------------------------ +// Sharp RGB->YUV conversion + +static const int kNumIterations = 4; +static const int kMinDimensionIterativeConversion = 4; + +// We could use SFIX=0 and only uint8_t for fixed_y_t, but it produces some +// banding sometimes. Better use extra precision. +#define SFIX 2 // fixed-point precision of RGB and Y/W +typedef int16_t fixed_t; // signed type with extra SFIX precision for UV +typedef uint16_t fixed_y_t; // unsigned type with extra SFIX precision for W + +#define SHALF (1 << SFIX >> 1) +#define MAX_Y_T ((256 << SFIX) - 1) +#define SROUNDER (1 << (YUV_FIX + SFIX - 1)) + +#if defined(USE_GAMMA_COMPRESSION) + +// float variant of gamma-correction +// We use tables of different size and precision for the Rec709 +// transfer function. +#define kGammaF (1./0.45) +static float kGammaToLinearTabF[MAX_Y_T + 1]; // size scales with Y_FIX +static float kLinearToGammaTabF[kGammaTabSize + 2]; +static volatile int kGammaTablesFOk = 0; + +static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) { + if (!kGammaTablesFOk) { + int v; + const double norm = 1. / MAX_Y_T; + const double scale = 1. / kGammaTabSize; + const double a = 0.099; + const double thresh = 0.018; + for (v = 0; v <= MAX_Y_T; ++v) { + const double g = norm * v; + if (g <= thresh * 4.5) { + kGammaToLinearTabF[v] = (float)(g / 4.5); + } else { + const double a_rec = 1. / (1. + a); + kGammaToLinearTabF[v] = (float)pow(a_rec * (g + a), kGammaF); + } + } + for (v = 0; v <= kGammaTabSize; ++v) { + const double g = scale * v; + double value; + if (g <= thresh) { + value = 4.5 * g; + } else { + value = (1. + a) * pow(g, 1. / kGammaF) - a; + } + kLinearToGammaTabF[v] = (float)(MAX_Y_T * value); + } + // to prevent small rounding errors to cause read-overflow: + kLinearToGammaTabF[kGammaTabSize + 1] = kLinearToGammaTabF[kGammaTabSize]; + kGammaTablesFOk = 1; + } +} + +static WEBP_INLINE float GammaToLinearF(int v) { + return kGammaToLinearTabF[v]; +} + +static WEBP_INLINE int LinearToGammaF(float value) { + const float v = value * kGammaTabSize; + const int tab_pos = (int)v; + const float x = v - (float)tab_pos; // fractional part + const float v0 = kLinearToGammaTabF[tab_pos + 0]; + const float v1 = kLinearToGammaTabF[tab_pos + 1]; + const float y = v1 * x + v0 * (1.f - x); // interpolate + return (int)(y + .5); +} + +#else + +static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) {} +static WEBP_INLINE float GammaToLinearF(int v) { + const float norm = 1.f / MAX_Y_T; + return norm * v; +} +static WEBP_INLINE int LinearToGammaF(float value) { + return (int)(MAX_Y_T * value + .5); +} + +#endif // USE_GAMMA_COMPRESSION + +//------------------------------------------------------------------------------ + +static uint8_t clip_8b(fixed_t v) { + return (!(v & ~0xff)) ? (uint8_t)v : (v < 0) ? 0u : 255u; +} + +static fixed_y_t clip_y(int y) { + return (!(y & ~MAX_Y_T)) ? (fixed_y_t)y : (y < 0) ? 0 : MAX_Y_T; +} + +//------------------------------------------------------------------------------ + +static int RGBToGray(int r, int g, int b) { + const int luma = 13933 * r + 46871 * g + 4732 * b + YUV_HALF; + return (luma >> YUV_FIX); +} + +static float RGBToGrayF(float r, float g, float b) { + return (float)(0.2126 * r + 0.7152 * g + 0.0722 * b); +} + +static int ScaleDown(int a, int b, int c, int d) { + const float A = GammaToLinearF(a); + const float B = GammaToLinearF(b); + const float C = GammaToLinearF(c); + const float D = GammaToLinearF(d); + return LinearToGammaF(0.25f * (A + B + C + D)); +} + +static WEBP_INLINE void UpdateW(const fixed_y_t* src, fixed_y_t* dst, int w) { + int i; + for (i = 0; i < w; ++i) { + const float R = GammaToLinearF(src[0 * w + i]); + const float G = GammaToLinearF(src[1 * w + i]); + const float B = GammaToLinearF(src[2 * w + i]); + const float Y = RGBToGrayF(R, G, B); + dst[i] = (fixed_y_t)LinearToGammaF(Y); + } +} + +static void UpdateChroma(const fixed_y_t* src1, const fixed_y_t* src2, + fixed_t* dst, int uv_w) { + int i; + for (i = 0; i < uv_w; ++i) { + const int r = ScaleDown(src1[0 * uv_w + 0], src1[0 * uv_w + 1], + src2[0 * uv_w + 0], src2[0 * uv_w + 1]); + const int g = ScaleDown(src1[2 * uv_w + 0], src1[2 * uv_w + 1], + src2[2 * uv_w + 0], src2[2 * uv_w + 1]); + const int b = ScaleDown(src1[4 * uv_w + 0], src1[4 * uv_w + 1], + src2[4 * uv_w + 0], src2[4 * uv_w + 1]); + const int W = RGBToGray(r, g, b); + dst[0 * uv_w] = (fixed_t)(r - W); + dst[1 * uv_w] = (fixed_t)(g - W); + dst[2 * uv_w] = (fixed_t)(b - W); + dst += 1; + src1 += 2; + src2 += 2; + } +} + +static void StoreGray(const fixed_y_t* rgb, fixed_y_t* y, int w) { + int i; + for (i = 0; i < w; ++i) { + y[i] = RGBToGray(rgb[0 * w + i], rgb[1 * w + i], rgb[2 * w + i]); + } +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE fixed_y_t Filter2(int A, int B, int W0) { + const int v0 = (A * 3 + B + 2) >> 2; + return clip_y(v0 + W0); +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE fixed_y_t UpLift(uint8_t a) { // 8bit -> SFIX + return ((fixed_y_t)a << SFIX) | SHALF; +} + +static void ImportOneRow(const uint8_t* const r_ptr, + const uint8_t* const g_ptr, + const uint8_t* const b_ptr, + int step, + int pic_width, + fixed_y_t* const dst) { + int i; + const int w = (pic_width + 1) & ~1; + for (i = 0; i < pic_width; ++i) { + const int off = i * step; + dst[i + 0 * w] = UpLift(r_ptr[off]); + dst[i + 1 * w] = UpLift(g_ptr[off]); + dst[i + 2 * w] = UpLift(b_ptr[off]); + } + if (pic_width & 1) { // replicate rightmost pixel + dst[pic_width + 0 * w] = dst[pic_width + 0 * w - 1]; + dst[pic_width + 1 * w] = dst[pic_width + 1 * w - 1]; + dst[pic_width + 2 * w] = dst[pic_width + 2 * w - 1]; + } +} + +static void InterpolateTwoRows(const fixed_y_t* const best_y, + const fixed_t* prev_uv, + const fixed_t* cur_uv, + const fixed_t* next_uv, + int w, + fixed_y_t* out1, + fixed_y_t* out2) { + const int uv_w = w >> 1; + const int len = (w - 1) >> 1; // length to filter + int k = 3; + while (k-- > 0) { // process each R/G/B segments in turn + // special boundary case for i==0 + out1[0] = Filter2(cur_uv[0], prev_uv[0], best_y[0]); + out2[0] = Filter2(cur_uv[0], next_uv[0], best_y[w]); + + WebPSharpYUVFilterRow(cur_uv, prev_uv, len, best_y + 0 + 1, out1 + 1); + WebPSharpYUVFilterRow(cur_uv, next_uv, len, best_y + w + 1, out2 + 1); + + // special boundary case for i == w - 1 when w is even + if (!(w & 1)) { + out1[w - 1] = Filter2(cur_uv[uv_w - 1], prev_uv[uv_w - 1], + best_y[w - 1 + 0]); + out2[w - 1] = Filter2(cur_uv[uv_w - 1], next_uv[uv_w - 1], + best_y[w - 1 + w]); + } + out1 += w; + out2 += w; + prev_uv += uv_w; + cur_uv += uv_w; + next_uv += uv_w; + } +} + +static WEBP_INLINE uint8_t ConvertRGBToY(int r, int g, int b) { + const int luma = 16839 * r + 33059 * g + 6420 * b + SROUNDER; + return clip_8b(16 + (luma >> (YUV_FIX + SFIX))); +} + +static WEBP_INLINE uint8_t ConvertRGBToU(int r, int g, int b) { + const int u = -9719 * r - 19081 * g + 28800 * b + SROUNDER; + return clip_8b(128 + (u >> (YUV_FIX + SFIX))); +} + +static WEBP_INLINE uint8_t ConvertRGBToV(int r, int g, int b) { + const int v = +28800 * r - 24116 * g - 4684 * b + SROUNDER; + return clip_8b(128 + (v >> (YUV_FIX + SFIX))); +} + +static int ConvertWRGBToYUV(const fixed_y_t* best_y, const fixed_t* best_uv, + WebPPicture* const picture) { + int i, j; + uint8_t* dst_y = picture->y; + uint8_t* dst_u = picture->u; + uint8_t* dst_v = picture->v; + const fixed_t* const best_uv_base = best_uv; + const int w = (picture->width + 1) & ~1; + const int h = (picture->height + 1) & ~1; + const int uv_w = w >> 1; + const int uv_h = h >> 1; + for (best_uv = best_uv_base, j = 0; j < picture->height; ++j) { + for (i = 0; i < picture->width; ++i) { + const int off = (i >> 1); + const int W = best_y[i]; + const int r = best_uv[off + 0 * uv_w] + W; + const int g = best_uv[off + 1 * uv_w] + W; + const int b = best_uv[off + 2 * uv_w] + W; + dst_y[i] = ConvertRGBToY(r, g, b); + } + best_y += w; + best_uv += (j & 1) * 3 * uv_w; + dst_y += picture->y_stride; + } + for (best_uv = best_uv_base, j = 0; j < uv_h; ++j) { + for (i = 0; i < uv_w; ++i) { + const int off = i; + const int r = best_uv[off + 0 * uv_w]; + const int g = best_uv[off + 1 * uv_w]; + const int b = best_uv[off + 2 * uv_w]; + dst_u[i] = ConvertRGBToU(r, g, b); + dst_v[i] = ConvertRGBToV(r, g, b); + } + best_uv += 3 * uv_w; + dst_u += picture->uv_stride; + dst_v += picture->uv_stride; + } + return 1; +} + +//------------------------------------------------------------------------------ +// Main function + +#define SAFE_ALLOC(W, H, T) ((T*)WebPSafeMalloc((W) * (H), sizeof(T))) + +static int PreprocessARGB(const uint8_t* r_ptr, + const uint8_t* g_ptr, + const uint8_t* b_ptr, + int step, int rgb_stride, + WebPPicture* const picture) { + // we expand the right/bottom border if needed + const int w = (picture->width + 1) & ~1; + const int h = (picture->height + 1) & ~1; + const int uv_w = w >> 1; + const int uv_h = h >> 1; + uint64_t prev_diff_y_sum = ~0; + int j, iter; + + // TODO(skal): allocate one big memory chunk. But for now, it's easier + // for valgrind debugging to have several chunks. + fixed_y_t* const tmp_buffer = SAFE_ALLOC(w * 3, 2, fixed_y_t); // scratch + fixed_y_t* const best_y_base = SAFE_ALLOC(w, h, fixed_y_t); + fixed_y_t* const target_y_base = SAFE_ALLOC(w, h, fixed_y_t); + fixed_y_t* const best_rgb_y = SAFE_ALLOC(w, 2, fixed_y_t); + fixed_t* const best_uv_base = SAFE_ALLOC(uv_w * 3, uv_h, fixed_t); + fixed_t* const target_uv_base = SAFE_ALLOC(uv_w * 3, uv_h, fixed_t); + fixed_t* const best_rgb_uv = SAFE_ALLOC(uv_w * 3, 1, fixed_t); + fixed_y_t* best_y = best_y_base; + fixed_y_t* target_y = target_y_base; + fixed_t* best_uv = best_uv_base; + fixed_t* target_uv = target_uv_base; + const uint64_t diff_y_threshold = (uint64_t)(3.0 * w * h); + int ok; + + if (best_y_base == NULL || best_uv_base == NULL || + target_y_base == NULL || target_uv_base == NULL || + best_rgb_y == NULL || best_rgb_uv == NULL || + tmp_buffer == NULL) { + ok = WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY); + goto End; + } + assert(picture->width >= kMinDimensionIterativeConversion); + assert(picture->height >= kMinDimensionIterativeConversion); + + WebPInitConvertARGBToYUV(); + + // Import RGB samples to W/RGB representation. + for (j = 0; j < picture->height; j += 2) { + const int is_last_row = (j == picture->height - 1); + fixed_y_t* const src1 = tmp_buffer + 0 * w; + fixed_y_t* const src2 = tmp_buffer + 3 * w; + + // prepare two rows of input + ImportOneRow(r_ptr, g_ptr, b_ptr, step, picture->width, src1); + if (!is_last_row) { + ImportOneRow(r_ptr + rgb_stride, g_ptr + rgb_stride, b_ptr + rgb_stride, + step, picture->width, src2); + } else { + memcpy(src2, src1, 3 * w * sizeof(*src2)); + } + StoreGray(src1, best_y + 0, w); + StoreGray(src2, best_y + w, w); + + UpdateW(src1, target_y, w); + UpdateW(src2, target_y + w, w); + UpdateChroma(src1, src2, target_uv, uv_w); + memcpy(best_uv, target_uv, 3 * uv_w * sizeof(*best_uv)); + best_y += 2 * w; + best_uv += 3 * uv_w; + target_y += 2 * w; + target_uv += 3 * uv_w; + r_ptr += 2 * rgb_stride; + g_ptr += 2 * rgb_stride; + b_ptr += 2 * rgb_stride; + } + + // Iterate and resolve clipping conflicts. + for (iter = 0; iter < kNumIterations; ++iter) { + const fixed_t* cur_uv = best_uv_base; + const fixed_t* prev_uv = best_uv_base; + uint64_t diff_y_sum = 0; + + best_y = best_y_base; + best_uv = best_uv_base; + target_y = target_y_base; + target_uv = target_uv_base; + for (j = 0; j < h; j += 2) { + fixed_y_t* const src1 = tmp_buffer + 0 * w; + fixed_y_t* const src2 = tmp_buffer + 3 * w; + { + const fixed_t* const next_uv = cur_uv + ((j < h - 2) ? 3 * uv_w : 0); + InterpolateTwoRows(best_y, prev_uv, cur_uv, next_uv, w, src1, src2); + prev_uv = cur_uv; + cur_uv = next_uv; + } + + UpdateW(src1, best_rgb_y + 0 * w, w); + UpdateW(src2, best_rgb_y + 1 * w, w); + UpdateChroma(src1, src2, best_rgb_uv, uv_w); + + // update two rows of Y and one row of RGB + diff_y_sum += WebPSharpYUVUpdateY(target_y, best_rgb_y, best_y, 2 * w); + WebPSharpYUVUpdateRGB(target_uv, best_rgb_uv, best_uv, 3 * uv_w); + + best_y += 2 * w; + best_uv += 3 * uv_w; + target_y += 2 * w; + target_uv += 3 * uv_w; + } + // test exit condition + if (iter > 0) { + if (diff_y_sum < diff_y_threshold) break; + if (diff_y_sum > prev_diff_y_sum) break; + } + prev_diff_y_sum = diff_y_sum; + } + // final reconstruction + ok = ConvertWRGBToYUV(best_y_base, best_uv_base, picture); + + End: + WebPSafeFree(best_y_base); + WebPSafeFree(best_uv_base); + WebPSafeFree(target_y_base); + WebPSafeFree(target_uv_base); + WebPSafeFree(best_rgb_y); + WebPSafeFree(best_rgb_uv); + WebPSafeFree(tmp_buffer); + return ok; +} +#undef SAFE_ALLOC + +//------------------------------------------------------------------------------ +// "Fast" regular RGB->YUV + +#define SUM4(ptr, step) LinearToGamma( \ + GammaToLinear((ptr)[0]) + \ + GammaToLinear((ptr)[(step)]) + \ + GammaToLinear((ptr)[rgb_stride]) + \ + GammaToLinear((ptr)[rgb_stride + (step)]), 0) \ + +#define SUM2(ptr) \ + LinearToGamma(GammaToLinear((ptr)[0]) + GammaToLinear((ptr)[rgb_stride]), 1) + +#define SUM2ALPHA(ptr) ((ptr)[0] + (ptr)[rgb_stride]) +#define SUM4ALPHA(ptr) (SUM2ALPHA(ptr) + SUM2ALPHA((ptr) + 4)) + +#if defined(USE_INVERSE_ALPHA_TABLE) + +static const int kAlphaFix = 19; +// Following table is (1 << kAlphaFix) / a. The (v * kInvAlpha[a]) >> kAlphaFix +// formula is then equal to v / a in most (99.6%) cases. Note that this table +// and constant are adjusted very tightly to fit 32b arithmetic. +// In particular, they use the fact that the operands for 'v / a' are actually +// derived as v = (a0.p0 + a1.p1 + a2.p2 + a3.p3) and a = a0 + a1 + a2 + a3 +// with ai in [0..255] and pi in [0..1<> (kAlphaFix - 2)) + +#else + +#define DIVIDE_BY_ALPHA(sum, a) (4 * (sum) / (a)) + +#endif // USE_INVERSE_ALPHA_TABLE + +static WEBP_INLINE int LinearToGammaWeighted(const uint8_t* src, + const uint8_t* a_ptr, + uint32_t total_a, int step, + int rgb_stride) { + const uint32_t sum = + a_ptr[0] * GammaToLinear(src[0]) + + a_ptr[step] * GammaToLinear(src[step]) + + a_ptr[rgb_stride] * GammaToLinear(src[rgb_stride]) + + a_ptr[rgb_stride + step] * GammaToLinear(src[rgb_stride + step]); + assert(total_a > 0 && total_a <= 4 * 0xff); +#if defined(USE_INVERSE_ALPHA_TABLE) + assert((uint64_t)sum * kInvAlpha[total_a] < ((uint64_t)1 << 32)); +#endif + return LinearToGamma(DIVIDE_BY_ALPHA(sum, total_a), 0); +} + +static WEBP_INLINE void ConvertRowToY(const uint8_t* const r_ptr, + const uint8_t* const g_ptr, + const uint8_t* const b_ptr, + int step, + uint8_t* const dst_y, + int width, + VP8Random* const rg) { + int i, j; + for (i = 0, j = 0; i < width; i += 1, j += step) { + dst_y[i] = RGBToY(r_ptr[j], g_ptr[j], b_ptr[j], rg); + } +} + +static WEBP_INLINE void AccumulateRGBA(const uint8_t* const r_ptr, + const uint8_t* const g_ptr, + const uint8_t* const b_ptr, + const uint8_t* const a_ptr, + int rgb_stride, + uint16_t* dst, int width) { + int i, j; + // we loop over 2x2 blocks and produce one R/G/B/A value for each. + for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2 * 4, dst += 4) { + const uint32_t a = SUM4ALPHA(a_ptr + j); + int r, g, b; + if (a == 4 * 0xff || a == 0) { + r = SUM4(r_ptr + j, 4); + g = SUM4(g_ptr + j, 4); + b = SUM4(b_ptr + j, 4); + } else { + r = LinearToGammaWeighted(r_ptr + j, a_ptr + j, a, 4, rgb_stride); + g = LinearToGammaWeighted(g_ptr + j, a_ptr + j, a, 4, rgb_stride); + b = LinearToGammaWeighted(b_ptr + j, a_ptr + j, a, 4, rgb_stride); + } + dst[0] = r; + dst[1] = g; + dst[2] = b; + dst[3] = a; + } + if (width & 1) { + const uint32_t a = 2u * SUM2ALPHA(a_ptr + j); + int r, g, b; + if (a == 4 * 0xff || a == 0) { + r = SUM2(r_ptr + j); + g = SUM2(g_ptr + j); + b = SUM2(b_ptr + j); + } else { + r = LinearToGammaWeighted(r_ptr + j, a_ptr + j, a, 0, rgb_stride); + g = LinearToGammaWeighted(g_ptr + j, a_ptr + j, a, 0, rgb_stride); + b = LinearToGammaWeighted(b_ptr + j, a_ptr + j, a, 0, rgb_stride); + } + dst[0] = r; + dst[1] = g; + dst[2] = b; + dst[3] = a; + } +} + +static WEBP_INLINE void AccumulateRGB(const uint8_t* const r_ptr, + const uint8_t* const g_ptr, + const uint8_t* const b_ptr, + int step, int rgb_stride, + uint16_t* dst, int width) { + int i, j; + for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2 * step, dst += 4) { + dst[0] = SUM4(r_ptr + j, step); + dst[1] = SUM4(g_ptr + j, step); + dst[2] = SUM4(b_ptr + j, step); + } + if (width & 1) { + dst[0] = SUM2(r_ptr + j); + dst[1] = SUM2(g_ptr + j); + dst[2] = SUM2(b_ptr + j); + } +} + +static WEBP_INLINE void ConvertRowsToUV(const uint16_t* rgb, + uint8_t* const dst_u, + uint8_t* const dst_v, + int width, + VP8Random* const rg) { + int i; + for (i = 0; i < width; i += 1, rgb += 4) { + const int r = rgb[0], g = rgb[1], b = rgb[2]; + dst_u[i] = RGBToU(r, g, b, rg); + dst_v[i] = RGBToV(r, g, b, rg); + } +} + +static int ImportYUVAFromRGBA(const uint8_t* r_ptr, + const uint8_t* g_ptr, + const uint8_t* b_ptr, + const uint8_t* a_ptr, + int step, // bytes per pixel + int rgb_stride, // bytes per scanline + float dithering, + int use_iterative_conversion, + WebPPicture* const picture) { + int y; + const int width = picture->width; + const int height = picture->height; + const int has_alpha = CheckNonOpaque(a_ptr, width, height, step, rgb_stride); + const int is_rgb = (r_ptr < b_ptr); // otherwise it's bgr + + picture->colorspace = has_alpha ? WEBP_YUV420A : WEBP_YUV420; + picture->use_argb = 0; + + // disable smart conversion if source is too small (overkill). + if (width < kMinDimensionIterativeConversion || + height < kMinDimensionIterativeConversion) { + use_iterative_conversion = 0; + } + + if (!WebPPictureAllocYUVA(picture, width, height)) { + return 0; + } + if (has_alpha) { + WebPInitAlphaProcessing(); + assert(step == 4); +#if defined(USE_GAMMA_COMPRESSION) && defined(USE_INVERSE_ALPHA_TABLE) + assert(kAlphaFix + kGammaFix <= 31); +#endif + } + + if (use_iterative_conversion) { + InitGammaTablesF(); + if (!PreprocessARGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, picture)) { + return 0; + } + if (has_alpha) { + WebPExtractAlpha(a_ptr, rgb_stride, width, height, + picture->a, picture->a_stride); + } + } else { + const int uv_width = (width + 1) >> 1; + int use_dsp = (step == 3); // use special function in this case + // temporary storage for accumulated R/G/B values during conversion to U/V + uint16_t* const tmp_rgb = + (uint16_t*)WebPSafeMalloc(4 * uv_width, sizeof(*tmp_rgb)); + uint8_t* dst_y = picture->y; + uint8_t* dst_u = picture->u; + uint8_t* dst_v = picture->v; + uint8_t* dst_a = picture->a; + + VP8Random base_rg; + VP8Random* rg = NULL; + if (dithering > 0.) { + VP8InitRandom(&base_rg, dithering); + rg = &base_rg; + use_dsp = 0; // can't use dsp in this case + } + WebPInitConvertARGBToYUV(); + InitGammaTables(); + + if (tmp_rgb == NULL) return 0; // malloc error + + // Downsample Y/U/V planes, two rows at a time + for (y = 0; y < (height >> 1); ++y) { + int rows_have_alpha = has_alpha; + if (use_dsp) { + if (is_rgb) { + WebPConvertRGB24ToY(r_ptr, dst_y, width); + WebPConvertRGB24ToY(r_ptr + rgb_stride, + dst_y + picture->y_stride, width); + } else { + WebPConvertBGR24ToY(b_ptr, dst_y, width); + WebPConvertBGR24ToY(b_ptr + rgb_stride, + dst_y + picture->y_stride, width); + } + } else { + ConvertRowToY(r_ptr, g_ptr, b_ptr, step, dst_y, width, rg); + ConvertRowToY(r_ptr + rgb_stride, + g_ptr + rgb_stride, + b_ptr + rgb_stride, step, + dst_y + picture->y_stride, width, rg); + } + dst_y += 2 * picture->y_stride; + if (has_alpha) { + rows_have_alpha &= !WebPExtractAlpha(a_ptr, rgb_stride, width, 2, + dst_a, picture->a_stride); + dst_a += 2 * picture->a_stride; + } + // Collect averaged R/G/B(/A) + if (!rows_have_alpha) { + AccumulateRGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, tmp_rgb, width); + } else { + AccumulateRGBA(r_ptr, g_ptr, b_ptr, a_ptr, rgb_stride, tmp_rgb, width); + } + // Convert to U/V + if (rg == NULL) { + WebPConvertRGBA32ToUV(tmp_rgb, dst_u, dst_v, uv_width); + } else { + ConvertRowsToUV(tmp_rgb, dst_u, dst_v, uv_width, rg); + } + dst_u += picture->uv_stride; + dst_v += picture->uv_stride; + r_ptr += 2 * rgb_stride; + b_ptr += 2 * rgb_stride; + g_ptr += 2 * rgb_stride; + if (has_alpha) a_ptr += 2 * rgb_stride; + } + if (height & 1) { // extra last row + int row_has_alpha = has_alpha; + if (use_dsp) { + if (r_ptr < b_ptr) { + WebPConvertRGB24ToY(r_ptr, dst_y, width); + } else { + WebPConvertBGR24ToY(b_ptr, dst_y, width); + } + } else { + ConvertRowToY(r_ptr, g_ptr, b_ptr, step, dst_y, width, rg); + } + if (row_has_alpha) { + row_has_alpha &= !WebPExtractAlpha(a_ptr, 0, width, 1, dst_a, 0); + } + // Collect averaged R/G/B(/A) + if (!row_has_alpha) { + // Collect averaged R/G/B + AccumulateRGB(r_ptr, g_ptr, b_ptr, step, /* rgb_stride = */ 0, + tmp_rgb, width); + } else { + AccumulateRGBA(r_ptr, g_ptr, b_ptr, a_ptr, /* rgb_stride = */ 0, + tmp_rgb, width); + } + if (rg == NULL) { + WebPConvertRGBA32ToUV(tmp_rgb, dst_u, dst_v, uv_width); + } else { + ConvertRowsToUV(tmp_rgb, dst_u, dst_v, uv_width, rg); + } + } + WebPSafeFree(tmp_rgb); + } + return 1; +} + +#undef SUM4 +#undef SUM2 +#undef SUM4ALPHA +#undef SUM2ALPHA + +//------------------------------------------------------------------------------ +// call for ARGB->YUVA conversion + +static int PictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace, + float dithering, int use_iterative_conversion) { + if (picture == NULL) return 0; + if (picture->argb == NULL) { + return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER); + } else if ((colorspace & WEBP_CSP_UV_MASK) != WEBP_YUV420) { + return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION); + } else { + const uint8_t* const argb = (const uint8_t*)picture->argb; + const uint8_t* const r = ALPHA_IS_LAST ? argb + 2 : argb + 1; + const uint8_t* const g = ALPHA_IS_LAST ? argb + 1 : argb + 2; + const uint8_t* const b = ALPHA_IS_LAST ? argb + 0 : argb + 3; + const uint8_t* const a = ALPHA_IS_LAST ? argb + 3 : argb + 0; + + picture->colorspace = WEBP_YUV420; + return ImportYUVAFromRGBA(r, g, b, a, 4, 4 * picture->argb_stride, + dithering, use_iterative_conversion, picture); + } +} + +int WebPPictureARGBToYUVADithered(WebPPicture* picture, WebPEncCSP colorspace, + float dithering) { + return PictureARGBToYUVA(picture, colorspace, dithering, 0); +} + +int WebPPictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace) { + return PictureARGBToYUVA(picture, colorspace, 0.f, 0); +} + +int WebPPictureSharpARGBToYUVA(WebPPicture* picture) { + return PictureARGBToYUVA(picture, WEBP_YUV420, 0.f, 1); +} +// for backward compatibility +int WebPPictureSmartARGBToYUVA(WebPPicture* picture) { + return WebPPictureSharpARGBToYUVA(picture); +} + +//------------------------------------------------------------------------------ +// call for YUVA -> ARGB conversion + +int WebPPictureYUVAToARGB(WebPPicture* picture) { + if (picture == NULL) return 0; + if (picture->y == NULL || picture->u == NULL || picture->v == NULL) { + return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER); + } + if ((picture->colorspace & WEBP_CSP_ALPHA_BIT) && picture->a == NULL) { + return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER); + } + if ((picture->colorspace & WEBP_CSP_UV_MASK) != WEBP_YUV420) { + return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION); + } + // Allocate a new argb buffer (discarding the previous one). + if (!WebPPictureAllocARGB(picture, picture->width, picture->height)) return 0; + picture->use_argb = 1; + + // Convert + { + int y; + const int width = picture->width; + const int height = picture->height; + const int argb_stride = 4 * picture->argb_stride; + uint8_t* dst = (uint8_t*)picture->argb; + const uint8_t *cur_u = picture->u, *cur_v = picture->v, *cur_y = picture->y; + WebPUpsampleLinePairFunc upsample = WebPGetLinePairConverter(ALPHA_IS_LAST); + + // First row, with replicated top samples. + upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, width); + cur_y += picture->y_stride; + dst += argb_stride; + // Center rows. + for (y = 1; y + 1 < height; y += 2) { + const uint8_t* const top_u = cur_u; + const uint8_t* const top_v = cur_v; + cur_u += picture->uv_stride; + cur_v += picture->uv_stride; + upsample(cur_y, cur_y + picture->y_stride, top_u, top_v, cur_u, cur_v, + dst, dst + argb_stride, width); + cur_y += 2 * picture->y_stride; + dst += 2 * argb_stride; + } + // Last row (if needed), with replicated bottom samples. + if (height > 1 && !(height & 1)) { + upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, width); + } + // Insert alpha values if needed, in replacement for the default 0xff ones. + if (picture->colorspace & WEBP_CSP_ALPHA_BIT) { + for (y = 0; y < height; ++y) { + uint32_t* const argb_dst = picture->argb + y * picture->argb_stride; + const uint8_t* const src = picture->a + y * picture->a_stride; + int x; + for (x = 0; x < width; ++x) { + argb_dst[x] = (argb_dst[x] & 0x00ffffffu) | ((uint32_t)src[x] << 24); + } + } + } + } + return 1; +} + +//------------------------------------------------------------------------------ +// automatic import / conversion + +static int Import(WebPPicture* const picture, + const uint8_t* const rgb, int rgb_stride, + int step, int swap_rb, int import_alpha) { + int y; + const uint8_t* r_ptr = rgb + (swap_rb ? 2 : 0); + const uint8_t* g_ptr = rgb + 1; + const uint8_t* b_ptr = rgb + (swap_rb ? 0 : 2); + const uint8_t* a_ptr = import_alpha ? rgb + 3 : NULL; + const int width = picture->width; + const int height = picture->height; + + if (!picture->use_argb) { + return ImportYUVAFromRGBA(r_ptr, g_ptr, b_ptr, a_ptr, step, rgb_stride, + 0.f /* no dithering */, 0, picture); + } + if (!WebPPictureAlloc(picture)) return 0; + + VP8EncDspARGBInit(); + + if (import_alpha) { + uint32_t* dst = picture->argb; + assert(step == 4); + for (y = 0; y < height; ++y) { + VP8PackARGB(a_ptr, r_ptr, g_ptr, b_ptr, width, dst); + a_ptr += rgb_stride; + r_ptr += rgb_stride; + g_ptr += rgb_stride; + b_ptr += rgb_stride; + dst += picture->argb_stride; + } + } else { + uint32_t* dst = picture->argb; + assert(step >= 3); + for (y = 0; y < height; ++y) { + VP8PackRGB(r_ptr, g_ptr, b_ptr, width, step, dst); + r_ptr += rgb_stride; + g_ptr += rgb_stride; + b_ptr += rgb_stride; + dst += picture->argb_stride; + } + } + return 1; +} + +// Public API + +int WebPPictureImportRGB(WebPPicture* picture, + const uint8_t* rgb, int rgb_stride) { + return (picture != NULL && rgb != NULL) + ? Import(picture, rgb, rgb_stride, 3, 0, 0) + : 0; +} + +int WebPPictureImportBGR(WebPPicture* picture, + const uint8_t* rgb, int rgb_stride) { + return (picture != NULL && rgb != NULL) + ? Import(picture, rgb, rgb_stride, 3, 1, 0) + : 0; +} + +int WebPPictureImportRGBA(WebPPicture* picture, + const uint8_t* rgba, int rgba_stride) { + return (picture != NULL && rgba != NULL) + ? Import(picture, rgba, rgba_stride, 4, 0, 1) + : 0; +} + +int WebPPictureImportBGRA(WebPPicture* picture, + const uint8_t* rgba, int rgba_stride) { + return (picture != NULL && rgba != NULL) + ? Import(picture, rgba, rgba_stride, 4, 1, 1) + : 0; +} + +int WebPPictureImportRGBX(WebPPicture* picture, + const uint8_t* rgba, int rgba_stride) { + return (picture != NULL && rgba != NULL) + ? Import(picture, rgba, rgba_stride, 4, 0, 0) + : 0; +} + +int WebPPictureImportBGRX(WebPPicture* picture, + const uint8_t* rgba, int rgba_stride) { + return (picture != NULL && rgba != NULL) + ? Import(picture, rgba, rgba_stride, 4, 1, 0) + : 0; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/enc/picture_enc.c b/3rdparty/libwebp/enc/picture_enc.c new file mode 100644 index 0000000000..dfa66510fb --- /dev/null +++ b/3rdparty/libwebp/enc/picture_enc.c @@ -0,0 +1,293 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// WebPPicture class basis +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include + +#include "./vp8i_enc.h" +#include "../dsp/dsp.h" +#include "../utils/utils.h" + +//------------------------------------------------------------------------------ +// WebPPicture +//------------------------------------------------------------------------------ + +static int DummyWriter(const uint8_t* data, size_t data_size, + const WebPPicture* const picture) { + // The following are to prevent 'unused variable' error message. + (void)data; + (void)data_size; + (void)picture; + return 1; +} + +int WebPPictureInitInternal(WebPPicture* picture, int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_ENCODER_ABI_VERSION)) { + return 0; // caller/system version mismatch! + } + if (picture != NULL) { + memset(picture, 0, sizeof(*picture)); + picture->writer = DummyWriter; + WebPEncodingSetError(picture, VP8_ENC_OK); + } + return 1; +} + +//------------------------------------------------------------------------------ + +static void WebPPictureResetBufferARGB(WebPPicture* const picture) { + picture->memory_argb_ = NULL; + picture->argb = NULL; + picture->argb_stride = 0; +} + +static void WebPPictureResetBufferYUVA(WebPPicture* const picture) { + picture->memory_ = NULL; + picture->y = picture->u = picture->v = picture->a = NULL; + picture->y_stride = picture->uv_stride = 0; + picture->a_stride = 0; +} + +void WebPPictureResetBuffers(WebPPicture* const picture) { + WebPPictureResetBufferARGB(picture); + WebPPictureResetBufferYUVA(picture); +} + +int WebPPictureAllocARGB(WebPPicture* const picture, int width, int height) { + void* memory; + const uint64_t argb_size = (uint64_t)width * height; + + assert(picture != NULL); + + WebPSafeFree(picture->memory_argb_); + WebPPictureResetBufferARGB(picture); + + if (width <= 0 || height <= 0) { + return WebPEncodingSetError(picture, VP8_ENC_ERROR_BAD_DIMENSION); + } + // allocate a new buffer. + memory = WebPSafeMalloc(argb_size, sizeof(*picture->argb)); + if (memory == NULL) { + return WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY); + } + // TODO(skal): align plane to cache line? + picture->memory_argb_ = memory; + picture->argb = (uint32_t*)memory; + picture->argb_stride = width; + return 1; +} + +int WebPPictureAllocYUVA(WebPPicture* const picture, int width, int height) { + const WebPEncCSP uv_csp = + (WebPEncCSP)((int)picture->colorspace & WEBP_CSP_UV_MASK); + const int has_alpha = (int)picture->colorspace & WEBP_CSP_ALPHA_BIT; + const int y_stride = width; + const int uv_width = (width + 1) >> 1; + const int uv_height = (height + 1) >> 1; + const int uv_stride = uv_width; + int a_width, a_stride; + uint64_t y_size, uv_size, a_size, total_size; + uint8_t* mem; + + assert(picture != NULL); + + WebPSafeFree(picture->memory_); + WebPPictureResetBufferYUVA(picture); + + if (uv_csp != WEBP_YUV420) { + return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION); + } + + // alpha + a_width = has_alpha ? width : 0; + a_stride = a_width; + y_size = (uint64_t)y_stride * height; + uv_size = (uint64_t)uv_stride * uv_height; + a_size = (uint64_t)a_stride * height; + + total_size = y_size + a_size + 2 * uv_size; + + // Security and validation checks + if (width <= 0 || height <= 0 || // luma/alpha param error + uv_width < 0 || uv_height < 0) { // u/v param error + return WebPEncodingSetError(picture, VP8_ENC_ERROR_BAD_DIMENSION); + } + // allocate a new buffer. + mem = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*mem)); + if (mem == NULL) { + return WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY); + } + + // From now on, we're in the clear, we can no longer fail... + picture->memory_ = (void*)mem; + picture->y_stride = y_stride; + picture->uv_stride = uv_stride; + picture->a_stride = a_stride; + + // TODO(skal): we could align the y/u/v planes and adjust stride. + picture->y = mem; + mem += y_size; + + picture->u = mem; + mem += uv_size; + picture->v = mem; + mem += uv_size; + + if (a_size > 0) { + picture->a = mem; + mem += a_size; + } + (void)mem; // makes the static analyzer happy + return 1; +} + +int WebPPictureAlloc(WebPPicture* picture) { + if (picture != NULL) { + const int width = picture->width; + const int height = picture->height; + + WebPPictureFree(picture); // erase previous buffer + + if (!picture->use_argb) { + return WebPPictureAllocYUVA(picture, width, height); + } else { + return WebPPictureAllocARGB(picture, width, height); + } + } + return 1; +} + +void WebPPictureFree(WebPPicture* picture) { + if (picture != NULL) { + WebPSafeFree(picture->memory_); + WebPSafeFree(picture->memory_argb_); + WebPPictureResetBuffers(picture); + } +} + +//------------------------------------------------------------------------------ +// WebPMemoryWriter: Write-to-memory + +void WebPMemoryWriterInit(WebPMemoryWriter* writer) { + writer->mem = NULL; + writer->size = 0; + writer->max_size = 0; +} + +int WebPMemoryWrite(const uint8_t* data, size_t data_size, + const WebPPicture* picture) { + WebPMemoryWriter* const w = (WebPMemoryWriter*)picture->custom_ptr; + uint64_t next_size; + if (w == NULL) { + return 1; + } + next_size = (uint64_t)w->size + data_size; + if (next_size > w->max_size) { + uint8_t* new_mem; + uint64_t next_max_size = 2ULL * w->max_size; + if (next_max_size < next_size) next_max_size = next_size; + if (next_max_size < 8192ULL) next_max_size = 8192ULL; + new_mem = (uint8_t*)WebPSafeMalloc(next_max_size, 1); + if (new_mem == NULL) { + return 0; + } + if (w->size > 0) { + memcpy(new_mem, w->mem, w->size); + } + WebPSafeFree(w->mem); + w->mem = new_mem; + // down-cast is ok, thanks to WebPSafeMalloc + w->max_size = (size_t)next_max_size; + } + if (data_size > 0) { + memcpy(w->mem + w->size, data, data_size); + w->size += data_size; + } + return 1; +} + +void WebPMemoryWriterClear(WebPMemoryWriter* writer) { + if (writer != NULL) { + WebPSafeFree(writer->mem); + writer->mem = NULL; + writer->size = 0; + writer->max_size = 0; + } +} + +//------------------------------------------------------------------------------ +// Simplest high-level calls: + +typedef int (*Importer)(WebPPicture* const, const uint8_t* const, int); + +static size_t Encode(const uint8_t* rgba, int width, int height, int stride, + Importer import, float quality_factor, int lossless, + uint8_t** output) { + WebPPicture pic; + WebPConfig config; + WebPMemoryWriter wrt; + int ok; + + if (output == NULL) return 0; + + if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality_factor) || + !WebPPictureInit(&pic)) { + return 0; // shouldn't happen, except if system installation is broken + } + + config.lossless = !!lossless; + pic.use_argb = !!lossless; + pic.width = width; + pic.height = height; + pic.writer = WebPMemoryWrite; + pic.custom_ptr = &wrt; + WebPMemoryWriterInit(&wrt); + + ok = import(&pic, rgba, stride) && WebPEncode(&config, &pic); + WebPPictureFree(&pic); + if (!ok) { + WebPMemoryWriterClear(&wrt); + *output = NULL; + return 0; + } + *output = wrt.mem; + return wrt.size; +} + +#define ENCODE_FUNC(NAME, IMPORTER) \ +size_t NAME(const uint8_t* in, int w, int h, int bps, float q, \ + uint8_t** out) { \ + return Encode(in, w, h, bps, IMPORTER, q, 0, out); \ +} + +ENCODE_FUNC(WebPEncodeRGB, WebPPictureImportRGB) +ENCODE_FUNC(WebPEncodeBGR, WebPPictureImportBGR) +ENCODE_FUNC(WebPEncodeRGBA, WebPPictureImportRGBA) +ENCODE_FUNC(WebPEncodeBGRA, WebPPictureImportBGRA) + +#undef ENCODE_FUNC + +#define LOSSLESS_DEFAULT_QUALITY 70. +#define LOSSLESS_ENCODE_FUNC(NAME, IMPORTER) \ +size_t NAME(const uint8_t* in, int w, int h, int bps, uint8_t** out) { \ + return Encode(in, w, h, bps, IMPORTER, LOSSLESS_DEFAULT_QUALITY, 1, out); \ +} + +LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGB, WebPPictureImportRGB) +LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessBGR, WebPPictureImportBGR) +LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGBA, WebPPictureImportRGBA) +LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessBGRA, WebPPictureImportBGRA) + +#undef LOSSLESS_ENCODE_FUNC + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/enc/picture_psnr_enc.c b/3rdparty/libwebp/enc/picture_psnr_enc.c new file mode 100644 index 0000000000..9c0b229507 --- /dev/null +++ b/3rdparty/libwebp/enc/picture_psnr_enc.c @@ -0,0 +1,213 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// WebPPicture tools for measuring distortion +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include + +#include "./vp8i_enc.h" +#include "../utils/utils.h" + +typedef double (*AccumulateFunc)(const uint8_t* src, int src_stride, + const uint8_t* ref, int ref_stride, + int w, int h); + +//------------------------------------------------------------------------------ +// local-min distortion +// +// For every pixel in the *reference* picture, we search for the local best +// match in the compressed image. This is not a symmetrical measure. + +#define RADIUS 2 // search radius. Shouldn't be too large. + +static double AccumulateLSIM(const uint8_t* src, int src_stride, + const uint8_t* ref, int ref_stride, + int w, int h) { + int x, y; + double total_sse = 0.; + for (y = 0; y < h; ++y) { + const int y_0 = (y - RADIUS < 0) ? 0 : y - RADIUS; + const int y_1 = (y + RADIUS + 1 >= h) ? h : y + RADIUS + 1; + for (x = 0; x < w; ++x) { + const int x_0 = (x - RADIUS < 0) ? 0 : x - RADIUS; + const int x_1 = (x + RADIUS + 1 >= w) ? w : x + RADIUS + 1; + double best_sse = 255. * 255.; + const double value = (double)ref[y * ref_stride + x]; + int i, j; + for (j = y_0; j < y_1; ++j) { + const uint8_t* const s = src + j * src_stride; + for (i = x_0; i < x_1; ++i) { + const double diff = s[i] - value; + const double sse = diff * diff; + if (sse < best_sse) best_sse = sse; + } + } + total_sse += best_sse; + } + } + return total_sse; +} +#undef RADIUS + +static double AccumulateSSE(const uint8_t* src, int src_stride, + const uint8_t* ref, int ref_stride, + int w, int h) { + int y; + double total_sse = 0.; + for (y = 0; y < h; ++y) { + total_sse += VP8AccumulateSSE(src, ref, w); + src += src_stride; + ref += ref_stride; + } + return total_sse; +} + +//------------------------------------------------------------------------------ + +static double AccumulateSSIM(const uint8_t* src, int src_stride, + const uint8_t* ref, int ref_stride, + int w, int h) { + const int w0 = (w < VP8_SSIM_KERNEL) ? w : VP8_SSIM_KERNEL; + const int w1 = w - VP8_SSIM_KERNEL - 1; + const int h0 = (h < VP8_SSIM_KERNEL) ? h : VP8_SSIM_KERNEL; + const int h1 = h - VP8_SSIM_KERNEL - 1; + int x, y; + double sum = 0.; + for (y = 0; y < h0; ++y) { + for (x = 0; x < w; ++x) { + sum += VP8SSIMGetClipped(src, src_stride, ref, ref_stride, x, y, w, h); + } + } + for (; y < h1; ++y) { + for (x = 0; x < w0; ++x) { + sum += VP8SSIMGetClipped(src, src_stride, ref, ref_stride, x, y, w, h); + } + for (; x < w1; ++x) { + const int off1 = x - VP8_SSIM_KERNEL + (y - VP8_SSIM_KERNEL) * src_stride; + const int off2 = x - VP8_SSIM_KERNEL + (y - VP8_SSIM_KERNEL) * ref_stride; + sum += VP8SSIMGet(src + off1, src_stride, ref + off2, ref_stride); + } + for (; x < w; ++x) { + sum += VP8SSIMGetClipped(src, src_stride, ref, ref_stride, x, y, w, h); + } + } + for (; y < h; ++y) { + for (x = 0; x < w; ++x) { + sum += VP8SSIMGetClipped(src, src_stride, ref, ref_stride, x, y, w, h); + } + } + return sum; +} + +//------------------------------------------------------------------------------ +// Distortion + +// Max value returned in case of exact similarity. +static const double kMinDistortion_dB = 99.; + +static double GetPSNR(double v, double size) { + return (v > 0. && size > 0.) ? -4.3429448 * log(v / (size * 255 * 255.)) + : kMinDistortion_dB; +} + +static double GetLogSSIM(double v, double size) { + v = (size > 0.) ? v / size : 1.; + return (v < 1.) ? -10.0 * log10(1. - v) : kMinDistortion_dB; +} + +int WebPPlaneDistortion(const uint8_t* src, size_t src_stride, + const uint8_t* ref, size_t ref_stride, + int width, int height, size_t x_step, + int type, float* distortion, float* result) { + uint8_t* allocated = NULL; + const AccumulateFunc metric = (type == 0) ? AccumulateSSE : + (type == 1) ? AccumulateSSIM : + AccumulateLSIM; + if (src == NULL || ref == NULL || + src_stride < x_step * width || ref_stride < x_step * width || + result == NULL || distortion == NULL) { + return 0; + } + + VP8SSIMDspInit(); + if (x_step != 1) { // extract a packed plane if needed + int x, y; + uint8_t* tmp1; + uint8_t* tmp2; + allocated = + (uint8_t*)WebPSafeMalloc(2ULL * width * height, sizeof(*allocated)); + if (allocated == NULL) return 0; + tmp1 = allocated; + tmp2 = tmp1 + (size_t)width * height; + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { + tmp1[x + y * width] = src[x * x_step + y * src_stride]; + tmp2[x + y * width] = ref[x * x_step + y * ref_stride]; + } + } + src = tmp1; + ref = tmp2; + } + *distortion = (float)metric(src, width, ref, width, width, height); + WebPSafeFree(allocated); + + *result = (type == 1) ? (float)GetLogSSIM(*distortion, (double)width * height) + : (float)GetPSNR(*distortion, (double)width * height); + return 1; +} + +int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref, + int type, float results[5]) { + int w, h, c; + int ok = 0; + WebPPicture p0, p1; + double total_size = 0., total_distortion = 0.; + if (src == NULL || ref == NULL || + src->width != ref->width || src->height != ref->height || + results == NULL) { + return 0; + } + + VP8SSIMDspInit(); + if (!WebPPictureInit(&p0) || !WebPPictureInit(&p1)) return 0; + w = src->width; + h = src->height; + if (!WebPPictureView(src, 0, 0, w, h, &p0)) goto Error; + if (!WebPPictureView(ref, 0, 0, w, h, &p1)) goto Error; + + // We always measure distortion in ARGB space. + if (p0.use_argb == 0 && !WebPPictureYUVAToARGB(&p0)) goto Error; + if (p1.use_argb == 0 && !WebPPictureYUVAToARGB(&p1)) goto Error; + for (c = 0; c < 4; ++c) { + float distortion; + const size_t stride0 = 4 * (size_t)p0.argb_stride; + const size_t stride1 = 4 * (size_t)p1.argb_stride; + if (!WebPPlaneDistortion((const uint8_t*)p0.argb + c, stride0, + (const uint8_t*)p1.argb + c, stride1, + w, h, 4, type, &distortion, results + c)) { + goto Error; + } + total_distortion += distortion; + total_size += w * h; + } + + results[4] = (type == 1) ? (float)GetLogSSIM(total_distortion, total_size) + : (float)GetPSNR(total_distortion, total_size); + ok = 1; + + Error: + WebPPictureFree(&p0); + WebPPictureFree(&p1); + return ok; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/enc/picture_rescale_enc.c b/3rdparty/libwebp/enc/picture_rescale_enc.c new file mode 100644 index 0000000000..0b7181c0d7 --- /dev/null +++ b/3rdparty/libwebp/enc/picture_rescale_enc.c @@ -0,0 +1,264 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// WebPPicture tools: copy, crop, rescaling and view. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include + +#include "./vp8i_enc.h" +#include "../utils/rescaler_utils.h" +#include "../utils/utils.h" + +#define HALVE(x) (((x) + 1) >> 1) + +// Grab the 'specs' (writer, *opaque, width, height...) from 'src' and copy them +// into 'dst'. Mark 'dst' as not owning any memory. +static void PictureGrabSpecs(const WebPPicture* const src, + WebPPicture* const dst) { + assert(src != NULL && dst != NULL); + *dst = *src; + WebPPictureResetBuffers(dst); +} + +//------------------------------------------------------------------------------ + +// Adjust top-left corner to chroma sample position. +static void SnapTopLeftPosition(const WebPPicture* const pic, + int* const left, int* const top) { + if (!pic->use_argb) { + *left &= ~1; + *top &= ~1; + } +} + +// Adjust top-left corner and verify that the sub-rectangle is valid. +static int AdjustAndCheckRectangle(const WebPPicture* const pic, + int* const left, int* const top, + int width, int height) { + SnapTopLeftPosition(pic, left, top); + if ((*left) < 0 || (*top) < 0) return 0; + if (width <= 0 || height <= 0) return 0; + if ((*left) + width > pic->width) return 0; + if ((*top) + height > pic->height) return 0; + return 1; +} + +int WebPPictureCopy(const WebPPicture* src, WebPPicture* dst) { + if (src == NULL || dst == NULL) return 0; + if (src == dst) return 1; + + PictureGrabSpecs(src, dst); + if (!WebPPictureAlloc(dst)) return 0; + + if (!src->use_argb) { + WebPCopyPlane(src->y, src->y_stride, + dst->y, dst->y_stride, dst->width, dst->height); + WebPCopyPlane(src->u, src->uv_stride, dst->u, dst->uv_stride, + HALVE(dst->width), HALVE(dst->height)); + WebPCopyPlane(src->v, src->uv_stride, dst->v, dst->uv_stride, + HALVE(dst->width), HALVE(dst->height)); + if (dst->a != NULL) { + WebPCopyPlane(src->a, src->a_stride, + dst->a, dst->a_stride, dst->width, dst->height); + } + } else { + WebPCopyPlane((const uint8_t*)src->argb, 4 * src->argb_stride, + (uint8_t*)dst->argb, 4 * dst->argb_stride, + 4 * dst->width, dst->height); + } + return 1; +} + +int WebPPictureIsView(const WebPPicture* picture) { + if (picture == NULL) return 0; + if (picture->use_argb) { + return (picture->memory_argb_ == NULL); + } + return (picture->memory_ == NULL); +} + +int WebPPictureView(const WebPPicture* src, + int left, int top, int width, int height, + WebPPicture* dst) { + if (src == NULL || dst == NULL) return 0; + + // verify rectangle position. + if (!AdjustAndCheckRectangle(src, &left, &top, width, height)) return 0; + + if (src != dst) { // beware of aliasing! We don't want to leak 'memory_'. + PictureGrabSpecs(src, dst); + } + dst->width = width; + dst->height = height; + if (!src->use_argb) { + dst->y = src->y + top * src->y_stride + left; + dst->u = src->u + (top >> 1) * src->uv_stride + (left >> 1); + dst->v = src->v + (top >> 1) * src->uv_stride + (left >> 1); + dst->y_stride = src->y_stride; + dst->uv_stride = src->uv_stride; + if (src->a != NULL) { + dst->a = src->a + top * src->a_stride + left; + dst->a_stride = src->a_stride; + } + } else { + dst->argb = src->argb + top * src->argb_stride + left; + dst->argb_stride = src->argb_stride; + } + return 1; +} + +//------------------------------------------------------------------------------ +// Picture cropping + +int WebPPictureCrop(WebPPicture* pic, + int left, int top, int width, int height) { + WebPPicture tmp; + + if (pic == NULL) return 0; + if (!AdjustAndCheckRectangle(pic, &left, &top, width, height)) return 0; + + PictureGrabSpecs(pic, &tmp); + tmp.width = width; + tmp.height = height; + if (!WebPPictureAlloc(&tmp)) return 0; + + if (!pic->use_argb) { + const int y_offset = top * pic->y_stride + left; + const int uv_offset = (top / 2) * pic->uv_stride + left / 2; + WebPCopyPlane(pic->y + y_offset, pic->y_stride, + tmp.y, tmp.y_stride, width, height); + WebPCopyPlane(pic->u + uv_offset, pic->uv_stride, + tmp.u, tmp.uv_stride, HALVE(width), HALVE(height)); + WebPCopyPlane(pic->v + uv_offset, pic->uv_stride, + tmp.v, tmp.uv_stride, HALVE(width), HALVE(height)); + + if (tmp.a != NULL) { + const int a_offset = top * pic->a_stride + left; + WebPCopyPlane(pic->a + a_offset, pic->a_stride, + tmp.a, tmp.a_stride, width, height); + } + } else { + const uint8_t* const src = + (const uint8_t*)(pic->argb + top * pic->argb_stride + left); + WebPCopyPlane(src, pic->argb_stride * 4, (uint8_t*)tmp.argb, + tmp.argb_stride * 4, width * 4, height); + } + WebPPictureFree(pic); + *pic = tmp; + return 1; +} + +//------------------------------------------------------------------------------ +// Simple picture rescaler + +static void RescalePlane(const uint8_t* src, + int src_width, int src_height, int src_stride, + uint8_t* dst, + int dst_width, int dst_height, int dst_stride, + rescaler_t* const work, + int num_channels) { + WebPRescaler rescaler; + int y = 0; + WebPRescalerInit(&rescaler, src_width, src_height, + dst, dst_width, dst_height, dst_stride, + num_channels, work); + while (y < src_height) { + y += WebPRescalerImport(&rescaler, src_height - y, + src + y * src_stride, src_stride); + WebPRescalerExport(&rescaler); + } +} + +static void AlphaMultiplyARGB(WebPPicture* const pic, int inverse) { + assert(pic->argb != NULL); + WebPMultARGBRows((uint8_t*)pic->argb, pic->argb_stride * sizeof(*pic->argb), + pic->width, pic->height, inverse); +} + +static void AlphaMultiplyY(WebPPicture* const pic, int inverse) { + if (pic->a != NULL) { + WebPMultRows(pic->y, pic->y_stride, pic->a, pic->a_stride, + pic->width, pic->height, inverse); + } +} + +int WebPPictureRescale(WebPPicture* pic, int width, int height) { + WebPPicture tmp; + int prev_width, prev_height; + rescaler_t* work; + + if (pic == NULL) return 0; + prev_width = pic->width; + prev_height = pic->height; + if (!WebPRescalerGetScaledDimensions( + prev_width, prev_height, &width, &height)) { + return 0; + } + + PictureGrabSpecs(pic, &tmp); + tmp.width = width; + tmp.height = height; + if (!WebPPictureAlloc(&tmp)) return 0; + + if (!pic->use_argb) { + work = (rescaler_t*)WebPSafeMalloc(2ULL * width, sizeof(*work)); + if (work == NULL) { + WebPPictureFree(&tmp); + return 0; + } + // If present, we need to rescale alpha first (for AlphaMultiplyY). + if (pic->a != NULL) { + WebPInitAlphaProcessing(); + RescalePlane(pic->a, prev_width, prev_height, pic->a_stride, + tmp.a, width, height, tmp.a_stride, work, 1); + } + + // We take transparency into account on the luma plane only. That's not + // totally exact blending, but still is a good approximation. + AlphaMultiplyY(pic, 0); + RescalePlane(pic->y, prev_width, prev_height, pic->y_stride, + tmp.y, width, height, tmp.y_stride, work, 1); + AlphaMultiplyY(&tmp, 1); + + RescalePlane(pic->u, + HALVE(prev_width), HALVE(prev_height), pic->uv_stride, + tmp.u, + HALVE(width), HALVE(height), tmp.uv_stride, work, 1); + RescalePlane(pic->v, + HALVE(prev_width), HALVE(prev_height), pic->uv_stride, + tmp.v, + HALVE(width), HALVE(height), tmp.uv_stride, work, 1); + } else { + work = (rescaler_t*)WebPSafeMalloc(2ULL * width * 4, sizeof(*work)); + if (work == NULL) { + WebPPictureFree(&tmp); + return 0; + } + // In order to correctly interpolate colors, we need to apply the alpha + // weighting first (black-matting), scale the RGB values, and remove + // the premultiplication afterward (while preserving the alpha channel). + WebPInitAlphaProcessing(); + AlphaMultiplyARGB(pic, 0); + RescalePlane((const uint8_t*)pic->argb, prev_width, prev_height, + pic->argb_stride * 4, + (uint8_t*)tmp.argb, width, height, + tmp.argb_stride * 4, + work, 4); + AlphaMultiplyARGB(&tmp, 1); + } + WebPPictureFree(pic); + WebPSafeFree(work); + *pic = tmp; + return 1; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/enc/picture_tools_enc.c b/3rdparty/libwebp/enc/picture_tools_enc.c new file mode 100644 index 0000000000..895df51156 --- /dev/null +++ b/3rdparty/libwebp/enc/picture_tools_enc.c @@ -0,0 +1,226 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// WebPPicture tools: alpha handling, etc. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "./vp8i_enc.h" +#include "../dsp/yuv.h" + +static WEBP_INLINE uint32_t MakeARGB32(int r, int g, int b) { + return (0xff000000u | (r << 16) | (g << 8) | b); +} + +//------------------------------------------------------------------------------ +// Helper: clean up fully transparent area to help compressibility. + +#define SIZE 8 +#define SIZE2 (SIZE / 2) +static int is_transparent_area(const uint8_t* ptr, int stride, int size) { + int y, x; + for (y = 0; y < size; ++y) { + for (x = 0; x < size; ++x) { + if (ptr[x]) { + return 0; + } + } + ptr += stride; + } + return 1; +} + +static int is_transparent_argb_area(const uint32_t* ptr, int stride, int size) { + int y, x; + for (y = 0; y < size; ++y) { + for (x = 0; x < size; ++x) { + if (ptr[x] & 0xff000000u) { + return 0; + } + } + ptr += stride; + } + return 1; +} + +static void flatten(uint8_t* ptr, int v, int stride, int size) { + int y; + for (y = 0; y < size; ++y) { + memset(ptr, v, size); + ptr += stride; + } +} + +static void flatten_argb(uint32_t* ptr, uint32_t v, int stride, int size) { + int x, y; + for (y = 0; y < size; ++y) { + for (x = 0; x < size; ++x) ptr[x] = v; + ptr += stride; + } +} + +void WebPCleanupTransparentArea(WebPPicture* pic) { + int x, y, w, h; + if (pic == NULL) return; + w = pic->width / SIZE; + h = pic->height / SIZE; + + // note: we ignore the left-overs on right/bottom + if (pic->use_argb) { + uint32_t argb_value = 0; + for (y = 0; y < h; ++y) { + int need_reset = 1; + for (x = 0; x < w; ++x) { + const int off = (y * pic->argb_stride + x) * SIZE; + if (is_transparent_argb_area(pic->argb + off, pic->argb_stride, SIZE)) { + if (need_reset) { + argb_value = pic->argb[off]; + need_reset = 0; + } + flatten_argb(pic->argb + off, argb_value, pic->argb_stride, SIZE); + } else { + need_reset = 1; + } + } + } + } else { + const uint8_t* const a_ptr = pic->a; + int values[3] = { 0 }; + if (a_ptr == NULL) return; // nothing to do + for (y = 0; y < h; ++y) { + int need_reset = 1; + for (x = 0; x < w; ++x) { + const int off_a = (y * pic->a_stride + x) * SIZE; + const int off_y = (y * pic->y_stride + x) * SIZE; + const int off_uv = (y * pic->uv_stride + x) * SIZE2; + if (is_transparent_area(a_ptr + off_a, pic->a_stride, SIZE)) { + if (need_reset) { + values[0] = pic->y[off_y]; + values[1] = pic->u[off_uv]; + values[2] = pic->v[off_uv]; + need_reset = 0; + } + flatten(pic->y + off_y, values[0], pic->y_stride, SIZE); + flatten(pic->u + off_uv, values[1], pic->uv_stride, SIZE2); + flatten(pic->v + off_uv, values[2], pic->uv_stride, SIZE2); + } else { + need_reset = 1; + } + } + } + } +} + +#undef SIZE +#undef SIZE2 + +void WebPCleanupTransparentAreaLossless(WebPPicture* const pic) { + int x, y, w, h; + uint32_t* argb; + assert(pic != NULL && pic->use_argb); + w = pic->width; + h = pic->height; + argb = pic->argb; + + for (y = 0; y < h; ++y) { + for (x = 0; x < w; ++x) { + if ((argb[x] & 0xff000000) == 0) { + argb[x] = 0x00000000; + } + } + argb += pic->argb_stride; + } +} + +//------------------------------------------------------------------------------ +// Blend color and remove transparency info + +#define BLEND(V0, V1, ALPHA) \ + ((((V0) * (255 - (ALPHA)) + (V1) * (ALPHA)) * 0x101) >> 16) +#define BLEND_10BIT(V0, V1, ALPHA) \ + ((((V0) * (1020 - (ALPHA)) + (V1) * (ALPHA)) * 0x101) >> 18) + +void WebPBlendAlpha(WebPPicture* pic, uint32_t background_rgb) { + const int red = (background_rgb >> 16) & 0xff; + const int green = (background_rgb >> 8) & 0xff; + const int blue = (background_rgb >> 0) & 0xff; + int x, y; + if (pic == NULL) return; + if (!pic->use_argb) { + const int uv_width = (pic->width >> 1); // omit last pixel during u/v loop + const int Y0 = VP8RGBToY(red, green, blue, YUV_HALF); + // VP8RGBToU/V expects the u/v values summed over four pixels + const int U0 = VP8RGBToU(4 * red, 4 * green, 4 * blue, 4 * YUV_HALF); + const int V0 = VP8RGBToV(4 * red, 4 * green, 4 * blue, 4 * YUV_HALF); + const int has_alpha = pic->colorspace & WEBP_CSP_ALPHA_BIT; + if (!has_alpha || pic->a == NULL) return; // nothing to do + for (y = 0; y < pic->height; ++y) { + // Luma blending + uint8_t* const y_ptr = pic->y + y * pic->y_stride; + uint8_t* const a_ptr = pic->a + y * pic->a_stride; + for (x = 0; x < pic->width; ++x) { + const int alpha = a_ptr[x]; + if (alpha < 0xff) { + y_ptr[x] = BLEND(Y0, y_ptr[x], a_ptr[x]); + } + } + // Chroma blending every even line + if ((y & 1) == 0) { + uint8_t* const u = pic->u + (y >> 1) * pic->uv_stride; + uint8_t* const v = pic->v + (y >> 1) * pic->uv_stride; + uint8_t* const a_ptr2 = + (y + 1 == pic->height) ? a_ptr : a_ptr + pic->a_stride; + for (x = 0; x < uv_width; ++x) { + // Average four alpha values into a single blending weight. + // TODO(skal): might lead to visible contouring. Can we do better? + const int alpha = + a_ptr[2 * x + 0] + a_ptr[2 * x + 1] + + a_ptr2[2 * x + 0] + a_ptr2[2 * x + 1]; + u[x] = BLEND_10BIT(U0, u[x], alpha); + v[x] = BLEND_10BIT(V0, v[x], alpha); + } + if (pic->width & 1) { // rightmost pixel + const int alpha = 2 * (a_ptr[2 * x + 0] + a_ptr2[2 * x + 0]); + u[x] = BLEND_10BIT(U0, u[x], alpha); + v[x] = BLEND_10BIT(V0, v[x], alpha); + } + } + memset(a_ptr, 0xff, pic->width); + } + } else { + uint32_t* argb = pic->argb; + const uint32_t background = MakeARGB32(red, green, blue); + for (y = 0; y < pic->height; ++y) { + for (x = 0; x < pic->width; ++x) { + const int alpha = (argb[x] >> 24) & 0xff; + if (alpha != 0xff) { + if (alpha > 0) { + int r = (argb[x] >> 16) & 0xff; + int g = (argb[x] >> 8) & 0xff; + int b = (argb[x] >> 0) & 0xff; + r = BLEND(red, r, alpha); + g = BLEND(green, g, alpha); + b = BLEND(blue, b, alpha); + argb[x] = MakeARGB32(r, g, b); + } else { + argb[x] = background; + } + } + } + argb += pic->argb_stride; + } + } +} + +#undef BLEND +#undef BLEND_10BIT + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/enc/predictor_enc.c b/3rdparty/libwebp/enc/predictor_enc.c new file mode 100644 index 0000000000..0639b74f1c --- /dev/null +++ b/3rdparty/libwebp/enc/predictor_enc.c @@ -0,0 +1,750 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transform methods for lossless encoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) +// Urvang Joshi (urvang@google.com) +// Vincent Rabaud (vrabaud@google.com) + +#include "../dsp/lossless.h" +#include "../dsp/lossless_common.h" +#include "./vp8li_enc.h" + +#define MAX_DIFF_COST (1e30f) + +static const float kSpatialPredictorBias = 15.f; +static const int kPredLowEffort = 11; +static const uint32_t kMaskAlpha = 0xff000000; + +// Mostly used to reduce code size + readability +static WEBP_INLINE int GetMin(int a, int b) { return (a > b) ? b : a; } +static WEBP_INLINE int GetMax(int a, int b) { return (a < b) ? b : a; } + +//------------------------------------------------------------------------------ +// Methods to calculate Entropy (Shannon). + +static float PredictionCostSpatial(const int counts[256], int weight_0, + double exp_val) { + const int significant_symbols = 256 >> 4; + const double exp_decay_factor = 0.6; + double bits = weight_0 * counts[0]; + int i; + for (i = 1; i < significant_symbols; ++i) { + bits += exp_val * (counts[i] + counts[256 - i]); + exp_val *= exp_decay_factor; + } + return (float)(-0.1 * bits); +} + +static float PredictionCostSpatialHistogram(const int accumulated[4][256], + const int tile[4][256]) { + int i; + double retval = 0; + for (i = 0; i < 4; ++i) { + const double kExpValue = 0.94; + retval += PredictionCostSpatial(tile[i], 1, kExpValue); + retval += VP8LCombinedShannonEntropy(tile[i], accumulated[i]); + } + return (float)retval; +} + +static WEBP_INLINE void UpdateHisto(int histo_argb[4][256], uint32_t argb) { + ++histo_argb[0][argb >> 24]; + ++histo_argb[1][(argb >> 16) & 0xff]; + ++histo_argb[2][(argb >> 8) & 0xff]; + ++histo_argb[3][argb & 0xff]; +} + +//------------------------------------------------------------------------------ +// Spatial transform functions. + +static WEBP_INLINE void PredictBatch(int mode, int x_start, int y, + int num_pixels, const uint32_t* current, + const uint32_t* upper, uint32_t* out) { + if (x_start == 0) { + if (y == 0) { + // ARGB_BLACK. + VP8LPredictorsSub[0](current, NULL, 1, out); + } else { + // Top one. + VP8LPredictorsSub[2](current, upper, 1, out); + } + ++x_start; + ++out; + --num_pixels; + } + if (y == 0) { + // Left one. + VP8LPredictorsSub[1](current + x_start, NULL, num_pixels, out); + } else { + VP8LPredictorsSub[mode](current + x_start, upper + x_start, num_pixels, + out); + } +} + +static int MaxDiffBetweenPixels(uint32_t p1, uint32_t p2) { + const int diff_a = abs((int)(p1 >> 24) - (int)(p2 >> 24)); + const int diff_r = abs((int)((p1 >> 16) & 0xff) - (int)((p2 >> 16) & 0xff)); + const int diff_g = abs((int)((p1 >> 8) & 0xff) - (int)((p2 >> 8) & 0xff)); + const int diff_b = abs((int)(p1 & 0xff) - (int)(p2 & 0xff)); + return GetMax(GetMax(diff_a, diff_r), GetMax(diff_g, diff_b)); +} + +static int MaxDiffAroundPixel(uint32_t current, uint32_t up, uint32_t down, + uint32_t left, uint32_t right) { + const int diff_up = MaxDiffBetweenPixels(current, up); + const int diff_down = MaxDiffBetweenPixels(current, down); + const int diff_left = MaxDiffBetweenPixels(current, left); + const int diff_right = MaxDiffBetweenPixels(current, right); + return GetMax(GetMax(diff_up, diff_down), GetMax(diff_left, diff_right)); +} + +static uint32_t AddGreenToBlueAndRed(uint32_t argb) { + const uint32_t green = (argb >> 8) & 0xff; + uint32_t red_blue = argb & 0x00ff00ffu; + red_blue += (green << 16) | green; + red_blue &= 0x00ff00ffu; + return (argb & 0xff00ff00u) | red_blue; +} + +static void MaxDiffsForRow(int width, int stride, const uint32_t* const argb, + uint8_t* const max_diffs, int used_subtract_green) { + uint32_t current, up, down, left, right; + int x; + if (width <= 2) return; + current = argb[0]; + right = argb[1]; + if (used_subtract_green) { + current = AddGreenToBlueAndRed(current); + right = AddGreenToBlueAndRed(right); + } + // max_diffs[0] and max_diffs[width - 1] are never used. + for (x = 1; x < width - 1; ++x) { + up = argb[-stride + x]; + down = argb[stride + x]; + left = current; + current = right; + right = argb[x + 1]; + if (used_subtract_green) { + up = AddGreenToBlueAndRed(up); + down = AddGreenToBlueAndRed(down); + right = AddGreenToBlueAndRed(right); + } + max_diffs[x] = MaxDiffAroundPixel(current, up, down, left, right); + } +} + +// Quantize the difference between the actual component value and its prediction +// to a multiple of quantization, working modulo 256, taking care not to cross +// a boundary (inclusive upper limit). +static uint8_t NearLosslessComponent(uint8_t value, uint8_t predict, + uint8_t boundary, int quantization) { + const int residual = (value - predict) & 0xff; + const int boundary_residual = (boundary - predict) & 0xff; + const int lower = residual & ~(quantization - 1); + const int upper = lower + quantization; + // Resolve ties towards a value closer to the prediction (i.e. towards lower + // if value comes after prediction and towards upper otherwise). + const int bias = ((boundary - value) & 0xff) < boundary_residual; + if (residual - lower < upper - residual + bias) { + // lower is closer to residual than upper. + if (residual > boundary_residual && lower <= boundary_residual) { + // Halve quantization step to avoid crossing boundary. This midpoint is + // on the same side of boundary as residual because midpoint >= residual + // (since lower is closer than upper) and residual is above the boundary. + return lower + (quantization >> 1); + } + return lower; + } else { + // upper is closer to residual than lower. + if (residual <= boundary_residual && upper > boundary_residual) { + // Halve quantization step to avoid crossing boundary. This midpoint is + // on the same side of boundary as residual because midpoint <= residual + // (since upper is closer than lower) and residual is below the boundary. + return lower + (quantization >> 1); + } + return upper & 0xff; + } +} + +// Quantize every component of the difference between the actual pixel value and +// its prediction to a multiple of a quantization (a power of 2, not larger than +// max_quantization which is a power of 2, smaller than max_diff). Take care if +// value and predict have undergone subtract green, which means that red and +// blue are represented as offsets from green. +static uint32_t NearLossless(uint32_t value, uint32_t predict, + int max_quantization, int max_diff, + int used_subtract_green) { + int quantization; + uint8_t new_green = 0; + uint8_t green_diff = 0; + uint8_t a, r, g, b; + if (max_diff <= 2) { + return VP8LSubPixels(value, predict); + } + quantization = max_quantization; + while (quantization >= max_diff) { + quantization >>= 1; + } + if ((value >> 24) == 0 || (value >> 24) == 0xff) { + // Preserve transparency of fully transparent or fully opaque pixels. + a = ((value >> 24) - (predict >> 24)) & 0xff; + } else { + a = NearLosslessComponent(value >> 24, predict >> 24, 0xff, quantization); + } + g = NearLosslessComponent((value >> 8) & 0xff, (predict >> 8) & 0xff, 0xff, + quantization); + if (used_subtract_green) { + // The green offset will be added to red and blue components during decoding + // to obtain the actual red and blue values. + new_green = ((predict >> 8) + g) & 0xff; + // The amount by which green has been adjusted during quantization. It is + // subtracted from red and blue for compensation, to avoid accumulating two + // quantization errors in them. + green_diff = (new_green - (value >> 8)) & 0xff; + } + r = NearLosslessComponent(((value >> 16) - green_diff) & 0xff, + (predict >> 16) & 0xff, 0xff - new_green, + quantization); + b = NearLosslessComponent((value - green_diff) & 0xff, predict & 0xff, + 0xff - new_green, quantization); + return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; +} + +// Stores the difference between the pixel and its prediction in "out". +// In case of a lossy encoding, updates the source image to avoid propagating +// the deviation further to pixels which depend on the current pixel for their +// predictions. +static WEBP_INLINE void GetResidual( + int width, int height, uint32_t* const upper_row, + uint32_t* const current_row, const uint8_t* const max_diffs, int mode, + int x_start, int x_end, int y, int max_quantization, int exact, + int used_subtract_green, uint32_t* const out) { + if (exact) { + PredictBatch(mode, x_start, y, x_end - x_start, current_row, upper_row, + out); + } else { + const VP8LPredictorFunc pred_func = VP8LPredictors[mode]; + int x; + for (x = x_start; x < x_end; ++x) { + uint32_t predict; + uint32_t residual; + if (y == 0) { + predict = (x == 0) ? ARGB_BLACK : current_row[x - 1]; // Left. + } else if (x == 0) { + predict = upper_row[x]; // Top. + } else { + predict = pred_func(current_row[x - 1], upper_row + x); + } + if (max_quantization == 1 || mode == 0 || y == 0 || y == height - 1 || + x == 0 || x == width - 1) { + residual = VP8LSubPixels(current_row[x], predict); + } else { + residual = NearLossless(current_row[x], predict, max_quantization, + max_diffs[x], used_subtract_green); + // Update the source image. + current_row[x] = VP8LAddPixels(predict, residual); + // x is never 0 here so we do not need to update upper_row like below. + } + if ((current_row[x] & kMaskAlpha) == 0) { + // If alpha is 0, cleanup RGB. We can choose the RGB values of the + // residual for best compression. The prediction of alpha itself can be + // non-zero and must be kept though. We choose RGB of the residual to be + // 0. + residual &= kMaskAlpha; + // Update the source image. + current_row[x] = predict & ~kMaskAlpha; + // The prediction for the rightmost pixel in a row uses the leftmost + // pixel + // in that row as its top-right context pixel. Hence if we change the + // leftmost pixel of current_row, the corresponding change must be + // applied + // to upper_row as well where top-right context is being read from. + if (x == 0 && y != 0) upper_row[width] = current_row[0]; + } + out[x - x_start] = residual; + } + } +} + +// Returns best predictor and updates the accumulated histogram. +// If max_quantization > 1, assumes that near lossless processing will be +// applied, quantizing residuals to multiples of quantization levels up to +// max_quantization (the actual quantization level depends on smoothness near +// the given pixel). +static int GetBestPredictorForTile(int width, int height, + int tile_x, int tile_y, int bits, + int accumulated[4][256], + uint32_t* const argb_scratch, + const uint32_t* const argb, + int max_quantization, + int exact, int used_subtract_green, + const uint32_t* const modes) { + const int kNumPredModes = 14; + const int start_x = tile_x << bits; + const int start_y = tile_y << bits; + const int tile_size = 1 << bits; + const int max_y = GetMin(tile_size, height - start_y); + const int max_x = GetMin(tile_size, width - start_x); + // Whether there exist columns just outside the tile. + const int have_left = (start_x > 0); + const int have_right = (max_x < width - start_x); + // Position and size of the strip covering the tile and adjacent columns if + // they exist. + const int context_start_x = start_x - have_left; + const int context_width = max_x + have_left + have_right; + const int tiles_per_row = VP8LSubSampleSize(width, bits); + // Prediction modes of the left and above neighbor tiles. + const int left_mode = (tile_x > 0) ? + (modes[tile_y * tiles_per_row + tile_x - 1] >> 8) & 0xff : 0xff; + const int above_mode = (tile_y > 0) ? + (modes[(tile_y - 1) * tiles_per_row + tile_x] >> 8) & 0xff : 0xff; + // The width of upper_row and current_row is one pixel larger than image width + // to allow the top right pixel to point to the leftmost pixel of the next row + // when at the right edge. + uint32_t* upper_row = argb_scratch; + uint32_t* current_row = upper_row + width + 1; + uint8_t* const max_diffs = (uint8_t*)(current_row + width + 1); + float best_diff = MAX_DIFF_COST; + int best_mode = 0; + int mode; + int histo_stack_1[4][256]; + int histo_stack_2[4][256]; + // Need pointers to be able to swap arrays. + int (*histo_argb)[256] = histo_stack_1; + int (*best_histo)[256] = histo_stack_2; + int i, j; + uint32_t residuals[1 << MAX_TRANSFORM_BITS]; + assert(bits <= MAX_TRANSFORM_BITS); + assert(max_x <= (1 << MAX_TRANSFORM_BITS)); + + for (mode = 0; mode < kNumPredModes; ++mode) { + float cur_diff; + int relative_y; + memset(histo_argb, 0, sizeof(histo_stack_1)); + if (start_y > 0) { + // Read the row above the tile which will become the first upper_row. + // Include a pixel to the left if it exists; include a pixel to the right + // in all cases (wrapping to the leftmost pixel of the next row if it does + // not exist). + memcpy(current_row + context_start_x, + argb + (start_y - 1) * width + context_start_x, + sizeof(*argb) * (max_x + have_left + 1)); + } + for (relative_y = 0; relative_y < max_y; ++relative_y) { + const int y = start_y + relative_y; + int relative_x; + uint32_t* tmp = upper_row; + upper_row = current_row; + current_row = tmp; + // Read current_row. Include a pixel to the left if it exists; include a + // pixel to the right in all cases except at the bottom right corner of + // the image (wrapping to the leftmost pixel of the next row if it does + // not exist in the current row). + memcpy(current_row + context_start_x, + argb + y * width + context_start_x, + sizeof(*argb) * (max_x + have_left + (y + 1 < height))); + if (max_quantization > 1 && y >= 1 && y + 1 < height) { + MaxDiffsForRow(context_width, width, argb + y * width + context_start_x, + max_diffs + context_start_x, used_subtract_green); + } + + GetResidual(width, height, upper_row, current_row, max_diffs, mode, + start_x, start_x + max_x, y, max_quantization, exact, + used_subtract_green, residuals); + for (relative_x = 0; relative_x < max_x; ++relative_x) { + UpdateHisto(histo_argb, residuals[relative_x]); + } + } + cur_diff = PredictionCostSpatialHistogram( + (const int (*)[256])accumulated, (const int (*)[256])histo_argb); + // Favor keeping the areas locally similar. + if (mode == left_mode) cur_diff -= kSpatialPredictorBias; + if (mode == above_mode) cur_diff -= kSpatialPredictorBias; + + if (cur_diff < best_diff) { + int (*tmp)[256] = histo_argb; + histo_argb = best_histo; + best_histo = tmp; + best_diff = cur_diff; + best_mode = mode; + } + } + + for (i = 0; i < 4; i++) { + for (j = 0; j < 256; j++) { + accumulated[i][j] += best_histo[i][j]; + } + } + + return best_mode; +} + +// Converts pixels of the image to residuals with respect to predictions. +// If max_quantization > 1, applies near lossless processing, quantizing +// residuals to multiples of quantization levels up to max_quantization +// (the actual quantization level depends on smoothness near the given pixel). +static void CopyImageWithPrediction(int width, int height, + int bits, uint32_t* const modes, + uint32_t* const argb_scratch, + uint32_t* const argb, + int low_effort, int max_quantization, + int exact, int used_subtract_green) { + const int tiles_per_row = VP8LSubSampleSize(width, bits); + // The width of upper_row and current_row is one pixel larger than image width + // to allow the top right pixel to point to the leftmost pixel of the next row + // when at the right edge. + uint32_t* upper_row = argb_scratch; + uint32_t* current_row = upper_row + width + 1; + uint8_t* current_max_diffs = (uint8_t*)(current_row + width + 1); + uint8_t* lower_max_diffs = current_max_diffs + width; + int y; + + for (y = 0; y < height; ++y) { + int x; + uint32_t* const tmp32 = upper_row; + upper_row = current_row; + current_row = tmp32; + memcpy(current_row, argb + y * width, + sizeof(*argb) * (width + (y + 1 < height))); + + if (low_effort) { + PredictBatch(kPredLowEffort, 0, y, width, current_row, upper_row, + argb + y * width); + } else { + if (max_quantization > 1) { + // Compute max_diffs for the lower row now, because that needs the + // contents of argb for the current row, which we will overwrite with + // residuals before proceeding with the next row. + uint8_t* const tmp8 = current_max_diffs; + current_max_diffs = lower_max_diffs; + lower_max_diffs = tmp8; + if (y + 2 < height) { + MaxDiffsForRow(width, width, argb + (y + 1) * width, lower_max_diffs, + used_subtract_green); + } + } + for (x = 0; x < width;) { + const int mode = + (modes[(y >> bits) * tiles_per_row + (x >> bits)] >> 8) & 0xff; + int x_end = x + (1 << bits); + if (x_end > width) x_end = width; + GetResidual(width, height, upper_row, current_row, current_max_diffs, + mode, x, x_end, y, max_quantization, exact, + used_subtract_green, argb + y * width + x); + x = x_end; + } + } + } +} + +// Finds the best predictor for each tile, and converts the image to residuals +// with respect to predictions. If near_lossless_quality < 100, applies +// near lossless processing, shaving off more bits of residuals for lower +// qualities. +void VP8LResidualImage(int width, int height, int bits, int low_effort, + uint32_t* const argb, uint32_t* const argb_scratch, + uint32_t* const image, int near_lossless_quality, + int exact, int used_subtract_green) { + const int tiles_per_row = VP8LSubSampleSize(width, bits); + const int tiles_per_col = VP8LSubSampleSize(height, bits); + int tile_y; + int histo[4][256]; + const int max_quantization = 1 << VP8LNearLosslessBits(near_lossless_quality); + if (low_effort) { + int i; + for (i = 0; i < tiles_per_row * tiles_per_col; ++i) { + image[i] = ARGB_BLACK | (kPredLowEffort << 8); + } + } else { + memset(histo, 0, sizeof(histo)); + for (tile_y = 0; tile_y < tiles_per_col; ++tile_y) { + int tile_x; + for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) { + const int pred = GetBestPredictorForTile(width, height, tile_x, tile_y, + bits, histo, argb_scratch, argb, max_quantization, exact, + used_subtract_green, image); + image[tile_y * tiles_per_row + tile_x] = ARGB_BLACK | (pred << 8); + } + } + } + + CopyImageWithPrediction(width, height, bits, image, argb_scratch, argb, + low_effort, max_quantization, exact, + used_subtract_green); +} + +//------------------------------------------------------------------------------ +// Color transform functions. + +static WEBP_INLINE void MultipliersClear(VP8LMultipliers* const m) { + m->green_to_red_ = 0; + m->green_to_blue_ = 0; + m->red_to_blue_ = 0; +} + +static WEBP_INLINE void ColorCodeToMultipliers(uint32_t color_code, + VP8LMultipliers* const m) { + m->green_to_red_ = (color_code >> 0) & 0xff; + m->green_to_blue_ = (color_code >> 8) & 0xff; + m->red_to_blue_ = (color_code >> 16) & 0xff; +} + +static WEBP_INLINE uint32_t MultipliersToColorCode( + const VP8LMultipliers* const m) { + return 0xff000000u | + ((uint32_t)(m->red_to_blue_) << 16) | + ((uint32_t)(m->green_to_blue_) << 8) | + m->green_to_red_; +} + +static float PredictionCostCrossColor(const int accumulated[256], + const int counts[256]) { + // Favor low entropy, locally and globally. + // Favor small absolute values for PredictionCostSpatial + static const double kExpValue = 2.4; + return VP8LCombinedShannonEntropy(counts, accumulated) + + PredictionCostSpatial(counts, 3, kExpValue); +} + +static float GetPredictionCostCrossColorRed( + const uint32_t* argb, int stride, int tile_width, int tile_height, + VP8LMultipliers prev_x, VP8LMultipliers prev_y, int green_to_red, + const int accumulated_red_histo[256]) { + int histo[256] = { 0 }; + float cur_diff; + + VP8LCollectColorRedTransforms(argb, stride, tile_width, tile_height, + green_to_red, histo); + + cur_diff = PredictionCostCrossColor(accumulated_red_histo, histo); + if ((uint8_t)green_to_red == prev_x.green_to_red_) { + cur_diff -= 3; // favor keeping the areas locally similar + } + if ((uint8_t)green_to_red == prev_y.green_to_red_) { + cur_diff -= 3; // favor keeping the areas locally similar + } + if (green_to_red == 0) { + cur_diff -= 3; + } + return cur_diff; +} + +static void GetBestGreenToRed( + const uint32_t* argb, int stride, int tile_width, int tile_height, + VP8LMultipliers prev_x, VP8LMultipliers prev_y, int quality, + const int accumulated_red_histo[256], VP8LMultipliers* const best_tx) { + const int kMaxIters = 4 + ((7 * quality) >> 8); // in range [4..6] + int green_to_red_best = 0; + int iter, offset; + float best_diff = GetPredictionCostCrossColorRed( + argb, stride, tile_width, tile_height, prev_x, prev_y, + green_to_red_best, accumulated_red_histo); + for (iter = 0; iter < kMaxIters; ++iter) { + // ColorTransformDelta is a 3.5 bit fixed point, so 32 is equal to + // one in color computation. Having initial delta here as 1 is sufficient + // to explore the range of (-2, 2). + const int delta = 32 >> iter; + // Try a negative and a positive delta from the best known value. + for (offset = -delta; offset <= delta; offset += 2 * delta) { + const int green_to_red_cur = offset + green_to_red_best; + const float cur_diff = GetPredictionCostCrossColorRed( + argb, stride, tile_width, tile_height, prev_x, prev_y, + green_to_red_cur, accumulated_red_histo); + if (cur_diff < best_diff) { + best_diff = cur_diff; + green_to_red_best = green_to_red_cur; + } + } + } + best_tx->green_to_red_ = green_to_red_best; +} + +static float GetPredictionCostCrossColorBlue( + const uint32_t* argb, int stride, int tile_width, int tile_height, + VP8LMultipliers prev_x, VP8LMultipliers prev_y, + int green_to_blue, int red_to_blue, const int accumulated_blue_histo[256]) { + int histo[256] = { 0 }; + float cur_diff; + + VP8LCollectColorBlueTransforms(argb, stride, tile_width, tile_height, + green_to_blue, red_to_blue, histo); + + cur_diff = PredictionCostCrossColor(accumulated_blue_histo, histo); + if ((uint8_t)green_to_blue == prev_x.green_to_blue_) { + cur_diff -= 3; // favor keeping the areas locally similar + } + if ((uint8_t)green_to_blue == prev_y.green_to_blue_) { + cur_diff -= 3; // favor keeping the areas locally similar + } + if ((uint8_t)red_to_blue == prev_x.red_to_blue_) { + cur_diff -= 3; // favor keeping the areas locally similar + } + if ((uint8_t)red_to_blue == prev_y.red_to_blue_) { + cur_diff -= 3; // favor keeping the areas locally similar + } + if (green_to_blue == 0) { + cur_diff -= 3; + } + if (red_to_blue == 0) { + cur_diff -= 3; + } + return cur_diff; +} + +#define kGreenRedToBlueNumAxis 8 +#define kGreenRedToBlueMaxIters 7 +static void GetBestGreenRedToBlue( + const uint32_t* argb, int stride, int tile_width, int tile_height, + VP8LMultipliers prev_x, VP8LMultipliers prev_y, int quality, + const int accumulated_blue_histo[256], + VP8LMultipliers* const best_tx) { + const int8_t offset[kGreenRedToBlueNumAxis][2] = + {{0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {-1, 1}, {1, -1}, {1, 1}}; + const int8_t delta_lut[kGreenRedToBlueMaxIters] = { 16, 16, 8, 4, 2, 2, 2 }; + const int iters = + (quality < 25) ? 1 : (quality > 50) ? kGreenRedToBlueMaxIters : 4; + int green_to_blue_best = 0; + int red_to_blue_best = 0; + int iter; + // Initial value at origin: + float best_diff = GetPredictionCostCrossColorBlue( + argb, stride, tile_width, tile_height, prev_x, prev_y, + green_to_blue_best, red_to_blue_best, accumulated_blue_histo); + for (iter = 0; iter < iters; ++iter) { + const int delta = delta_lut[iter]; + int axis; + for (axis = 0; axis < kGreenRedToBlueNumAxis; ++axis) { + const int green_to_blue_cur = + offset[axis][0] * delta + green_to_blue_best; + const int red_to_blue_cur = offset[axis][1] * delta + red_to_blue_best; + const float cur_diff = GetPredictionCostCrossColorBlue( + argb, stride, tile_width, tile_height, prev_x, prev_y, + green_to_blue_cur, red_to_blue_cur, accumulated_blue_histo); + if (cur_diff < best_diff) { + best_diff = cur_diff; + green_to_blue_best = green_to_blue_cur; + red_to_blue_best = red_to_blue_cur; + } + if (quality < 25 && iter == 4) { + // Only axis aligned diffs for lower quality. + break; // next iter. + } + } + if (delta == 2 && green_to_blue_best == 0 && red_to_blue_best == 0) { + // Further iterations would not help. + break; // out of iter-loop. + } + } + best_tx->green_to_blue_ = green_to_blue_best; + best_tx->red_to_blue_ = red_to_blue_best; +} +#undef kGreenRedToBlueMaxIters +#undef kGreenRedToBlueNumAxis + +static VP8LMultipliers GetBestColorTransformForTile( + int tile_x, int tile_y, int bits, + VP8LMultipliers prev_x, + VP8LMultipliers prev_y, + int quality, int xsize, int ysize, + const int accumulated_red_histo[256], + const int accumulated_blue_histo[256], + const uint32_t* const argb) { + const int max_tile_size = 1 << bits; + const int tile_y_offset = tile_y * max_tile_size; + const int tile_x_offset = tile_x * max_tile_size; + const int all_x_max = GetMin(tile_x_offset + max_tile_size, xsize); + const int all_y_max = GetMin(tile_y_offset + max_tile_size, ysize); + const int tile_width = all_x_max - tile_x_offset; + const int tile_height = all_y_max - tile_y_offset; + const uint32_t* const tile_argb = argb + tile_y_offset * xsize + + tile_x_offset; + VP8LMultipliers best_tx; + MultipliersClear(&best_tx); + + GetBestGreenToRed(tile_argb, xsize, tile_width, tile_height, + prev_x, prev_y, quality, accumulated_red_histo, &best_tx); + GetBestGreenRedToBlue(tile_argb, xsize, tile_width, tile_height, + prev_x, prev_y, quality, accumulated_blue_histo, + &best_tx); + return best_tx; +} + +static void CopyTileWithColorTransform(int xsize, int ysize, + int tile_x, int tile_y, + int max_tile_size, + VP8LMultipliers color_transform, + uint32_t* argb) { + const int xscan = GetMin(max_tile_size, xsize - tile_x); + int yscan = GetMin(max_tile_size, ysize - tile_y); + argb += tile_y * xsize + tile_x; + while (yscan-- > 0) { + VP8LTransformColor(&color_transform, argb, xscan); + argb += xsize; + } +} + +void VP8LColorSpaceTransform(int width, int height, int bits, int quality, + uint32_t* const argb, uint32_t* image) { + const int max_tile_size = 1 << bits; + const int tile_xsize = VP8LSubSampleSize(width, bits); + const int tile_ysize = VP8LSubSampleSize(height, bits); + int accumulated_red_histo[256] = { 0 }; + int accumulated_blue_histo[256] = { 0 }; + int tile_x, tile_y; + VP8LMultipliers prev_x, prev_y; + MultipliersClear(&prev_y); + MultipliersClear(&prev_x); + for (tile_y = 0; tile_y < tile_ysize; ++tile_y) { + for (tile_x = 0; tile_x < tile_xsize; ++tile_x) { + int y; + const int tile_x_offset = tile_x * max_tile_size; + const int tile_y_offset = tile_y * max_tile_size; + const int all_x_max = GetMin(tile_x_offset + max_tile_size, width); + const int all_y_max = GetMin(tile_y_offset + max_tile_size, height); + const int offset = tile_y * tile_xsize + tile_x; + if (tile_y != 0) { + ColorCodeToMultipliers(image[offset - tile_xsize], &prev_y); + } + prev_x = GetBestColorTransformForTile(tile_x, tile_y, bits, + prev_x, prev_y, + quality, width, height, + accumulated_red_histo, + accumulated_blue_histo, + argb); + image[offset] = MultipliersToColorCode(&prev_x); + CopyTileWithColorTransform(width, height, tile_x_offset, tile_y_offset, + max_tile_size, prev_x, argb); + + // Gather accumulated histogram data. + for (y = tile_y_offset; y < all_y_max; ++y) { + int ix = y * width + tile_x_offset; + const int ix_end = ix + all_x_max - tile_x_offset; + for (; ix < ix_end; ++ix) { + const uint32_t pix = argb[ix]; + if (ix >= 2 && + pix == argb[ix - 2] && + pix == argb[ix - 1]) { + continue; // repeated pixels are handled by backward references + } + if (ix >= width + 2 && + argb[ix - 2] == argb[ix - width - 2] && + argb[ix - 1] == argb[ix - width - 1] && + pix == argb[ix - width]) { + continue; // repeated pixels are handled by backward references + } + ++accumulated_red_histo[(pix >> 16) & 0xff]; + ++accumulated_blue_histo[(pix >> 0) & 0xff]; + } + } + } + } +} diff --git a/3rdparty/libwebp/enc/quant.c b/3rdparty/libwebp/enc/quant_enc.c similarity index 55% rename from 3rdparty/libwebp/enc/quant.c rename to 3rdparty/libwebp/enc/quant_enc.c index 462d4e9e6e..b118fb2a13 100644 --- a/3rdparty/libwebp/enc/quant.c +++ b/3rdparty/libwebp/enc/quant_enc.c @@ -13,9 +13,10 @@ #include #include +#include // for abs() -#include "./vp8enci.h" -#include "./cost.h" +#include "./vp8i_enc.h" +#include "./cost_enc.h" #define DO_TRELLIS_I4 1 #define DO_TRELLIS_I16 1 // not a huge gain, but ok at low bitrate. @@ -24,18 +25,100 @@ #define MID_ALPHA 64 // neutral value for susceptibility #define MIN_ALPHA 30 // lowest usable value for susceptibility -#define MAX_ALPHA 100 // higher meaninful value for susceptibility +#define MAX_ALPHA 100 // higher meaningful value for susceptibility #define SNS_TO_DQ 0.9 // Scaling constant between the sns value and the QP // power-law modulation. Must be strictly less than 1. -#define I4_PENALTY 4000 // Rate-penalty for quick i4/i16 decision +// number of non-zero coeffs below which we consider the block very flat +// (and apply a penalty to complex predictions) +#define FLATNESS_LIMIT_I16 10 // I16 mode +#define FLATNESS_LIMIT_I4 3 // I4 mode +#define FLATNESS_LIMIT_UV 2 // UV mode +#define FLATNESS_PENALTY 140 // roughly ~1bit per block #define MULT_8B(a, b) (((a) * (b) + 128) >> 8) -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#define RD_DISTO_MULT 256 // distortion multiplier (equivalent of lambda) + +// #define DEBUG_BLOCK + +//------------------------------------------------------------------------------ + +#if defined(DEBUG_BLOCK) + +#include +#include + +static void PrintBlockInfo(const VP8EncIterator* const it, + const VP8ModeScore* const rd) { + int i, j; + const int is_i16 = (it->mb_->type_ == 1); + const uint8_t* const y_in = it->yuv_in_ + Y_OFF_ENC; + const uint8_t* const y_out = it->yuv_out_ + Y_OFF_ENC; + const uint8_t* const uv_in = it->yuv_in_ + U_OFF_ENC; + const uint8_t* const uv_out = it->yuv_out_ + U_OFF_ENC; + printf("SOURCE / OUTPUT / ABS DELTA\n"); + for (j = 0; j < 16; ++j) { + for (i = 0; i < 16; ++i) printf("%3d ", y_in[i + j * BPS]); + printf(" "); + for (i = 0; i < 16; ++i) printf("%3d ", y_out[i + j * BPS]); + printf(" "); + for (i = 0; i < 16; ++i) { + printf("%1d ", abs(y_in[i + j * BPS] - y_out[i + j * BPS])); + } + printf("\n"); + } + printf("\n"); // newline before the U/V block + for (j = 0; j < 8; ++j) { + for (i = 0; i < 8; ++i) printf("%3d ", uv_in[i + j * BPS]); + printf(" "); + for (i = 8; i < 16; ++i) printf("%3d ", uv_in[i + j * BPS]); + printf(" "); + for (i = 0; i < 8; ++i) printf("%3d ", uv_out[i + j * BPS]); + printf(" "); + for (i = 8; i < 16; ++i) printf("%3d ", uv_out[i + j * BPS]); + printf(" "); + for (i = 0; i < 8; ++i) { + printf("%1d ", abs(uv_out[i + j * BPS] - uv_in[i + j * BPS])); + } + printf(" "); + for (i = 8; i < 16; ++i) { + printf("%1d ", abs(uv_out[i + j * BPS] - uv_in[i + j * BPS])); + } + printf("\n"); + } + printf("\nD:%d SD:%d R:%d H:%d nz:0x%x score:%d\n", + (int)rd->D, (int)rd->SD, (int)rd->R, (int)rd->H, (int)rd->nz, + (int)rd->score); + if (is_i16) { + printf("Mode: %d\n", rd->mode_i16); + printf("y_dc_levels:"); + for (i = 0; i < 16; ++i) printf("%3d ", rd->y_dc_levels[i]); + printf("\n"); + } else { + printf("Modes[16]: "); + for (i = 0; i < 16; ++i) printf("%d ", rd->modes_i4[i]); + printf("\n"); + } + printf("y_ac_levels:\n"); + for (j = 0; j < 16; ++j) { + for (i = is_i16 ? 1 : 0; i < 16; ++i) { + printf("%4d ", rd->y_ac_levels[j][i]); + } + printf("\n"); + } + printf("\n"); + printf("uv_levels (mode=%d):\n", rd->mode_uv); + for (j = 0; j < 8; ++j) { + for (i = 0; i < 16; ++i) { + printf("%4d ", rd->uv_levels[j][i]); + } + printf("\n"); + } +} + +#endif // DEBUG_BLOCK //------------------------------------------------------------------------------ @@ -104,31 +187,13 @@ static const uint16_t kAcTable2[128] = { 385, 393, 401, 409, 416, 424, 432, 440 }; -static const uint16_t kCoeffThresh[16] = { - 0, 10, 20, 30, - 10, 20, 30, 30, - 20, 30, 30, 30, - 30, 30, 30, 30 +static const uint8_t kBiasMatrices[3][2] = { // [luma-ac,luma-dc,chroma][dc,ac] + { 96, 110 }, { 96, 108 }, { 110, 115 } }; -// TODO(skal): tune more. Coeff thresholding? -static const uint8_t kBiasMatrices[3][16] = { // [3] = [luma-ac,luma-dc,chroma] - { 96, 96, 96, 96, - 96, 96, 96, 96, - 96, 96, 96, 96, - 96, 96, 96, 96 }, - { 96, 96, 96, 96, - 96, 96, 96, 96, - 96, 96, 96, 96, - 96, 96, 96, 96 }, - { 96, 96, 96, 96, - 96, 96, 96, 96, - 96, 96, 96, 96, - 96, 96, 96, 96 } -}; - -// Sharpening by (slightly) raising the hi-frequency coeffs (only for trellis). +// Sharpening by (slightly) raising the hi-frequency coeffs. // Hack-ish but helpful for mid-bitrate range. Use with care. +#define SHARPEN_BITS 11 // number of descaling bits for sharpening bias static const uint8_t kFreqSharpening[16] = { 0, 30, 60, 90, 30, 60, 90, 90, @@ -141,24 +206,36 @@ static const uint8_t kFreqSharpening[16] = { // Returns the average quantizer static int ExpandMatrix(VP8Matrix* const m, int type) { - int i; - int sum = 0; + int i, sum; + for (i = 0; i < 2; ++i) { + const int is_ac_coeff = (i > 0); + const int bias = kBiasMatrices[type][is_ac_coeff]; + m->iq_[i] = (1 << QFIX) / m->q_[i]; + m->bias_[i] = BIAS(bias); + // zthresh_ is the exact value such that QUANTDIV(coeff, iQ, B) is: + // * zero if coeff <= zthresh + // * non-zero if coeff > zthresh + m->zthresh_[i] = ((1 << QFIX) - 1 - m->bias_[i]) / m->iq_[i]; + } for (i = 2; i < 16; ++i) { m->q_[i] = m->q_[1]; + m->iq_[i] = m->iq_[1]; + m->bias_[i] = m->bias_[1]; + m->zthresh_[i] = m->zthresh_[1]; } - for (i = 0; i < 16; ++i) { - const int j = kZigzag[i]; - const int bias = kBiasMatrices[type][j]; - m->iq_[j] = (1 << QFIX) / m->q_[j]; - m->bias_[j] = BIAS(bias); - // TODO(skal): tune kCoeffThresh[] - m->zthresh_[j] = ((256 /*+ kCoeffThresh[j]*/ - bias) * m->q_[j] + 127) >> 8; - m->sharpen_[j] = (kFreqSharpening[j] * m->q_[j]) >> 11; - sum += m->q_[j]; + for (sum = 0, i = 0; i < 16; ++i) { + if (type == 0) { // we only use sharpening for AC luma coeffs + m->sharpen_[i] = (kFreqSharpening[i] * m->q_[i]) >> SHARPEN_BITS; + } else { + m->sharpen_[i] = 0; + } + sum += m->q_[i]; } return (sum + 8) >> 4; } +static void CheckLambdaValue(int* const v) { if (*v < 1) *v = 1; } + static void SetupMatrices(VP8Encoder* enc) { int i; const int tlambda_scale = @@ -168,7 +245,7 @@ static void SetupMatrices(VP8Encoder* enc) { for (i = 0; i < num_segments; ++i) { VP8SegmentInfo* const m = &enc->dqm_[i]; const int q = m->quant_; - int q4, q16, quv; + int q_i4, q_i16, q_uv; m->y1_.q_[0] = kDcTable[clip(q + enc->dq_y1_dc_, 0, 127)]; m->y1_.q_[1] = kAcTable[clip(q, 0, 127)]; @@ -178,21 +255,33 @@ static void SetupMatrices(VP8Encoder* enc) { m->uv_.q_[0] = kDcTable[clip(q + enc->dq_uv_dc_, 0, 117)]; m->uv_.q_[1] = kAcTable[clip(q + enc->dq_uv_ac_, 0, 127)]; - q4 = ExpandMatrix(&m->y1_, 0); - q16 = ExpandMatrix(&m->y2_, 1); - quv = ExpandMatrix(&m->uv_, 2); + q_i4 = ExpandMatrix(&m->y1_, 0); + q_i16 = ExpandMatrix(&m->y2_, 1); + q_uv = ExpandMatrix(&m->uv_, 2); - // TODO: Switch to kLambda*[] tables? - { - m->lambda_i4_ = (3 * q4 * q4) >> 7; - m->lambda_i16_ = (3 * q16 * q16); - m->lambda_uv_ = (3 * quv * quv) >> 6; - m->lambda_mode_ = (1 * q4 * q4) >> 7; - m->lambda_trellis_i4_ = (7 * q4 * q4) >> 3; - m->lambda_trellis_i16_ = (q16 * q16) >> 2; - m->lambda_trellis_uv_ = (quv *quv) << 1; - m->tlambda_ = (tlambda_scale * q4) >> 5; - } + m->lambda_i4_ = (3 * q_i4 * q_i4) >> 7; + m->lambda_i16_ = (3 * q_i16 * q_i16); + m->lambda_uv_ = (3 * q_uv * q_uv) >> 6; + m->lambda_mode_ = (1 * q_i4 * q_i4) >> 7; + m->lambda_trellis_i4_ = (7 * q_i4 * q_i4) >> 3; + m->lambda_trellis_i16_ = (q_i16 * q_i16) >> 2; + m->lambda_trellis_uv_ = (q_uv * q_uv) << 1; + m->tlambda_ = (tlambda_scale * q_i4) >> 5; + + // none of these constants should be < 1 + CheckLambdaValue(&m->lambda_i4_); + CheckLambdaValue(&m->lambda_i16_); + CheckLambdaValue(&m->lambda_uv_); + CheckLambdaValue(&m->lambda_mode_); + CheckLambdaValue(&m->lambda_trellis_i4_); + CheckLambdaValue(&m->lambda_trellis_i16_); + CheckLambdaValue(&m->lambda_trellis_uv_); + CheckLambdaValue(&m->tlambda_); + + m->min_disto_ = 20 * m->y1_.q_[0]; // quantization-aware min disto + m->max_edge_ = 0; + + m->i4_penalty_ = 1000 * q_i4 * q_i4; } } @@ -201,16 +290,21 @@ static void SetupMatrices(VP8Encoder* enc) { // Very small filter-strength values have close to no visual effect. So we can // save a little decoding-CPU by turning filtering off for these. -#define FSTRENGTH_CUTOFF 3 +#define FSTRENGTH_CUTOFF 2 static void SetupFilterStrength(VP8Encoder* const enc) { int i; - const int level0 = enc->config_->filter_strength; + // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. + const int level0 = 5 * enc->config_->filter_strength; for (i = 0; i < NUM_MB_SEGMENTS; ++i) { - // Segments with lower quantizer will be less filtered. TODO: tune (wrt SNS) - const int level = level0 * 256 * enc->dqm_[i].quant_ / 128; - const int f = level / (256 + enc->dqm_[i].beta_); - enc->dqm_[i].fstrength_ = (f < FSTRENGTH_CUTOFF) ? 0 : (f > 63) ? 63 : f; + VP8SegmentInfo* const m = &enc->dqm_[i]; + // We focus on the quantization of AC coeffs. + const int qstep = kAcTable[clip(m->quant_, 0, 127)] >> 2; + const int base_strength = + VP8FilterStrengthFromDelta(enc->filter_hdr_.sharpness_, qstep); + // Segments with lower complexity ('beta') will be less filtered. + const int f = base_strength * level0 / (256 + m->beta_); + m->fstrength_ = (f < FSTRENGTH_CUTOFF) ? 0 : (f > 63) ? 63 : f; } // We record the initial strength (mainly for the case of 1-segment only). enc->filter_hdr_.level_ = enc->dqm_[0].fstrength_; @@ -234,7 +328,7 @@ static double QualityToCompression(double c) { // exponent is somewhere between 2.8 and 3.2, but we're mostly interested // in the mid-quant range. So we scale the compressibility inversely to // this power-law: quant ~= compression ^ 1/3. This law holds well for - // low quant. Finer modelling for high-quant would make use of kAcTable[] + // low quant. Finer modeling for high-quant would make use of kAcTable[] // more explicitly. const double v = pow(linear_c, 1 / 3.); return v; @@ -266,7 +360,12 @@ static int SegmentsAreEquivalent(const VP8SegmentInfo* const S1, static void SimplifySegments(VP8Encoder* const enc) { int map[NUM_MB_SEGMENTS] = { 0, 1, 2, 3 }; - const int num_segments = enc->segment_hdr_.num_segments_; + // 'num_segments_' is previously validated and <= NUM_MB_SEGMENTS, but an + // explicit check is needed to avoid a spurious warning about 'i' exceeding + // array bounds of 'dqm_' with some compilers (noticed with gcc-4.9). + const int num_segments = (enc->segment_hdr_.num_segments_ < NUM_MB_SEGMENTS) + ? enc->segment_hdr_.num_segments_ + : NUM_MB_SEGMENTS; int num_final_segments = 1; int s1, s2; for (s1 = 1; s1 < num_segments; ++s1) { // find similar segments @@ -337,7 +436,7 @@ void VP8SetSegmentParams(VP8Encoder* const enc, float quality) { dq_uv_ac = clip(dq_uv_ac, MIN_DQ_UV, MAX_DQ_UV); // We also boost the dc-uv-quant a little, based on sns-strength, since // U/V channels are quite more reactive to high quants (flat DC-blocks - // tend to appear, and are displeasant). + // tend to appear, and are unpleasant). dq_uv_dc = -4 * enc->config_->sns_strength / 100; dq_uv_dc = clip(dq_uv_dc, -15, 15); // 4bit-signed max allowed @@ -367,16 +466,14 @@ const int VP8I4ModeOffsets[NUM_BMODES] = { }; void VP8MakeLuma16Preds(const VP8EncIterator* const it) { - const VP8Encoder* const enc = it->enc_; - const uint8_t* const left = it->x_ ? enc->y_left_ : NULL; - const uint8_t* const top = it->y_ ? enc->y_top_ + it->x_ * 16 : NULL; + const uint8_t* const left = it->x_ ? it->y_left_ : NULL; + const uint8_t* const top = it->y_ ? it->y_top_ : NULL; VP8EncPredLuma16(it->yuv_p_, left, top); } void VP8MakeChroma8Preds(const VP8EncIterator* const it) { - const VP8Encoder* const enc = it->enc_; - const uint8_t* const left = it->x_ ? enc->u_left_ : NULL; - const uint8_t* const top = it->y_ ? enc->uv_top_ + it->x_ * 16 : NULL; + const uint8_t* const left = it->x_ ? it->u_left_ : NULL; + const uint8_t* const top = it->y_ ? it->uv_top_ : NULL; VP8EncPredChroma8(it->yuv_p_, left, top); } @@ -388,23 +485,21 @@ void VP8MakeIntra4Preds(const VP8EncIterator* const it) { // Quantize // Layout: -// +----+ -// |YYYY| 0 -// |YYYY| 4 -// |YYYY| 8 -// |YYYY| 12 -// +----+ -// |UUVV| 16 -// |UUVV| 20 -// +----+ +// +----+----+ +// |YYYY|UUVV| 0 +// |YYYY|UUVV| 4 +// |YYYY|....| 8 +// |YYYY|....| 12 +// +----+----+ -const int VP8Scan[16 + 4 + 4] = { - // Luma +const int VP8Scan[16] = { // Luma 0 + 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS, 0 + 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS, 0 + 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS, 0 + 12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS, +}; +static const int VP8ScanUV[4 + 4] = { 0 + 0 * BPS, 4 + 0 * BPS, 0 + 4 * BPS, 4 + 4 * BPS, // U 8 + 0 * BPS, 12 + 0 * BPS, 8 + 4 * BPS, 12 + 4 * BPS // V }; @@ -432,6 +527,7 @@ static void InitScore(VP8ModeScore* const rd) { rd->D = 0; rd->SD = 0; rd->R = 0; + rd->H = 0; rd->nz = 0; rd->score = MAX_COST; } @@ -440,6 +536,7 @@ static void CopyScore(VP8ModeScore* const dst, const VP8ModeScore* const src) { dst->D = src->D; dst->SD = src->SD; dst->R = src->R; + dst->H = src->H; dst->nz = src->nz; // note that nz is not accumulated, but just copied. dst->score = src->score; } @@ -448,6 +545,7 @@ static void AddScore(VP8ModeScore* const dst, const VP8ModeScore* const src) { dst->D += src->D; dst->SD += src->SD; dst->R += src->R; + dst->H += src->H; dst->nz |= src->nz; // here, new nz bits are accumulated. dst->score += src->score; } @@ -455,63 +553,68 @@ static void AddScore(VP8ModeScore* const dst, const VP8ModeScore* const src) { //------------------------------------------------------------------------------ // Performs trellis-optimized quantization. -// Trellis - +// Trellis node typedef struct { - int prev; // best previous - int level; // level - int sign; // sign of coeff_i - score_t cost; // bit cost - score_t error; // distortion = sum of (|coeff_i| - level_i * Q_i)^2 - int ctx; // context (only depends on 'level'. Could be spared.) + int8_t prev; // best previous node + int8_t sign; // sign of coeff_i + int16_t level; // level } Node; +// Score state +typedef struct { + score_t score; // partial RD score + const uint16_t* costs; // shortcut to cost tables +} ScoreState; + // If a coefficient was quantized to a value Q (using a neutral bias), // we test all alternate possibilities between [Q-MIN_DELTA, Q+MAX_DELTA] // We don't test negative values though. #define MIN_DELTA 0 // how much lower level to try #define MAX_DELTA 1 // how much higher #define NUM_NODES (MIN_DELTA + 1 + MAX_DELTA) -#define NODE(n, l) (nodes[(n) + 1][(l) + MIN_DELTA]) +#define NODE(n, l) (nodes[(n)][(l) + MIN_DELTA]) +#define SCORE_STATE(n, l) (score_states[n][(l) + MIN_DELTA]) static WEBP_INLINE void SetRDScore(int lambda, VP8ModeScore* const rd) { - // TODO: incorporate the "* 256" in the tables? - rd->score = rd->R * lambda + 256 * (rd->D + rd->SD); + rd->score = (rd->R + rd->H) * lambda + RD_DISTO_MULT * (rd->D + rd->SD); } static WEBP_INLINE score_t RDScoreTrellis(int lambda, score_t rate, score_t distortion) { - return rate * lambda + 256 * distortion; + return rate * lambda + RD_DISTO_MULT * distortion; } -static int TrellisQuantizeBlock(const VP8EncIterator* const it, +static int TrellisQuantizeBlock(const VP8Encoder* const enc, int16_t in[16], int16_t out[16], int ctx0, int coeff_type, const VP8Matrix* const mtx, int lambda) { - ProbaArray* const last_costs = it->enc_->proba_.coeffs_[coeff_type]; - CostArray* const costs = it->enc_->proba_.level_cost_[coeff_type]; + const ProbaArray* const probas = enc->proba_.coeffs_[coeff_type]; + CostArrayPtr const costs = + (CostArrayPtr)enc->proba_.remapped_costs_[coeff_type]; const int first = (coeff_type == 0) ? 1 : 0; - Node nodes[17][NUM_NODES]; + Node nodes[16][NUM_NODES]; + ScoreState score_states[2][NUM_NODES]; + ScoreState* ss_cur = &SCORE_STATE(0, MIN_DELTA); + ScoreState* ss_prev = &SCORE_STATE(1, MIN_DELTA); int best_path[3] = {-1, -1, -1}; // store best-last/best-level/best-previous score_t best_score; - int best_node; - int last = first - 1; - int n, m, p, nz; + int n, m, p, last; { score_t cost; - score_t max_error; const int thresh = mtx->q_[1] * mtx->q_[1] / 4; - const int last_proba = last_costs[VP8EncBands[first]][ctx0][0]; + const int last_proba = probas[VP8EncBands[first]][ctx0][0]; - // compute maximal distortion. - max_error = 0; - for (n = first; n < 16; ++n) { - const int j = kZigzag[n]; + // compute the position of the last interesting coefficient + last = first - 1; + for (n = 15; n >= first; --n) { + const int j = kZigzag[n]; const int err = in[j] * in[j]; - max_error += kWeightTrellis[j] * err; - if (err > thresh) last = n; + if (err > thresh) { + last = n; + break; + } } // we don't need to go inspect up to n = 16 coeffs. We can just go up // to last + 1 (inclusive) without losing much. @@ -519,93 +622,94 @@ static int TrellisQuantizeBlock(const VP8EncIterator* const it, // compute 'skip' score. This is the max score one can do. cost = VP8BitCost(0, last_proba); - best_score = RDScoreTrellis(lambda, cost, max_error); + best_score = RDScoreTrellis(lambda, cost, 0); // initialize source node. - n = first - 1; for (m = -MIN_DELTA; m <= MAX_DELTA; ++m) { - NODE(n, m).cost = 0; - NODE(n, m).error = max_error; - NODE(n, m).ctx = ctx0; + const score_t rate = (ctx0 == 0) ? VP8BitCost(1, last_proba) : 0; + ss_cur[m].score = RDScoreTrellis(lambda, rate, 0); + ss_cur[m].costs = costs[first][ctx0]; } } // traverse trellis. for (n = first; n <= last; ++n) { - const int j = kZigzag[n]; - const int Q = mtx->q_[j]; - const int iQ = mtx->iq_[j]; - const int B = BIAS(0x00); // neutral bias + const int j = kZigzag[n]; + const uint32_t Q = mtx->q_[j]; + const uint32_t iQ = mtx->iq_[j]; + const uint32_t B = BIAS(0x00); // neutral bias // note: it's important to take sign of the _original_ coeff, // so we don't have to consider level < 0 afterward. const int sign = (in[j] < 0); - int coeff0 = (sign ? -in[j] : in[j]) + mtx->sharpen_[j]; - int level0; - if (coeff0 > 2047) coeff0 = 2047; + const uint32_t coeff0 = (sign ? -in[j] : in[j]) + mtx->sharpen_[j]; + int level0 = QUANTDIV(coeff0, iQ, B); + int thresh_level = QUANTDIV(coeff0, iQ, BIAS(0x80)); + if (thresh_level > MAX_LEVEL) thresh_level = MAX_LEVEL; + if (level0 > MAX_LEVEL) level0 = MAX_LEVEL; + + { // Swap current and previous score states + ScoreState* const tmp = ss_cur; + ss_cur = ss_prev; + ss_prev = tmp; + } - level0 = QUANTDIV(coeff0, iQ, B); // test all alternate level values around level0. for (m = -MIN_DELTA; m <= MAX_DELTA; ++m) { Node* const cur = &NODE(n, m); - int delta_error, new_error; - score_t cur_score = MAX_COST; int level = level0 + m; - int last_proba; + const int ctx = (level > 2) ? 2 : level; + const int band = VP8EncBands[n + 1]; + score_t base_score; + score_t best_cur_score = MAX_COST; + int best_prev = 0; // default, in case - cur->sign = sign; - cur->level = level; - cur->ctx = (level == 0) ? 0 : (level == 1) ? 1 : 2; - if (level >= 2048 || level < 0) { // node is dead? - cur->cost = MAX_COST; + ss_cur[m].score = MAX_COST; + ss_cur[m].costs = costs[n + 1][ctx]; + if (level < 0 || level > thresh_level) { + // Node is dead. continue; } - last_proba = last_costs[VP8EncBands[n + 1]][cur->ctx][0]; - // Compute delta_error = how much coding this level will - // subtract as distortion to max_error - new_error = coeff0 - level * Q; - delta_error = - kWeightTrellis[j] * (coeff0 * coeff0 - new_error * new_error); + { + // Compute delta_error = how much coding this level will + // subtract to max_error as distortion. + // Here, distortion = sum of (|coeff_i| - level_i * Q_i)^2 + const int new_error = coeff0 - level * Q; + const int delta_error = + kWeightTrellis[j] * (new_error * new_error - coeff0 * coeff0); + base_score = RDScoreTrellis(lambda, 0, delta_error); + } // Inspect all possible non-dead predecessors. Retain only the best one. for (p = -MIN_DELTA; p <= MAX_DELTA; ++p) { - const Node* const prev = &NODE(n - 1, p); - const int prev_ctx = prev->ctx; - const uint16_t* const tcost = costs[VP8EncBands[n]][prev_ctx]; - const score_t total_error = prev->error - delta_error; - score_t cost, base_cost, score; - - if (prev->cost >= MAX_COST) { // dead node? - continue; - } - - // Base cost of both terminal/non-terminal - base_cost = prev->cost + VP8LevelCost(tcost, level); - + // Dead nodes (with ss_prev[p].score >= MAX_COST) are automatically + // eliminated since their score can't be better than the current best. + const score_t cost = VP8LevelCost(ss_prev[p].costs, level); // Examine node assuming it's a non-terminal one. - cost = base_cost; - if (level && n < 15) { - cost += VP8BitCost(1, last_proba); - } - score = RDScoreTrellis(lambda, cost, total_error); - if (score < cur_score) { - cur_score = score; - cur->cost = cost; - cur->error = total_error; - cur->prev = p; + const score_t score = + base_score + ss_prev[p].score + RDScoreTrellis(lambda, cost, 0); + if (score < best_cur_score) { + best_cur_score = score; + best_prev = p; } + } + // Store best finding in current node. + cur->sign = sign; + cur->level = level; + cur->prev = best_prev; + ss_cur[m].score = best_cur_score; - // Now, record best terminal node (and thus best entry in the graph). - if (level) { - cost = base_cost; - if (n < 15) cost += VP8BitCost(0, last_proba); - score = RDScoreTrellis(lambda, cost, total_error); - if (score < best_score) { - best_score = score; - best_path[0] = n; // best eob position - best_path[1] = m; // best level - best_path[2] = p; // best predecessor - } + // Now, record best terminal node (and thus best entry in the graph). + if (level != 0) { + const score_t last_pos_cost = + (n < 15) ? VP8BitCost(0, probas[band][ctx][0]) : 0; + const score_t last_pos_score = RDScoreTrellis(lambda, last_pos_cost, 0); + const score_t score = best_cur_score + last_pos_score; + if (score < best_score) { + best_score = score; + best_path[0] = n; // best eob position + best_path[1] = m; // best node index + best_path[2] = best_prev; // best predecessor } } } @@ -618,23 +722,25 @@ static int TrellisQuantizeBlock(const VP8EncIterator* const it, return 0; // skip! } - // Unwind the best path. - // Note: best-prev on terminal node is not necessarily equal to the - // best_prev for non-terminal. So we patch best_path[2] in. - n = best_path[0]; - best_node = best_path[1]; - NODE(n, best_node).prev = best_path[2]; // force best-prev for terminal - nz = 0; + { + // Unwind the best path. + // Note: best-prev on terminal node is not necessarily equal to the + // best_prev for non-terminal. So we patch best_path[2] in. + int nz = 0; + int best_node = best_path[1]; + n = best_path[0]; + NODE(n, best_node).prev = best_path[2]; // force best-prev for terminal - for (; n >= first; --n) { - const Node* const node = &NODE(n, best_node); - const int j = kZigzag[n]; - out[n] = node->sign ? -node->level : node->level; - nz |= (node->level != 0); - in[j] = out[n] * mtx->q_[j]; - best_node = node->prev; + for (; n >= first; --n) { + const Node* const node = &NODE(n, best_node); + const int j = kZigzag[n]; + out[n] = node->sign ? -node->level : node->level; + nz |= node->level; + in[j] = out[n] * mtx->q_[j]; + best_node = node->prev; + } + return (nz != 0); } - return nz; } #undef NODE @@ -650,17 +756,17 @@ static int ReconstructIntra16(VP8EncIterator* const it, int mode) { const VP8Encoder* const enc = it->enc_; const uint8_t* const ref = it->yuv_p_ + VP8I16ModeOffsets[mode]; - const uint8_t* const src = it->yuv_in_ + Y_OFF; + const uint8_t* const src = it->yuv_in_ + Y_OFF_ENC; const VP8SegmentInfo* const dqm = &enc->dqm_[it->mb_->segment_]; int nz = 0; int n; int16_t tmp[16][16], dc_tmp[16]; - for (n = 0; n < 16; ++n) { - VP8FTransform(src + VP8Scan[n], ref + VP8Scan[n], tmp[n]); + for (n = 0; n < 16; n += 2) { + VP8FTransform2(src + VP8Scan[n], ref + VP8Scan[n], tmp[n]); } VP8FTransformWHT(tmp[0], dc_tmp); - nz |= VP8EncQuantizeBlock(dc_tmp, rd->y_dc_levels, 0, &dqm->y2_) << 24; + nz |= VP8EncQuantizeBlockWHT(dc_tmp, rd->y_dc_levels, &dqm->y2_) << 24; if (DO_TRELLIS_I16 && it->do_trellis_) { int x, y; @@ -669,20 +775,26 @@ static int ReconstructIntra16(VP8EncIterator* const it, for (x = 0; x < 4; ++x, ++n) { const int ctx = it->top_nz_[x] + it->left_nz_[y]; const int non_zero = - TrellisQuantizeBlock(it, tmp[n], rd->y_ac_levels[n], ctx, 0, - &dqm->y1_, dqm->lambda_trellis_i16_); + TrellisQuantizeBlock(enc, tmp[n], rd->y_ac_levels[n], ctx, 0, + &dqm->y1_, dqm->lambda_trellis_i16_); it->top_nz_[x] = it->left_nz_[y] = non_zero; + rd->y_ac_levels[n][0] = 0; nz |= non_zero << n; } } } else { - for (n = 0; n < 16; ++n) { - nz |= VP8EncQuantizeBlock(tmp[n], rd->y_ac_levels[n], 1, &dqm->y1_) << n; + for (n = 0; n < 16; n += 2) { + // Zero-out the first coeff, so that: a) nz is correct below, and + // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. + tmp[n][0] = tmp[n + 1][0] = 0; + nz |= VP8EncQuantize2Blocks(tmp[n], rd->y_ac_levels[n], &dqm->y1_) << n; + assert(rd->y_ac_levels[n + 0][0] == 0); + assert(rd->y_ac_levels[n + 1][0] == 0); } } // Transform back - VP8ITransformWHT(dc_tmp, tmp[0]); + VP8TransformWHT(dc_tmp, tmp[0]); for (n = 0; n < 16; n += 2) { VP8ITransform(ref + VP8Scan[n], tmp[n], yuv_out + VP8Scan[n], 1); } @@ -705,10 +817,10 @@ static int ReconstructIntra4(VP8EncIterator* const it, if (DO_TRELLIS_I4 && it->do_trellis_) { const int x = it->i4_ & 3, y = it->i4_ >> 2; const int ctx = it->top_nz_[x] + it->left_nz_[y]; - nz = TrellisQuantizeBlock(it, tmp, levels, ctx, 3, &dqm->y1_, + nz = TrellisQuantizeBlock(enc, tmp, levels, ctx, 3, &dqm->y1_, dqm->lambda_trellis_i4_); } else { - nz = VP8EncQuantizeBlock(tmp, levels, 0, &dqm->y1_); + nz = VP8EncQuantizeBlock(tmp, levels, &dqm->y1_); } VP8ITransform(ref, tmp, yuv_out, 0); return nz; @@ -718,14 +830,14 @@ static int ReconstructUV(VP8EncIterator* const it, VP8ModeScore* const rd, uint8_t* const yuv_out, int mode) { const VP8Encoder* const enc = it->enc_; const uint8_t* const ref = it->yuv_p_ + VP8UVModeOffsets[mode]; - const uint8_t* const src = it->yuv_in_ + U_OFF; + const uint8_t* const src = it->yuv_in_ + U_OFF_ENC; const VP8SegmentInfo* const dqm = &enc->dqm_[it->mb_->segment_]; int nz = 0; int n; int16_t tmp[8][16]; - for (n = 0; n < 8; ++n) { - VP8FTransform(src + VP8Scan[16 + n], ref + VP8Scan[16 + n], tmp[n]); + for (n = 0; n < 8; n += 2) { + VP8FTransform2(src + VP8ScanUV[n], ref + VP8ScanUV[n], tmp[n]); } if (DO_TRELLIS_UV && it->do_trellis_) { int ch, x, y; @@ -734,28 +846,45 @@ static int ReconstructUV(VP8EncIterator* const it, VP8ModeScore* const rd, for (x = 0; x < 2; ++x, ++n) { const int ctx = it->top_nz_[4 + ch + x] + it->left_nz_[4 + ch + y]; const int non_zero = - TrellisQuantizeBlock(it, tmp[n], rd->uv_levels[n], ctx, 2, - &dqm->uv_, dqm->lambda_trellis_uv_); + TrellisQuantizeBlock(enc, tmp[n], rd->uv_levels[n], ctx, 2, + &dqm->uv_, dqm->lambda_trellis_uv_); it->top_nz_[4 + ch + x] = it->left_nz_[4 + ch + y] = non_zero; nz |= non_zero << n; } } } } else { - for (n = 0; n < 8; ++n) { - nz |= VP8EncQuantizeBlock(tmp[n], rd->uv_levels[n], 0, &dqm->uv_) << n; + for (n = 0; n < 8; n += 2) { + nz |= VP8EncQuantize2Blocks(tmp[n], rd->uv_levels[n], &dqm->uv_) << n; } } for (n = 0; n < 8; n += 2) { - VP8ITransform(ref + VP8Scan[16 + n], tmp[n], yuv_out + VP8Scan[16 + n], 1); + VP8ITransform(ref + VP8ScanUV[n], tmp[n], yuv_out + VP8ScanUV[n], 1); } return (nz << 16); } //------------------------------------------------------------------------------ // RD-opt decision. Reconstruct each modes, evalue distortion and bit-cost. -// Pick the mode is lower RD-cost = Rate + lamba * Distortion. +// Pick the mode is lower RD-cost = Rate + lambda * Distortion. + +static void StoreMaxDelta(VP8SegmentInfo* const dqm, const int16_t DCs[16]) { + // We look at the first three AC coefficients to determine what is the average + // delta between each sub-4x4 block. + const int v0 = abs(DCs[1]); + const int v1 = abs(DCs[2]); + const int v2 = abs(DCs[4]); + int max_v = (v1 > v0) ? v1 : v0; + max_v = (v2 > max_v) ? v2 : max_v; + if (max_v > dqm->max_edge_) dqm->max_edge_ = max_v; +} + +static void SwapModeScore(VP8ModeScore** a, VP8ModeScore** b) { + VP8ModeScore* const tmp = *a; + *a = *b; + *b = tmp; +} static void SwapPtr(uint8_t** a, uint8_t** b) { uint8_t* const tmp = *a; @@ -767,43 +896,69 @@ static void SwapOut(VP8EncIterator* const it) { SwapPtr(&it->yuv_out_, &it->yuv_out2_); } -static void PickBestIntra16(VP8EncIterator* const it, VP8ModeScore* const rd) { - const VP8Encoder* const enc = it->enc_; - const VP8SegmentInfo* const dqm = &enc->dqm_[it->mb_->segment_]; +static score_t IsFlat(const int16_t* levels, int num_blocks, score_t thresh) { + score_t score = 0; + while (num_blocks-- > 0) { // TODO(skal): refine positional scoring? + int i; + for (i = 1; i < 16; ++i) { // omit DC, we're only interested in AC + score += (levels[i] != 0); + if (score > thresh) return 0; + } + levels += 16; + } + return 1; +} + +static void PickBestIntra16(VP8EncIterator* const it, VP8ModeScore* rd) { + const int kNumBlocks = 16; + VP8SegmentInfo* const dqm = &it->enc_->dqm_[it->mb_->segment_]; const int lambda = dqm->lambda_i16_; const int tlambda = dqm->tlambda_; - const uint8_t* const src = it->yuv_in_ + Y_OFF; - VP8ModeScore rd16; + const uint8_t* const src = it->yuv_in_ + Y_OFF_ENC; + VP8ModeScore rd_tmp; + VP8ModeScore* rd_cur = &rd_tmp; + VP8ModeScore* rd_best = rd; int mode; rd->mode_i16 = -1; for (mode = 0; mode < NUM_PRED_MODES; ++mode) { - uint8_t* const tmp_dst = it->yuv_out2_ + Y_OFF; // scratch buffer - int nz; + uint8_t* const tmp_dst = it->yuv_out2_ + Y_OFF_ENC; // scratch buffer + rd_cur->mode_i16 = mode; // Reconstruct - nz = ReconstructIntra16(it, &rd16, tmp_dst, mode); + rd_cur->nz = ReconstructIntra16(it, rd_cur, tmp_dst, mode); // Measure RD-score - rd16.D = VP8SSE16x16(src, tmp_dst); - rd16.SD = tlambda ? MULT_8B(tlambda, VP8TDisto16x16(src, tmp_dst, kWeightY)) - : 0; - rd16.R = VP8GetCostLuma16(it, &rd16); - rd16.R += VP8FixedCostsI16[mode]; + rd_cur->D = VP8SSE16x16(src, tmp_dst); + rd_cur->SD = + tlambda ? MULT_8B(tlambda, VP8TDisto16x16(src, tmp_dst, kWeightY)) : 0; + rd_cur->H = VP8FixedCostsI16[mode]; + rd_cur->R = VP8GetCostLuma16(it, rd_cur); + if (mode > 0 && + IsFlat(rd_cur->y_ac_levels[0], kNumBlocks, FLATNESS_LIMIT_I16)) { + // penalty to avoid flat area to be mispredicted by complex mode + rd_cur->R += FLATNESS_PENALTY * kNumBlocks; + } // Since we always examine Intra16 first, we can overwrite *rd directly. - SetRDScore(lambda, &rd16); - if (mode == 0 || rd16.score < rd->score) { - CopyScore(rd, &rd16); - rd->mode_i16 = mode; - rd->nz = nz; - memcpy(rd->y_ac_levels, rd16.y_ac_levels, sizeof(rd16.y_ac_levels)); - memcpy(rd->y_dc_levels, rd16.y_dc_levels, sizeof(rd16.y_dc_levels)); + SetRDScore(lambda, rd_cur); + if (mode == 0 || rd_cur->score < rd_best->score) { + SwapModeScore(&rd_cur, &rd_best); SwapOut(it); } } + if (rd_best != rd) { + memcpy(rd, rd_best, sizeof(*rd)); + } SetRDScore(dqm->lambda_mode_, rd); // finalize score for mode decision. VP8SetIntra16Mode(it, rd->mode_i16); + + // we have a blocky macroblock (only DCs are non-zero) with fairly high + // distortion, record max delta so we can later adjust the minimal filtering + // strength needed to smooth these blocks out. + if ((rd->nz & 0x100ffff) == 0x1000000 && rd->D > dqm->min_disto_) { + StoreMaxDelta(dqm, rd->y_dc_levels); + } } //------------------------------------------------------------------------------ @@ -823,8 +978,8 @@ static int PickBestIntra4(VP8EncIterator* const it, VP8ModeScore* const rd) { const VP8SegmentInfo* const dqm = &enc->dqm_[it->mb_->segment_]; const int lambda = dqm->lambda_i4_; const int tlambda = dqm->tlambda_; - const uint8_t* const src0 = it->yuv_in_ + Y_OFF; - uint8_t* const best_blocks = it->yuv_out2_ + Y_OFF; + const uint8_t* const src0 = it->yuv_in_ + Y_OFF_ENC; + uint8_t* const best_blocks = it->yuv_out2_ + Y_OFF_ENC; int total_header_bits = 0; VP8ModeScore rd_best; @@ -833,9 +988,11 @@ static int PickBestIntra4(VP8EncIterator* const it, VP8ModeScore* const rd) { } InitScore(&rd_best); - rd_best.score = 211; // '211' is the value of VP8BitCost(0, 145) + rd_best.H = 211; // '211' is the value of VP8BitCost(0, 145) + SetRDScore(dqm->lambda_mode_, &rd_best); VP8IteratorStartI4(it); do { + const int kNumBlocks = 1; VP8ModeScore rd_i4; int mode; int best_mode = -1; @@ -859,27 +1016,44 @@ static int PickBestIntra4(VP8EncIterator* const it, VP8ModeScore* const rd) { rd_tmp.SD = tlambda ? MULT_8B(tlambda, VP8TDisto4x4(src, tmp_dst, kWeightY)) : 0; - rd_tmp.R = VP8GetCostLuma4(it, tmp_levels); - rd_tmp.R += mode_costs[mode]; + rd_tmp.H = mode_costs[mode]; + // Add flatness penalty + if (mode > 0 && IsFlat(tmp_levels, kNumBlocks, FLATNESS_LIMIT_I4)) { + rd_tmp.R = FLATNESS_PENALTY * kNumBlocks; + } else { + rd_tmp.R = 0; + } + + // early-out check SetRDScore(lambda, &rd_tmp); + if (best_mode >= 0 && rd_tmp.score >= rd_i4.score) continue; + + // finish computing score + rd_tmp.R += VP8GetCostLuma4(it, tmp_levels); + SetRDScore(lambda, &rd_tmp); + if (best_mode < 0 || rd_tmp.score < rd_i4.score) { CopyScore(&rd_i4, &rd_tmp); best_mode = mode; SwapPtr(&tmp_dst, &best_block); - memcpy(rd_best.y_ac_levels[it->i4_], tmp_levels, sizeof(tmp_levels)); + memcpy(rd_best.y_ac_levels[it->i4_], tmp_levels, + sizeof(rd_best.y_ac_levels[it->i4_])); } } SetRDScore(dqm->lambda_mode_, &rd_i4); AddScore(&rd_best, &rd_i4); - total_header_bits += mode_costs[best_mode]; - if (rd_best.score >= rd->score || - total_header_bits > enc->max_i4_header_bits_) { + if (rd_best.score >= rd->score) { + return 0; + } + total_header_bits += (int)rd_i4.H; // <- equal to mode_costs[best_mode]; + if (total_header_bits > enc->max_i4_header_bits_) { return 0; } // Copy selected samples if not in the right place already. - if (best_block != best_blocks + VP8Scan[it->i4_]) + if (best_block != best_blocks + VP8Scan[it->i4_]) { VP8Copy4x4(best_block, best_blocks + VP8Scan[it->i4_]); + } rd->modes_i4[it->i4_] = best_mode; it->top_nz_[it->i4_ & 3] = it->left_nz_[it->i4_ >> 2] = (rd_i4.nz ? 1 : 0); } while (VP8IteratorRotateI4(it, best_blocks)); @@ -895,12 +1069,13 @@ static int PickBestIntra4(VP8EncIterator* const it, VP8ModeScore* const rd) { //------------------------------------------------------------------------------ static void PickBestUV(VP8EncIterator* const it, VP8ModeScore* const rd) { - const VP8Encoder* const enc = it->enc_; - const VP8SegmentInfo* const dqm = &enc->dqm_[it->mb_->segment_]; + const int kNumBlocks = 8; + const VP8SegmentInfo* const dqm = &it->enc_->dqm_[it->mb_->segment_]; const int lambda = dqm->lambda_uv_; - const uint8_t* const src = it->yuv_in_ + U_OFF; - uint8_t* const tmp_dst = it->yuv_out2_ + U_OFF; // scratch buffer - uint8_t* const dst0 = it->yuv_out_ + U_OFF; + const uint8_t* const src = it->yuv_in_ + U_OFF_ENC; + uint8_t* tmp_dst = it->yuv_out2_ + U_OFF_ENC; // scratch buffer + uint8_t* dst0 = it->yuv_out_ + U_OFF_ENC; + uint8_t* dst = dst0; VP8ModeScore rd_best; int mode; @@ -914,20 +1089,26 @@ static void PickBestUV(VP8EncIterator* const it, VP8ModeScore* const rd) { // Compute RD-score rd_uv.D = VP8SSE16x8(src, tmp_dst); - rd_uv.SD = 0; // TODO: should we call TDisto? it tends to flatten areas. + rd_uv.SD = 0; // not calling TDisto here: it tends to flatten areas. + rd_uv.H = VP8FixedCostsUV[mode]; rd_uv.R = VP8GetCostUV(it, &rd_uv); - rd_uv.R += VP8FixedCostsUV[mode]; + if (mode > 0 && IsFlat(rd_uv.uv_levels[0], kNumBlocks, FLATNESS_LIMIT_UV)) { + rd_uv.R += FLATNESS_PENALTY * kNumBlocks; + } SetRDScore(lambda, &rd_uv); if (mode == 0 || rd_uv.score < rd_best.score) { CopyScore(&rd_best, &rd_uv); rd->mode_uv = mode; memcpy(rd->uv_levels, rd_uv.uv_levels, sizeof(rd->uv_levels)); - memcpy(dst0, tmp_dst, UV_SIZE); // TODO: SwapUVOut() ? + SwapPtr(&dst, &tmp_dst); } } VP8SetIntraUVMode(it, rd->mode_uv); AddScore(rd, &rd_best); + if (dst != dst0) { // copy 16x8 block if needed + VP8Copy16x8(dst, dst0); + } } //------------------------------------------------------------------------------ @@ -939,75 +1120,128 @@ static void SimpleQuantize(VP8EncIterator* const it, VP8ModeScore* const rd) { int nz = 0; if (is_i16) { - nz = ReconstructIntra16(it, rd, it->yuv_out_ + Y_OFF, it->preds_[0]); + nz = ReconstructIntra16(it, rd, it->yuv_out_ + Y_OFF_ENC, it->preds_[0]); } else { VP8IteratorStartI4(it); do { const int mode = it->preds_[(it->i4_ & 3) + (it->i4_ >> 2) * enc->preds_w_]; - const uint8_t* const src = it->yuv_in_ + Y_OFF + VP8Scan[it->i4_]; - uint8_t* const dst = it->yuv_out_ + Y_OFF + VP8Scan[it->i4_]; + const uint8_t* const src = it->yuv_in_ + Y_OFF_ENC + VP8Scan[it->i4_]; + uint8_t* const dst = it->yuv_out_ + Y_OFF_ENC + VP8Scan[it->i4_]; VP8MakeIntra4Preds(it); nz |= ReconstructIntra4(it, rd->y_ac_levels[it->i4_], src, dst, mode) << it->i4_; - } while (VP8IteratorRotateI4(it, it->yuv_out_ + Y_OFF)); + } while (VP8IteratorRotateI4(it, it->yuv_out_ + Y_OFF_ENC)); } - nz |= ReconstructUV(it, rd, it->yuv_out_ + U_OFF, it->mb_->uv_mode_); + nz |= ReconstructUV(it, rd, it->yuv_out_ + U_OFF_ENC, it->mb_->uv_mode_); rd->nz = nz; } // Refine intra16/intra4 sub-modes based on distortion only (not rate). -static void DistoRefine(VP8EncIterator* const it, int try_both_i4_i16) { - const int is_i16 = (it->mb_->type_ == 1); +static void RefineUsingDistortion(VP8EncIterator* const it, + int try_both_modes, int refine_uv_mode, + VP8ModeScore* const rd) { score_t best_score = MAX_COST; + int nz = 0; + int mode; + int is_i16 = try_both_modes || (it->mb_->type_ == 1); - if (try_both_i4_i16 || is_i16) { - int mode; + const VP8SegmentInfo* const dqm = &it->enc_->dqm_[it->mb_->segment_]; + // Some empiric constants, of approximate order of magnitude. + const int lambda_d_i16 = 106; + const int lambda_d_i4 = 11; + const int lambda_d_uv = 120; + score_t score_i4 = dqm->i4_penalty_; + score_t i4_bit_sum = 0; + const score_t bit_limit = try_both_modes ? it->enc_->mb_header_limit_ + : MAX_COST; // no early-out allowed + + if (is_i16) { // First, evaluate Intra16 distortion int best_mode = -1; + const uint8_t* const src = it->yuv_in_ + Y_OFF_ENC; for (mode = 0; mode < NUM_PRED_MODES; ++mode) { const uint8_t* const ref = it->yuv_p_ + VP8I16ModeOffsets[mode]; - const uint8_t* const src = it->yuv_in_ + Y_OFF; - const score_t score = VP8SSE16x16(src, ref); + const score_t score = VP8SSE16x16(src, ref) * RD_DISTO_MULT + + VP8FixedCostsI16[mode] * lambda_d_i16; + if (mode > 0 && VP8FixedCostsI16[mode] > bit_limit) { + continue; + } if (score < best_score) { best_mode = mode; best_score = score; } } VP8SetIntra16Mode(it, best_mode); + // we'll reconstruct later, if i16 mode actually gets selected } - if (try_both_i4_i16 || !is_i16) { - uint8_t modes_i4[16]; + + // Next, evaluate Intra4 + if (try_both_modes || !is_i16) { // We don't evaluate the rate here, but just account for it through a // constant penalty (i4 mode usually needs more bits compared to i16). - score_t score_i4 = (score_t)I4_PENALTY; - + is_i16 = 0; VP8IteratorStartI4(it); do { - int mode; - int best_sub_mode = -1; - score_t best_sub_score = MAX_COST; - const uint8_t* const src = it->yuv_in_ + Y_OFF + VP8Scan[it->i4_]; + int best_i4_mode = -1; + score_t best_i4_score = MAX_COST; + const uint8_t* const src = it->yuv_in_ + Y_OFF_ENC + VP8Scan[it->i4_]; + const uint16_t* const mode_costs = GetCostModeI4(it, rd->modes_i4); - // TODO(skal): we don't really need the prediction pixels here, - // but just the distortion against 'src'. VP8MakeIntra4Preds(it); for (mode = 0; mode < NUM_BMODES; ++mode) { const uint8_t* const ref = it->yuv_p_ + VP8I4ModeOffsets[mode]; - const score_t score = VP8SSE4x4(src, ref); - if (score < best_sub_score) { - best_sub_mode = mode; - best_sub_score = score; + const score_t score = VP8SSE4x4(src, ref) * RD_DISTO_MULT + + mode_costs[mode] * lambda_d_i4; + if (score < best_i4_score) { + best_i4_mode = mode; + best_i4_score = score; } } - modes_i4[it->i4_] = best_sub_mode; - score_i4 += best_sub_score; - if (score_i4 >= best_score) break; - } while (VP8IteratorRotateI4(it, it->yuv_in_ + Y_OFF)); - if (score_i4 < best_score) { - VP8SetIntra4Mode(it, modes_i4); - } + i4_bit_sum += mode_costs[best_i4_mode]; + rd->modes_i4[it->i4_] = best_i4_mode; + score_i4 += best_i4_score; + if (score_i4 >= best_score || i4_bit_sum > bit_limit) { + // Intra4 won't be better than Intra16. Bail out and pick Intra16. + is_i16 = 1; + break; + } else { // reconstruct partial block inside yuv_out2_ buffer + uint8_t* const tmp_dst = it->yuv_out2_ + Y_OFF_ENC + VP8Scan[it->i4_]; + nz |= ReconstructIntra4(it, rd->y_ac_levels[it->i4_], + src, tmp_dst, best_i4_mode) << it->i4_; + } + } while (VP8IteratorRotateI4(it, it->yuv_out2_ + Y_OFF_ENC)); } + + // Final reconstruction, depending on which mode is selected. + if (!is_i16) { + VP8SetIntra4Mode(it, rd->modes_i4); + SwapOut(it); + best_score = score_i4; + } else { + nz = ReconstructIntra16(it, rd, it->yuv_out_ + Y_OFF_ENC, it->preds_[0]); + } + + // ... and UV! + if (refine_uv_mode) { + int best_mode = -1; + score_t best_uv_score = MAX_COST; + const uint8_t* const src = it->yuv_in_ + U_OFF_ENC; + for (mode = 0; mode < NUM_PRED_MODES; ++mode) { + const uint8_t* const ref = it->yuv_p_ + VP8UVModeOffsets[mode]; + const score_t score = VP8SSE16x8(src, ref) * RD_DISTO_MULT + + VP8FixedCostsUV[mode] * lambda_d_uv; + if (score < best_uv_score) { + best_mode = mode; + best_uv_score = score; + } + } + VP8SetIntraUVMode(it, best_mode); + } + nz |= ReconstructUV(it, rd, it->yuv_out_ + U_OFF_ENC, it->mb_->uv_mode_); + + rd->nz = nz; + rd->score = best_score; } //------------------------------------------------------------------------------ @@ -1037,16 +1271,13 @@ int VP8Decimate(VP8EncIterator* const it, VP8ModeScore* const rd, SimpleQuantize(it, rd); } } else { - // For method == 2, pick the best intra4/intra16 based on SSE (~tad slower). - // For method <= 1, we refine intra4 or intra16 (but don't re-examine mode). - DistoRefine(it, (method >= 2)); - SimpleQuantize(it, rd); + // At this point we have heuristically decided intra16 / intra4. + // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). + // For method <= 1, we don't re-examine the decision but just go ahead with + // quantization/reconstruction. + RefineUsingDistortion(it, (method >= 2), (method >= 1), rd); } is_skipped = (rd->nz == 0); VP8SetSkip(it, is_skipped); return is_skipped; } - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/syntax.c b/3rdparty/libwebp/enc/syntax_enc.c similarity index 80% rename from 3rdparty/libwebp/enc/syntax.c rename to 3rdparty/libwebp/enc/syntax_enc.c index b0f7676b48..90665bd7e5 100644 --- a/3rdparty/libwebp/enc/syntax.c +++ b/3rdparty/libwebp/enc/syntax_enc.c @@ -16,11 +16,7 @@ #include "../utils/utils.h" #include "../webp/format_constants.h" // RIFF constants #include "../webp/mux_types.h" // ALPHA_FLAG -#include "./vp8enci.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./vp8i_enc.h" //------------------------------------------------------------------------------ // Helper functions @@ -190,8 +186,8 @@ static int PutWebPHeaders(const VP8Encoder* const enc, size_t size0, // Segmentation header static void PutSegmentHeader(VP8BitWriter* const bw, const VP8Encoder* const enc) { - const VP8SegmentHeader* const hdr = &enc->segment_hdr_; - const VP8Proba* const proba = &enc->proba_; + const VP8EncSegmentHeader* const hdr = &enc->segment_hdr_; + const VP8EncProba* const proba = &enc->proba_; if (VP8PutBitUniform(bw, (hdr->num_segments_ > 1))) { // We always 'update' the quant and filter strength values const int update_data = 1; @@ -201,16 +197,16 @@ static void PutSegmentHeader(VP8BitWriter* const bw, // we always use absolute values, not relative ones VP8PutBitUniform(bw, 1); // (segment_feature_mode = 1. Paragraph 9.3.) for (s = 0; s < NUM_MB_SEGMENTS; ++s) { - VP8PutSignedValue(bw, enc->dqm_[s].quant_, 7); + VP8PutSignedBits(bw, enc->dqm_[s].quant_, 7); } for (s = 0; s < NUM_MB_SEGMENTS; ++s) { - VP8PutSignedValue(bw, enc->dqm_[s].fstrength_, 6); + VP8PutSignedBits(bw, enc->dqm_[s].fstrength_, 6); } } if (hdr->update_map_) { for (s = 0; s < 3; ++s) { if (VP8PutBitUniform(bw, (proba->segments_[s] != 255u))) { - VP8PutValue(bw, proba->segments_[s], 8); + VP8PutBits(bw, proba->segments_[s], 8); } } } @@ -219,20 +215,20 @@ static void PutSegmentHeader(VP8BitWriter* const bw, // Filtering parameters header static void PutFilterHeader(VP8BitWriter* const bw, - const VP8FilterHeader* const hdr) { + const VP8EncFilterHeader* const hdr) { const int use_lf_delta = (hdr->i4x4_lf_delta_ != 0); VP8PutBitUniform(bw, hdr->simple_); - VP8PutValue(bw, hdr->level_, 6); - VP8PutValue(bw, hdr->sharpness_, 3); + VP8PutBits(bw, hdr->level_, 6); + VP8PutBits(bw, hdr->sharpness_, 3); if (VP8PutBitUniform(bw, use_lf_delta)) { // '0' is the default value for i4x4_lf_delta_ at frame #0. const int need_update = (hdr->i4x4_lf_delta_ != 0); if (VP8PutBitUniform(bw, need_update)) { // we don't use ref_lf_delta => emit four 0 bits - VP8PutValue(bw, 0, 4); + VP8PutBits(bw, 0, 4); // we use mode_lf_delta for i4x4 - VP8PutSignedValue(bw, hdr->i4x4_lf_delta_, 6); - VP8PutValue(bw, 0, 3); // all others unused + VP8PutSignedBits(bw, hdr->i4x4_lf_delta_, 6); + VP8PutBits(bw, 0, 3); // all others unused } } } @@ -240,12 +236,12 @@ static void PutFilterHeader(VP8BitWriter* const bw, // Nominal quantization parameters static void PutQuant(VP8BitWriter* const bw, const VP8Encoder* const enc) { - VP8PutValue(bw, enc->base_quant_, 7); - VP8PutSignedValue(bw, enc->dq_y1_dc_, 4); - VP8PutSignedValue(bw, enc->dq_y2_dc_, 4); - VP8PutSignedValue(bw, enc->dq_y2_ac_, 4); - VP8PutSignedValue(bw, enc->dq_uv_dc_, 4); - VP8PutSignedValue(bw, enc->dq_uv_ac_, 4); + VP8PutBits(bw, enc->base_quant_, 7); + VP8PutSignedBits(bw, enc->dq_y1_dc_, 4); + VP8PutSignedBits(bw, enc->dq_y2_dc_, 4); + VP8PutSignedBits(bw, enc->dq_y2_ac_, 4); + VP8PutSignedBits(bw, enc->dq_uv_dc_, 4); + VP8PutSignedBits(bw, enc->dq_uv_ac_, 4); } // Partition sizes @@ -267,60 +263,23 @@ static int EmitPartitionsSize(const VP8Encoder* const enc, //------------------------------------------------------------------------------ -#ifdef WEBP_EXPERIMENTAL_FEATURES - -#define KTRAILER_SIZE 8 - -static int WriteExtensions(VP8Encoder* const enc) { - uint8_t buffer[KTRAILER_SIZE]; - VP8BitWriter* const bw = &enc->bw_; - WebPPicture* const pic = enc->pic_; - - // Layer (bytes 0..3) - PutLE24(buffer + 0, enc->layer_data_size_); - buffer[3] = enc->pic_->colorspace & WEBP_CSP_UV_MASK; - if (enc->layer_data_size_ > 0) { - assert(enc->use_layer_); - // append layer data to last partition - if (!VP8BitWriterAppend(&enc->parts_[enc->num_parts_ - 1], - enc->layer_data_, enc->layer_data_size_)) { - return WebPEncodingSetError(pic, VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY); - } - } - - buffer[KTRAILER_SIZE - 1] = 0x01; // marker - if (!VP8BitWriterAppend(bw, buffer, KTRAILER_SIZE)) { - return WebPEncodingSetError(pic, VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY); - } - return 1; -} - -#endif /* WEBP_EXPERIMENTAL_FEATURES */ - -//------------------------------------------------------------------------------ - -static size_t GeneratePartition0(VP8Encoder* const enc) { +static int GeneratePartition0(VP8Encoder* const enc) { VP8BitWriter* const bw = &enc->bw_; const int mb_size = enc->mb_w_ * enc->mb_h_; uint64_t pos1, pos2, pos3; -#ifdef WEBP_EXPERIMENTAL_FEATURES - const int need_extensions = enc->use_layer_; -#endif pos1 = VP8BitWriterPos(bw); - VP8BitWriterInit(bw, mb_size * 7 / 8); // ~7 bits per macroblock -#ifdef WEBP_EXPERIMENTAL_FEATURES - VP8PutBitUniform(bw, need_extensions); // extensions -#else + if (!VP8BitWriterInit(bw, mb_size * 7 / 8)) { // ~7 bits per macroblock + return WebPEncodingSetError(enc->pic_, VP8_ENC_ERROR_OUT_OF_MEMORY); + } VP8PutBitUniform(bw, 0); // colorspace -#endif VP8PutBitUniform(bw, 0); // clamp type PutSegmentHeader(bw, enc); PutFilterHeader(bw, &enc->filter_hdr_); - VP8PutValue(bw, enc->num_parts_ == 8 ? 3 : - enc->num_parts_ == 4 ? 2 : - enc->num_parts_ == 2 ? 1 : 0, 2); + VP8PutBits(bw, enc->num_parts_ == 8 ? 3 : + enc->num_parts_ == 4 ? 2 : + enc->num_parts_ == 2 ? 1 : 0, 2); PutQuant(bw, enc); VP8PutBitUniform(bw, 0); // no proba update VP8WriteProbas(bw, &enc->proba_); @@ -328,21 +287,17 @@ static size_t GeneratePartition0(VP8Encoder* const enc) { VP8CodeIntraModes(enc); VP8BitWriterFinish(bw); -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (need_extensions && !WriteExtensions(enc)) { - return 0; - } -#endif - pos3 = VP8BitWriterPos(bw); if (enc->pic_->stats) { enc->pic_->stats->header_bytes[0] = (int)((pos2 - pos1 + 7) >> 3); enc->pic_->stats->header_bytes[1] = (int)((pos3 - pos2 + 7) >> 3); enc->pic_->stats->alpha_data_size = (int)enc->alpha_data_size_; - enc->pic_->stats->layer_data_size = (int)enc->layer_data_size_; } - return !bw->error_; + if (bw->error_) { + return WebPEncodingSetError(enc->pic_, VP8_ENC_ERROR_OUT_OF_MEMORY); + } + return 1; } void VP8EncFreeBitWriters(VP8Encoder* const enc) { @@ -364,7 +319,8 @@ int VP8EncWrite(VP8Encoder* const enc) { int p; // Partition #0 with header and partition sizes - ok = !!GeneratePartition0(enc); + ok = GeneratePartition0(enc); + if (!ok) return 0; // Compute VP8 size vp8_size = VP8_FRAME_HEADER_SIZE + @@ -406,8 +362,7 @@ int VP8EncWrite(VP8Encoder* const enc) { for (p = 0; p < enc->num_parts_; ++p) { const uint8_t* const buf = VP8BitWriterBuf(enc->parts_ + p); const size_t size = VP8BitWriterSize(enc->parts_ + p); - if (size) - ok = ok && pic->writer(buf, size, pic); + if (size) ok = ok && pic->writer(buf, size, pic); VP8BitWriterWipeOut(enc->parts_ + p); // will free the internal buffer. ok = ok && WebPReportProgress(pic, enc->percent_ + percent_per_part, &enc->percent_); @@ -425,6 +380,3 @@ int VP8EncWrite(VP8Encoder* const enc) { //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/token.c b/3rdparty/libwebp/enc/token_enc.c similarity index 51% rename from 3rdparty/libwebp/enc/token.c rename to 3rdparty/libwebp/enc/token_enc.c index 6a63371f71..02a0d72cc6 100644 --- a/3rdparty/libwebp/enc/token.c +++ b/3rdparty/libwebp/enc/token_enc.c @@ -20,113 +20,128 @@ #include #include -#include "./vp8enci.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./cost_enc.h" +#include "./vp8i_enc.h" +#include "../utils/utils.h" #if !defined(DISABLE_TOKEN_BUFFER) // we use pages to reduce the number of memcpy() -#define MAX_NUM_TOKEN 8192 // max number of token per page +#define MIN_PAGE_SIZE 8192 // minimum number of token per page #define FIXED_PROBA_BIT (1u << 14) +typedef uint16_t token_t; // bit #15: bit value + // bit #14: flags for constant proba or idx + // bits #0..13: slot or constant proba struct VP8Tokens { - uint16_t tokens_[MAX_NUM_TOKEN]; // bit#15: bit - // bit #14: constant proba or idx - // bits 0..13: slot or constant proba - VP8Tokens* next_; + VP8Tokens* next_; // pointer to next page }; +// Token data is located in memory just after the next_ field. +// This macro is used to return their address and hide the trick. +#define TOKEN_DATA(p) ((const token_t*)&(p)[1]) //------------------------------------------------------------------------------ -void VP8TBufferInit(VP8TBuffer* const b) { +void VP8TBufferInit(VP8TBuffer* const b, int page_size) { b->tokens_ = NULL; b->pages_ = NULL; b->last_page_ = &b->pages_; b->left_ = 0; + b->page_size_ = (page_size < MIN_PAGE_SIZE) ? MIN_PAGE_SIZE : page_size; b->error_ = 0; } void VP8TBufferClear(VP8TBuffer* const b) { if (b != NULL) { - const VP8Tokens* p = b->pages_; + VP8Tokens* p = b->pages_; while (p != NULL) { - const VP8Tokens* const next = p->next_; - free((void*)p); + VP8Tokens* const next = p->next_; + WebPSafeFree(p); p = next; } - VP8TBufferInit(b); + VP8TBufferInit(b, b->page_size_); } } static int TBufferNewPage(VP8TBuffer* const b) { - VP8Tokens* const page = b->error_ ? NULL : (VP8Tokens*)malloc(sizeof(*page)); + VP8Tokens* page = NULL; + if (!b->error_) { + const size_t size = sizeof(*page) + b->page_size_ * sizeof(token_t); + page = (VP8Tokens*)WebPSafeMalloc(1ULL, size); + } if (page == NULL) { b->error_ = 1; return 0; } + page->next_ = NULL; + *b->last_page_ = page; b->last_page_ = &page->next_; - b->left_ = MAX_NUM_TOKEN; - b->tokens_ = page->tokens_; - page->next_ = NULL; + b->left_ = b->page_size_; + b->tokens_ = (token_t*)TOKEN_DATA(page); return 1; } //------------------------------------------------------------------------------ -#define TOKEN_ID(t, b, ctx, p) \ - ((p) + NUM_PROBAS * ((ctx) + NUM_CTX * ((b) + NUM_BANDS * (t)))) +#define TOKEN_ID(t, b, ctx) \ + (NUM_PROBAS * ((ctx) + NUM_CTX * ((b) + NUM_BANDS * (t)))) -static WEBP_INLINE int AddToken(VP8TBuffer* const b, - int bit, uint32_t proba_idx) { +static WEBP_INLINE uint32_t AddToken(VP8TBuffer* const b, uint32_t bit, + uint32_t proba_idx, + proba_t* const stats) { assert(proba_idx < FIXED_PROBA_BIT); - assert(bit == 0 || bit == 1); + assert(bit <= 1); if (b->left_ > 0 || TBufferNewPage(b)) { const int slot = --b->left_; b->tokens_[slot] = (bit << 15) | proba_idx; } + VP8RecordStats(bit, stats); return bit; } static WEBP_INLINE void AddConstantToken(VP8TBuffer* const b, - int bit, int proba) { + uint32_t bit, uint32_t proba) { assert(proba < 256); - assert(bit == 0 || bit == 1); + assert(bit <= 1); if (b->left_ > 0 || TBufferNewPage(b)) { const int slot = --b->left_; b->tokens_[slot] = (bit << 15) | FIXED_PROBA_BIT | proba; } } -int VP8RecordCoeffTokens(int ctx, int coeff_type, int first, int last, - const int16_t* const coeffs, +int VP8RecordCoeffTokens(int ctx, const struct VP8Residual* const res, VP8TBuffer* const tokens) { - int n = first; - uint32_t base_id = TOKEN_ID(coeff_type, n, ctx, 0); - if (!AddToken(tokens, last >= 0, base_id + 0)) { + const int16_t* const coeffs = res->coeffs; + const int coeff_type = res->coeff_type; + const int last = res->last; + int n = res->first; + uint32_t base_id = TOKEN_ID(coeff_type, n, ctx); + // should be stats[VP8EncBands[n]], but it's equivalent for n=0 or 1 + proba_t* s = res->stats[n][ctx]; + if (!AddToken(tokens, last >= 0, base_id + 0, s + 0)) { return 0; } while (n < 16) { const int c = coeffs[n++]; const int sign = c < 0; - int v = sign ? -c : c; - if (!AddToken(tokens, v != 0, base_id + 1)) { - ctx = 0; - base_id = TOKEN_ID(coeff_type, VP8EncBands[n], ctx, 0); + const uint32_t v = sign ? -c : c; + if (!AddToken(tokens, v != 0, base_id + 1, s + 1)) { + base_id = TOKEN_ID(coeff_type, VP8EncBands[n], 0); // ctx=0 + s = res->stats[VP8EncBands[n]][0]; continue; } - if (!AddToken(tokens, v > 1, base_id + 2)) { - ctx = 1; + if (!AddToken(tokens, v > 1, base_id + 2, s + 2)) { + base_id = TOKEN_ID(coeff_type, VP8EncBands[n], 1); // ctx=1 + s = res->stats[VP8EncBands[n]][1]; } else { - if (!AddToken(tokens, v > 4, base_id + 3)) { - if (AddToken(tokens, v != 2, base_id + 4)) - AddToken(tokens, v == 4, base_id + 5); - } else if (!AddToken(tokens, v > 10, base_id + 6)) { - if (!AddToken(tokens, v > 6, base_id + 7)) { + if (!AddToken(tokens, v > 4, base_id + 3, s + 3)) { + if (AddToken(tokens, v != 2, base_id + 4, s + 4)) { + AddToken(tokens, v == 4, base_id + 5, s + 5); + } + } else if (!AddToken(tokens, v > 10, base_id + 6, s + 6)) { + if (!AddToken(tokens, v > 6, base_id + 7, s + 7)) { AddConstantToken(tokens, v == 6, 159); } else { AddConstantToken(tokens, v >= 9, 165); @@ -135,41 +150,42 @@ int VP8RecordCoeffTokens(int ctx, int coeff_type, int first, int last, } else { int mask; const uint8_t* tab; - if (v < 3 + (8 << 1)) { // VP8Cat3 (3b) - AddToken(tokens, 0, base_id + 8); - AddToken(tokens, 0, base_id + 9); - v -= 3 + (8 << 0); + uint32_t residue = v - 3; + if (residue < (8 << 1)) { // VP8Cat3 (3b) + AddToken(tokens, 0, base_id + 8, s + 8); + AddToken(tokens, 0, base_id + 9, s + 9); + residue -= (8 << 0); mask = 1 << 2; tab = VP8Cat3; - } else if (v < 3 + (8 << 2)) { // VP8Cat4 (4b) - AddToken(tokens, 0, base_id + 8); - AddToken(tokens, 1, base_id + 9); - v -= 3 + (8 << 1); + } else if (residue < (8 << 2)) { // VP8Cat4 (4b) + AddToken(tokens, 0, base_id + 8, s + 8); + AddToken(tokens, 1, base_id + 9, s + 9); + residue -= (8 << 1); mask = 1 << 3; tab = VP8Cat4; - } else if (v < 3 + (8 << 3)) { // VP8Cat5 (5b) - AddToken(tokens, 1, base_id + 8); - AddToken(tokens, 0, base_id + 10); - v -= 3 + (8 << 2); + } else if (residue < (8 << 3)) { // VP8Cat5 (5b) + AddToken(tokens, 1, base_id + 8, s + 8); + AddToken(tokens, 0, base_id + 10, s + 9); + residue -= (8 << 2); mask = 1 << 4; tab = VP8Cat5; } else { // VP8Cat6 (11b) - AddToken(tokens, 1, base_id + 8); - AddToken(tokens, 1, base_id + 10); - v -= 3 + (8 << 3); + AddToken(tokens, 1, base_id + 8, s + 8); + AddToken(tokens, 1, base_id + 10, s + 9); + residue -= (8 << 3); mask = 1 << 10; tab = VP8Cat6; } while (mask) { - AddConstantToken(tokens, !!(v & mask), *tab++); + AddConstantToken(tokens, !!(residue & mask), *tab++); mask >>= 1; } } - ctx = 2; + base_id = TOKEN_ID(coeff_type, VP8EncBands[n], 2); // ctx=2 + s = res->stats[VP8EncBands[n]][2]; } AddConstantToken(tokens, sign, 128); - base_id = TOKEN_ID(coeff_type, VP8EncBands[n], ctx, 0); - if (n == 16 || !AddToken(tokens, n <= last, base_id + 0)) { + if (n == 16 || !AddToken(tokens, n <= last, base_id + 0, s + 0)) { return 1; // EOB } } @@ -198,8 +214,9 @@ void VP8TokenToStats(const VP8TBuffer* const b, proba_t* const stats) { while (p != NULL) { const int N = (p->next_ == NULL) ? b->left_ : 0; int n = MAX_NUM_TOKEN; + const token_t* const tokens = TOKEN_DATA(p); while (n-- > N) { - const uint16_t token = p->tokens_[n]; + const token_t token = tokens[n]; if (!(token & FIXED_PROBA_BIT)) { Record((token >> 15) & 1, stats + (token & 0x3fffu)); } @@ -216,14 +233,14 @@ void VP8TokenToStats(const VP8TBuffer* const b, proba_t* const stats) { int VP8EmitTokens(VP8TBuffer* const b, VP8BitWriter* const bw, const uint8_t* const probas, int final_pass) { const VP8Tokens* p = b->pages_; - (void)final_pass; - if (b->error_) return 0; + assert(!b->error_); while (p != NULL) { const VP8Tokens* const next = p->next_; const int N = (next == NULL) ? b->left_ : 0; - int n = MAX_NUM_TOKEN; + int n = b->page_size_; + const token_t* const tokens = TOKEN_DATA(p); while (n-- > N) { - const uint16_t token = p->tokens_[n]; + const token_t token = tokens[n]; const int bit = (token >> 15) & 1; if (token & FIXED_PROBA_BIT) { VP8PutBit(bw, bit, token & 0xffu); // constant proba @@ -231,13 +248,37 @@ int VP8EmitTokens(VP8TBuffer* const b, VP8BitWriter* const bw, VP8PutBit(bw, bit, probas[token & 0x3fffu]); } } - if (final_pass) free((void*)p); + if (final_pass) WebPSafeFree((void*)p); p = next; } if (final_pass) b->pages_ = NULL; return 1; } +// Size estimation +size_t VP8EstimateTokenSize(VP8TBuffer* const b, const uint8_t* const probas) { + size_t size = 0; + const VP8Tokens* p = b->pages_; + assert(!b->error_); + while (p != NULL) { + const VP8Tokens* const next = p->next_; + const int N = (next == NULL) ? b->left_ : 0; + int n = b->page_size_; + const token_t* const tokens = TOKEN_DATA(p); + while (n-- > N) { + const token_t token = tokens[n]; + const int bit = token & (1 << 15); + if (token & FIXED_PROBA_BIT) { + size += VP8BitCost(bit, token & 0xffu); + } else { + size += VP8BitCost(bit, probas[token & 0x3fffu]); + } + } + p = next; + } + return size; +} + //------------------------------------------------------------------------------ #else // DISABLE_TOKEN_BUFFER @@ -251,6 +292,3 @@ void VP8TBufferClear(VP8TBuffer* const b) { #endif // !DISABLE_TOKEN_BUFFER -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/tree.c b/3rdparty/libwebp/enc/tree_enc.c similarity index 97% rename from 3rdparty/libwebp/enc/tree.c rename to 3rdparty/libwebp/enc/tree_enc.c index ecd8fb910e..2c40fe7f3d 100644 --- a/3rdparty/libwebp/enc/tree.c +++ b/3rdparty/libwebp/enc/tree_enc.c @@ -7,15 +7,11 @@ // be found in the AUTHORS file in the root of the source tree. // ----------------------------------------------------------------------------- // -// Token probabilities +// Coding of token probabilities, intra modes and segments. // // Author: Skal (pascal.massimino@gmail.com) -#include "./vp8enci.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./vp8i_enc.h" //------------------------------------------------------------------------------ // Default probabilities @@ -23,7 +19,6 @@ extern "C" { // Paragraph 13.5 const uint8_t VP8CoeffsProba0[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = { - // genereated using vp8_default_coef_probs() in entropy.c:129 { { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } @@ -159,7 +154,7 @@ const uint8_t }; void VP8DefaultProbas(VP8Encoder* const enc) { - VP8Proba* const probas = &enc->proba_; + VP8EncProba* const probas = &enc->proba_; probas->use_skip_proba_ = 0; memset(probas->segments_, 255u, sizeof(probas->segments_)); memcpy(probas->coeffs_, VP8CoeffsProba0, sizeof(VP8CoeffsProba0)); @@ -320,7 +315,7 @@ void VP8CodeIntraModes(VP8Encoder* const enc) { VP8EncIterator it; VP8IteratorInit(enc, &it); do { - const VP8MBInfo* mb = it.mb_; + const VP8MBInfo* const mb = it.mb_; const uint8_t* preds = it.preds_; if (enc->segment_hdr_.update_map_) { PutSegment(bw, mb->segment_, enc->proba_.segments_); @@ -345,7 +340,7 @@ void VP8CodeIntraModes(VP8Encoder* const enc) { } } PutUVMode(bw, mb->uv_mode_); - } while (VP8IteratorNext(&it, 0)); + } while (VP8IteratorNext(&it)); } //------------------------------------------------------------------------------ @@ -487,7 +482,7 @@ const uint8_t } }; -void VP8WriteProbas(VP8BitWriter* const bw, const VP8Proba* const probas) { +void VP8WriteProbas(VP8BitWriter* const bw, const VP8EncProba* const probas) { int t, b, c, p; for (t = 0; t < NUM_TYPES; ++t) { for (b = 0; b < NUM_BANDS; ++b) { @@ -496,17 +491,14 @@ void VP8WriteProbas(VP8BitWriter* const bw, const VP8Proba* const probas) { const uint8_t p0 = probas->coeffs_[t][b][c][p]; const int update = (p0 != VP8CoeffsProba0[t][b][c][p]); if (VP8PutBit(bw, update, VP8CoeffsUpdateProba[t][b][c][p])) { - VP8PutValue(bw, p0, 8); + VP8PutBits(bw, p0, 8); } } } } } if (VP8PutBitUniform(bw, probas->use_skip_proba_)) { - VP8PutValue(bw, probas->skip_proba_, 8); + VP8PutBits(bw, probas->skip_proba_, 8); } } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/vp8enci.h b/3rdparty/libwebp/enc/vp8i_enc.h similarity index 64% rename from 3rdparty/libwebp/enc/vp8enci.h rename to 3rdparty/libwebp/enc/vp8i_enc.h index 61d56be554..93c95ecbfb 100644 --- a/3rdparty/libwebp/enc/vp8enci.h +++ b/3rdparty/libwebp/enc/vp8i_enc.h @@ -15,12 +15,14 @@ #define WEBP_ENC_VP8ENCI_H_ #include // for memcpy() -#include "../webp/encode.h" +#include "../dec/common_dec.h" #include "../dsp/dsp.h" -#include "../utils/bit_writer.h" -#include "../utils/thread.h" +#include "../utils/bit_writer_utils.h" +#include "../utils/thread_utils.h" +#include "../utils/utils.h" +#include "../webp/encode.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -29,35 +31,10 @@ extern "C" { // version numbers #define ENC_MAJ_VERSION 0 -#define ENC_MIN_VERSION 3 -#define ENC_REV_VERSION 1 +#define ENC_MIN_VERSION 6 +#define ENC_REV_VERSION 0 -// intra prediction modes -enum { B_DC_PRED = 0, // 4x4 modes - B_TM_PRED = 1, - B_VE_PRED = 2, - B_HE_PRED = 3, - B_RD_PRED = 4, - B_VR_PRED = 5, - B_LD_PRED = 6, - B_VL_PRED = 7, - B_HD_PRED = 8, - B_HU_PRED = 9, - NUM_BMODES = B_HU_PRED + 1 - B_DC_PRED, // = 10 - - // Luma16 or UV modes - DC_PRED = B_DC_PRED, V_PRED = B_VE_PRED, - H_PRED = B_HE_PRED, TM_PRED = B_TM_PRED, - NUM_PRED_MODES = 4 - }; - -enum { NUM_MB_SEGMENTS = 4, - MAX_NUM_PARTITIONS = 8, - NUM_TYPES = 4, // 0: i16-AC, 1: i16-DC, 2:chroma-AC, 3:i4-AC - NUM_BANDS = 8, - NUM_CTX = 3, - NUM_PROBAS = 11, - MAX_LF_LEVELS = 64, // Maximum loop filter level +enum { MAX_LF_LEVELS = 64, // Maximum loop filter level MAX_VARIABLE_LEVEL = 67, // last (inclusive) level with variable cost MAX_LEVEL = 2047 // max level (note: max codable is 2047 + 67) }; @@ -69,115 +46,77 @@ typedef enum { // Rate-distortion optimization levels RD_OPT_TRELLIS_ALL = 3 // trellis-quant for every scoring (much slower) } VP8RDLevel; -// YUV-cache parameters. Cache is 16-pixels wide. -// The original or reconstructed samples can be accessed using VP8Scan[] +// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline). +// The original or reconstructed samples can be accessed using VP8Scan[]. // The predicted blocks can be accessed using offsets to yuv_p_ and -// the arrays VP8*ModeOffsets[]; -// +----+ YUV Samples area. See VP8Scan[] for accessing the blocks. -// Y_OFF |YYYY| <- original samples (enc->yuv_in_) -// |YYYY| -// |YYYY| -// |YYYY| -// U_OFF |UUVV| V_OFF (=U_OFF + 8) -// |UUVV| -// +----+ -// Y_OFF |YYYY| <- compressed/decoded samples ('yuv_out_') -// |YYYY| There are two buffers like this ('yuv_out_'/'yuv_out2_') -// |YYYY| -// |YYYY| -// U_OFF |UUVV| V_OFF -// |UUVV| -// x2 (for yuv_out2_) -// +----+ Prediction area ('yuv_p_', size = PRED_SIZE) -// I16DC16 |YYYY| Intra16 predictions (16x16 block each) -// |YYYY| -// |YYYY| -// |YYYY| -// I16TM16 |YYYY| -// |YYYY| -// |YYYY| -// |YYYY| -// I16VE16 |YYYY| -// |YYYY| -// |YYYY| -// |YYYY| -// I16HE16 |YYYY| -// |YYYY| -// |YYYY| -// |YYYY| -// +----+ Chroma U/V predictions (16x8 block each) -// C8DC8 |UUVV| -// |UUVV| -// C8TM8 |UUVV| -// |UUVV| -// C8VE8 |UUVV| -// |UUVV| -// C8HE8 |UUVV| -// |UUVV| -// +----+ Intra 4x4 predictions (4x4 block each) -// |YYYY| I4DC4 I4TM4 I4VE4 I4HE4 -// |YYYY| I4RD4 I4VR4 I4LD4 I4VL4 -// |YY..| I4HD4 I4HU4 I4TMP -// +----+ -#define BPS 16 // this is the common stride -#define Y_SIZE (BPS * 16) -#define UV_SIZE (BPS * 8) -#define YUV_SIZE (Y_SIZE + UV_SIZE) -#define PRED_SIZE (6 * 16 * BPS + 12 * BPS) -#define Y_OFF (0) -#define U_OFF (Y_SIZE) -#define V_OFF (U_OFF + 8) -#define ALIGN_CST 15 -#define DO_ALIGN(PTR) ((uintptr_t)((PTR) + ALIGN_CST) & ~ALIGN_CST) +// the arrays VP8*ModeOffsets[]. +// * YUV Samples area (yuv_in_/yuv_out_/yuv_out2_) +// (see VP8Scan[] for accessing the blocks, along with +// Y_OFF_ENC/U_OFF_ENC/V_OFF_ENC): +// +----+----+ +// Y_OFF_ENC |YYYY|UUVV| +// U_OFF_ENC |YYYY|UUVV| +// V_OFF_ENC |YYYY|....| <- 25% wasted U/V area +// |YYYY|....| +// +----+----+ +// * Prediction area ('yuv_p_', size = PRED_SIZE_ENC) +// Intra16 predictions (16x16 block each, two per row): +// |I16DC16|I16TM16| +// |I16VE16|I16HE16| +// Chroma U/V predictions (16x8 block each, two per row): +// |C8DC8|C8TM8| +// |C8VE8|C8HE8| +// Intra 4x4 predictions (4x4 block each) +// |I4DC4 I4TM4 I4VE4 I4HE4|I4RD4 I4VR4 I4LD4 I4VL4| +// |I4HD4 I4HU4 I4TMP .....|.......................| <- ~31% wasted +#define YUV_SIZE_ENC (BPS * 16) +#define PRED_SIZE_ENC (32 * BPS + 16 * BPS + 8 * BPS) // I16+Chroma+I4 preds +#define Y_OFF_ENC (0) +#define U_OFF_ENC (16) +#define V_OFF_ENC (16 + 8) -extern const int VP8Scan[16 + 4 + 4]; // in quant.c -extern const int VP8UVModeOffsets[4]; // in analyze.c +extern const int VP8Scan[16]; // in quant.c +extern const int VP8UVModeOffsets[4]; // in analyze.c extern const int VP8I16ModeOffsets[4]; extern const int VP8I4ModeOffsets[NUM_BMODES]; // Layout of prediction blocks // intra 16x16 #define I16DC16 (0 * 16 * BPS) -#define I16TM16 (1 * 16 * BPS) -#define I16VE16 (2 * 16 * BPS) -#define I16HE16 (3 * 16 * BPS) +#define I16TM16 (I16DC16 + 16) +#define I16VE16 (1 * 16 * BPS) +#define I16HE16 (I16VE16 + 16) // chroma 8x8, two U/V blocks side by side (hence: 16x8 each) -#define C8DC8 (4 * 16 * BPS) -#define C8TM8 (4 * 16 * BPS + 8 * BPS) -#define C8VE8 (5 * 16 * BPS) -#define C8HE8 (5 * 16 * BPS + 8 * BPS) +#define C8DC8 (2 * 16 * BPS) +#define C8TM8 (C8DC8 + 1 * 16) +#define C8VE8 (2 * 16 * BPS + 8 * BPS) +#define C8HE8 (C8VE8 + 1 * 16) // intra 4x4 -#define I4DC4 (6 * 16 * BPS + 0) -#define I4TM4 (6 * 16 * BPS + 4) -#define I4VE4 (6 * 16 * BPS + 8) -#define I4HE4 (6 * 16 * BPS + 12) -#define I4RD4 (6 * 16 * BPS + 4 * BPS + 0) -#define I4VR4 (6 * 16 * BPS + 4 * BPS + 4) -#define I4LD4 (6 * 16 * BPS + 4 * BPS + 8) -#define I4VL4 (6 * 16 * BPS + 4 * BPS + 12) -#define I4HD4 (6 * 16 * BPS + 8 * BPS + 0) -#define I4HU4 (6 * 16 * BPS + 8 * BPS + 4) -#define I4TMP (6 * 16 * BPS + 8 * BPS + 8) +#define I4DC4 (3 * 16 * BPS + 0) +#define I4TM4 (I4DC4 + 4) +#define I4VE4 (I4DC4 + 8) +#define I4HE4 (I4DC4 + 12) +#define I4RD4 (I4DC4 + 16) +#define I4VR4 (I4DC4 + 20) +#define I4LD4 (I4DC4 + 24) +#define I4VL4 (I4DC4 + 28) +#define I4HD4 (3 * 16 * BPS + 4 * BPS) +#define I4HU4 (I4HD4 + 4) +#define I4TMP (I4HD4 + 8) typedef int64_t score_t; // type used for scores, rate, distortion +// Note that MAX_COST is not the maximum allowed by sizeof(score_t), +// in order to allow overflowing computations. #define MAX_COST ((score_t)0x7fffffffffffffLL) #define QFIX 17 #define BIAS(b) ((b) << (QFIX - 8)) // Fun fact: this is the _only_ line where we're actually being lossy and // discarding bits. -static WEBP_INLINE int QUANTDIV(int n, int iQ, int B) { - return (n * iQ + B) >> QFIX; +static WEBP_INLINE int QUANTDIV(uint32_t n, uint32_t iQ, uint32_t B) { + return (int)((n * iQ + B) >> QFIX); } -// size of histogram used by CollectHistogram. -#define MAX_COEFF_THRESH 31 -typedef struct VP8Histogram VP8Histogram; -struct VP8Histogram { - // TODO(skal): we only need to store the max_value and last_non_zero actually. - int distribution[MAX_COEFF_THRESH + 1]; -}; - // Uncomment the following to remove token-buffer code: // #define DISABLE_TOKEN_BUFFER @@ -188,6 +127,8 @@ typedef uint32_t proba_t; // 16b + 16b typedef uint8_t ProbaArray[NUM_CTX][NUM_PROBAS]; typedef proba_t StatsArray[NUM_CTX][NUM_PROBAS]; typedef uint16_t CostArray[NUM_CTX][MAX_VARIABLE_LEVEL + 1]; +typedef const uint16_t* (*CostArrayPtr)[NUM_CTX]; // for easy casting +typedef const uint16_t* CostArrayMap[16][NUM_CTX]; typedef double LFStats[NUM_MB_SEGMENTS][MAX_LF_LEVELS]; // filter stats typedef struct VP8Encoder VP8Encoder; @@ -198,19 +139,20 @@ typedef struct { int update_map_; // whether to update the segment map or not. // must be 0 if there's only 1 segment. int size_; // bit-cost for transmitting the segment map -} VP8SegmentHeader; +} VP8EncSegmentHeader; // Struct collecting all frame-persistent probabilities. typedef struct { uint8_t segments_[3]; // probabilities for segment tree uint8_t skip_proba_; // final probability of being skipped. - ProbaArray coeffs_[NUM_TYPES][NUM_BANDS]; // 924 bytes + ProbaArray coeffs_[NUM_TYPES][NUM_BANDS]; // 1056 bytes StatsArray stats_[NUM_TYPES][NUM_BANDS]; // 4224 bytes - CostArray level_cost_[NUM_TYPES][NUM_BANDS]; // 11.4k + CostArray level_cost_[NUM_TYPES][NUM_BANDS]; // 13056 bytes + CostArrayMap remapped_costs_[NUM_TYPES]; // 1536 bytes int dirty_; // if true, need to call VP8CalculateLevelCosts() int use_skip_proba_; // Note: we always use skip_proba for now. int nb_skip_; // number of skipped blocks -} VP8Proba; +} VP8EncProba; // Filter parameters. Not actually used in the code (we don't perform // the in-loop filtering), but filled from user's config @@ -219,7 +161,7 @@ typedef struct { int level_; // base filter level [0..63] int sharpness_; // [0..7] int i4x4_lf_delta_; // delta filter level for i4x4 relative to i16x16 -} VP8FilterHeader; +} VP8EncFilterHeader; //------------------------------------------------------------------------------ // Informations about the macroblocks. @@ -236,8 +178,8 @@ typedef struct { typedef struct VP8Matrix { uint16_t q_[16]; // quantizer steps uint16_t iq_[16]; // reciprocals, fixed point. - uint16_t bias_[16]; // rounding bias - uint16_t zthresh_[16]; // value under which a coefficient is zeroed + uint32_t bias_[16]; // rounding bias + uint32_t zthresh_[16]; // value below which a coefficient is zeroed uint16_t sharpen_[16]; // frequency boosters for slight sharpening } VP8Matrix; @@ -248,16 +190,22 @@ typedef struct { int beta_; // filter-susceptibility, range [0,255]. int quant_; // final segment quantizer. int fstrength_; // final in-loop filtering strength + int max_edge_; // max edge delta (for filtering strength) + int min_disto_; // minimum distortion required to trigger filtering record // reactivities int lambda_i16_, lambda_i4_, lambda_uv_; int lambda_mode_, lambda_trellis_, tlambda_; int lambda_trellis_i16_, lambda_trellis_i4_, lambda_trellis_uv_; + + // lambda values for distortion-based evaluation + score_t i4_penalty_; // penalty for using Intra4 } VP8SegmentInfo; -// Handy transcient struct to accumulate score and info during RD-optimization +// Handy transient struct to accumulate score and info during RD-optimization // and mode evaluation. typedef struct { - score_t D, SD, R, score; // Distortion, spectral distortion, rate, score. + score_t D, SD; // Distortion, spectral distortion + score_t H, R, score; // header bits, rate, score. int16_t y_dc_levels[16]; // Quantized levels for luma-DC, luma-AC, chroma. int16_t y_ac_levels[16][16]; int16_t uv_levels[4 + 4][16]; @@ -271,12 +219,10 @@ typedef struct { // right neighbouring data (samples, predictions, contexts, ...) typedef struct { int x_, y_; // current macroblock - int y_offset_, uv_offset_; // offset to the luma / chroma planes - int y_stride_, uv_stride_; // respective strides - uint8_t* yuv_in_; // borrowed from enc_ (for now) - uint8_t* yuv_out_; // '' - uint8_t* yuv_out2_; // '' - uint8_t* yuv_p_; // '' + uint8_t* yuv_in_; // input samples + uint8_t* yuv_out_; // output samples + uint8_t* yuv_out2_; // secondary buffer swapped with yuv_out_. + uint8_t* yuv_p_; // scratch buffer for prediction VP8Encoder* enc_; // back-pointer VP8MBInfo* mb_; // current macroblock VP8BitWriter* bw_; // current bit-writer @@ -292,24 +238,44 @@ typedef struct { uint64_t uv_bits_; // macroblock bit-cost for chroma LFStats* lf_stats_; // filter stats (borrowed from enc_) int do_trellis_; // if true, perform extra level optimisation - int done_; // true when scan is finished + int count_down_; // number of mb still to be processed + int count_down0_; // starting counter value (for progress) int percent0_; // saved initial progress percent + + uint8_t* y_left_; // left luma samples (addressable from index -1 to 15). + uint8_t* u_left_; // left u samples (addressable from index -1 to 7) + uint8_t* v_left_; // left v samples (addressable from index -1 to 7) + + uint8_t* y_top_; // top luma samples at position 'x_' + uint8_t* uv_top_; // top u/v samples at position 'x_', packed as 16 bytes + + // memory for storing y/u/v_left_ + uint8_t yuv_left_mem_[17 + 16 + 16 + 8 + WEBP_ALIGN_CST]; + // memory for yuv_* + uint8_t yuv_mem_[3 * YUV_SIZE_ENC + PRED_SIZE_ENC + WEBP_ALIGN_CST]; } VP8EncIterator; // in iterator.c -// must be called first. +// must be called first void VP8IteratorInit(VP8Encoder* const enc, VP8EncIterator* const it); -// restart a scan. +// restart a scan void VP8IteratorReset(VP8EncIterator* const it); -// import samples from source -void VP8IteratorImport(const VP8EncIterator* const it); +// reset iterator position to row 'y' +void VP8IteratorSetRow(VP8EncIterator* const it, int y); +// set count down (=number of iterations to go) +void VP8IteratorSetCountDown(VP8EncIterator* const it, int count_down); +// return true if iteration is finished +int VP8IteratorIsDone(const VP8EncIterator* const it); +// Import uncompressed samples from source. +// If tmp_32 is not NULL, import boundary samples too. +// tmp_32 is a 32-bytes scratch buffer that must be aligned in memory. +void VP8IteratorImport(VP8EncIterator* const it, uint8_t* tmp_32); // export decimated samples void VP8IteratorExport(const VP8EncIterator* const it); -// go to next macroblock. Returns !done_. If *block_to_save is non-null, will -// save the boundary values to top_/left_ arrays. block_to_save can be -// it->yuv_out_ or it->yuv_in_. -int VP8IteratorNext(VP8EncIterator* const it, - const uint8_t* const block_to_save); +// go to next macroblock. Returns false if not finished. +int VP8IteratorNext(VP8EncIterator* const it); +// save the yuv_out_ boundary values to top_/left_ arrays for next iterations. +void VP8IteratorSaveBoundary(VP8EncIterator* const it); // Report progression based on macroblock rows. Return 0 for user-abort request. int VP8IteratorProgress(const VP8EncIterator* const it, int final_delta_percent); @@ -340,12 +306,14 @@ typedef struct { VP8Tokens* pages_; // first page VP8Tokens** last_page_; // last page uint16_t* tokens_; // set to (*last_page_)->tokens_ - int left_; // how many free tokens left before the page is full. + int left_; // how many free tokens left before the page is full + int page_size_; // number of tokens per page #endif int error_; // true in case of malloc error } VP8TBuffer; -void VP8TBufferInit(VP8TBuffer* const b); // initialize an empty buffer +// initialize an empty buffer +void VP8TBufferInit(VP8TBuffer* const b, int page_size); void VP8TBufferClear(VP8TBuffer* const b); // de-allocate pages memory #if !defined(DISABLE_TOKEN_BUFFER) @@ -356,10 +324,12 @@ int VP8EmitTokens(VP8TBuffer* const b, VP8BitWriter* const bw, const uint8_t* const probas, int final_pass); // record the coding of coefficients without knowing the probabilities yet -int VP8RecordCoeffTokens(int ctx, int coeff_type, int first, int last, - const int16_t* const coeffs, +int VP8RecordCoeffTokens(int ctx, const struct VP8Residual* const res, VP8TBuffer* const tokens); +// Estimate the final coded size given a set of 'probas'. +size_t VP8EstimateTokenSize(VP8TBuffer* const b, const uint8_t* const probas); + // unused for now void VP8TokenToStats(const VP8TBuffer* const b, proba_t* const stats); @@ -373,8 +343,8 @@ struct VP8Encoder { WebPPicture* pic_; // input / output picture // headers - VP8FilterHeader filter_hdr_; // filtering information - VP8SegmentHeader segment_hdr_; // segment information + VP8EncFilterHeader filter_hdr_; // filtering information + VP8EncSegmentHeader segment_hdr_; // segment information int profile_; // VP8's profile, deduced from Config. @@ -398,12 +368,6 @@ struct VP8Encoder { uint32_t alpha_data_size_; WebPWorker alpha_worker_; - // enhancement layer - int use_layer_; - VP8BitWriter layer_bw_; - uint8_t* layer_data_; - size_t layer_data_size_; - // quantization info (one set of DC/AC dequant factor per segment) VP8SegmentInfo dqm_[NUM_MB_SEGMENTS]; int base_quant_; // nominal quantizer value. Only used @@ -416,17 +380,18 @@ struct VP8Encoder { int dq_uv_dc_, dq_uv_ac_; // probabilities and statistics - VP8Proba proba_; - uint64_t sse_[4]; // sum of Y/U/V/A squared errors for all macroblocks - uint64_t sse_count_; // pixel count for the sse_[] stats - int coded_size_; - int residual_bytes_[3][4]; - int block_count_[3]; + VP8EncProba proba_; + uint64_t sse_[4]; // sum of Y/U/V/A squared errors for all macroblocks + uint64_t sse_count_; // pixel count for the sse_[] stats + int coded_size_; + int residual_bytes_[3][4]; + int block_count_[3]; // quality/speed settings int method_; // 0=fastest, 6=best/slowest. VP8RDLevel rd_opt_level_; // Deduced from method_. int max_i4_header_bits_; // partition #0 safeness factor + int mb_header_limit_; // rough limit for header bits per MB int thread_level_; // derived from config->thread_level int do_search_; // derived from config->target_XXX int use_tokens_; // if true, use token buffer @@ -435,18 +400,10 @@ struct VP8Encoder { VP8MBInfo* mb_info_; // contextual macroblock infos (mb_w_ + 1) uint8_t* preds_; // predictions modes: (4*mb_w+1) * (4*mb_h+1) uint32_t* nz_; // non-zero bit context: mb_w+1 - uint8_t* yuv_in_; // input samples - uint8_t* yuv_out_; // output samples - uint8_t* yuv_out2_; // secondary scratch out-buffer. swapped with yuv_out_. - uint8_t* yuv_p_; // scratch buffer for prediction - uint8_t *y_top_; // top luma samples. - uint8_t *uv_top_; // top u/v samples. - // U and V are packed into 16 pixels (8 U + 8 V) - uint8_t *y_left_; // left luma samples (adressable from index -1 to 15). - uint8_t *u_left_; // left u samples (adressable from index -1 to 7) - uint8_t *v_left_; // left v samples (adressable from index -1 to 7) - - LFStats *lf_stats_; // autofilter stats (if NULL, autofilter is off) + uint8_t* y_top_; // top luma samples. + uint8_t* uv_top_; // top u/v samples. + // U and V are packed into 16 bytes (8 U + 8 V) + LFStats* lf_stats_; // autofilter stats (if NULL, autofilter is off) }; //------------------------------------------------------------------------------ @@ -459,7 +416,7 @@ extern const uint8_t // Reset the token probabilities to their initial (default) values void VP8DefaultProbas(VP8Encoder* const enc); // Write the token probabilities -void VP8WriteProbas(VP8BitWriter* const bw, const VP8Proba* const probas); +void VP8WriteProbas(VP8BitWriter* const bw, const VP8EncProba* const probas); // Writes the partition #0 modes (that is: all intra modes) void VP8CodeIntraModes(VP8Encoder* const enc); @@ -472,7 +429,6 @@ int VP8EncWrite(VP8Encoder* const enc); void VP8EncFreeBitWriters(VP8Encoder* const enc); // in frame.c -extern const uint8_t VP8EncBands[16 + 1]; extern const uint8_t VP8Cat3[]; extern const uint8_t VP8Cat4[]; extern const uint8_t VP8Cat5[]; @@ -517,33 +473,47 @@ int VP8EncStartAlpha(VP8Encoder* const enc); // start alpha coding process int VP8EncFinishAlpha(VP8Encoder* const enc); // finalize compressed data int VP8EncDeleteAlpha(VP8Encoder* const enc); // delete compressed data - // in layer.c -void VP8EncInitLayer(VP8Encoder* const enc); // init everything -void VP8EncCodeLayerBlock(VP8EncIterator* it); // code one more macroblock -int VP8EncFinishLayer(VP8Encoder* const enc); // finalize coding -void VP8EncDeleteLayer(VP8Encoder* enc); // reclaim memory - - // in filter.c - -// SSIM utils -typedef struct { - double w, xm, ym, xxm, xym, yym; -} DistoStats; -void VP8SSIMAddStats(const DistoStats* const src, DistoStats* const dst); -void VP8SSIMAccumulatePlane(const uint8_t* src1, int stride1, - const uint8_t* src2, int stride2, - int W, int H, DistoStats* const stats); -double VP8SSIMGet(const DistoStats* const stats); -double VP8SSIMGetSquaredError(const DistoStats* const stats); - // autofilter void VP8InitFilter(VP8EncIterator* const it); void VP8StoreFilterStats(VP8EncIterator* const it); void VP8AdjustFilterStrength(VP8EncIterator* const it); +// returns the approximate filtering strength needed to smooth a edge +// step of 'delta', given a sharpness parameter 'sharpness'. +int VP8FilterStrengthFromDelta(int sharpness, int delta); + + // misc utils for picture_*.c: + +// Remove reference to the ARGB/YUVA buffer (doesn't free anything). +void WebPPictureResetBuffers(WebPPicture* const picture); + +// Allocates ARGB buffer of given dimension (previous one is always free'd). +// Preserves the YUV(A) buffer. Returns false in case of error (invalid param, +// out-of-memory). +int WebPPictureAllocARGB(WebPPicture* const picture, int width, int height); + +// Allocates YUVA buffer of given dimension (previous one is always free'd). +// Uses picture->csp to determine whether an alpha buffer is needed. +// Preserves the ARGB buffer. +// Returns false in case of error (invalid param, out-of-memory). +int WebPPictureAllocYUVA(WebPPicture* const picture, int width, int height); + +// Clean-up the RGB samples under fully transparent area, to help lossless +// compressibility (no guarantee, though). Assumes that pic->use_argb is true. +void WebPCleanupTransparentAreaLossless(WebPPicture* const pic); + + // in near_lossless.c +// Near lossless preprocessing in RGB color-space. +int VP8ApplyNearLossless(int xsize, int ysize, uint32_t* argb, int quality); +// Near lossless adjustment for predictors. +void VP8ApplyNearLosslessPredict(int xsize, int ysize, int pred_bits, + const uint32_t* argb_orig, + uint32_t* argb, uint32_t* argb_scratch, + const uint32_t* const transform_data, + int quality, int subtract_green); //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/enc/vp8l.c b/3rdparty/libwebp/enc/vp8l.c deleted file mode 100644 index 945870ca46..0000000000 --- a/3rdparty/libwebp/enc/vp8l.c +++ /dev/null @@ -1,1175 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// main entry for the lossless encoder. -// -// Author: Vikas Arora (vikaas.arora@gmail.com) -// - -#include -#include -#include - -#include "./backward_references.h" -#include "./vp8enci.h" -#include "./vp8li.h" -#include "../dsp/lossless.h" -#include "../utils/bit_writer.h" -#include "../utils/huffman_encode.h" -#include "../utils/utils.h" -#include "../webp/format_constants.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#define PALETTE_KEY_RIGHT_SHIFT 22 // Key for 1K buffer. -#define MAX_HUFF_IMAGE_SIZE (16 * 1024 * 1024) -#define MAX_COLORS_FOR_GRAPH 64 - -// ----------------------------------------------------------------------------- -// Palette - -static int CompareColors(const void* p1, const void* p2) { - const uint32_t a = *(const uint32_t*)p1; - const uint32_t b = *(const uint32_t*)p2; - assert(a != b); - return (a < b) ? -1 : 1; -} - -// If number of colors in the image is less than or equal to MAX_PALETTE_SIZE, -// creates a palette and returns true, else returns false. -static int AnalyzeAndCreatePalette(const WebPPicture* const pic, - uint32_t palette[MAX_PALETTE_SIZE], - int* const palette_size) { - int i, x, y, key; - int num_colors = 0; - uint8_t in_use[MAX_PALETTE_SIZE * 4] = { 0 }; - uint32_t colors[MAX_PALETTE_SIZE * 4]; - static const uint32_t kHashMul = 0x1e35a7bd; - const uint32_t* argb = pic->argb; - const int width = pic->width; - const int height = pic->height; - uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0] - - for (y = 0; y < height; ++y) { - for (x = 0; x < width; ++x) { - if (argb[x] == last_pix) { - continue; - } - last_pix = argb[x]; - key = (kHashMul * last_pix) >> PALETTE_KEY_RIGHT_SHIFT; - while (1) { - if (!in_use[key]) { - colors[key] = last_pix; - in_use[key] = 1; - ++num_colors; - if (num_colors > MAX_PALETTE_SIZE) { - return 0; - } - break; - } else if (colors[key] == last_pix) { - // The color is already there. - break; - } else { - // Some other color sits there. - // Do linear conflict resolution. - ++key; - key &= (MAX_PALETTE_SIZE * 4 - 1); // key mask for 1K buffer. - } - } - } - argb += pic->argb_stride; - } - - // TODO(skal): could we reuse in_use[] to speed up EncodePalette()? - num_colors = 0; - for (i = 0; i < (int)(sizeof(in_use) / sizeof(in_use[0])); ++i) { - if (in_use[i]) { - palette[num_colors] = colors[i]; - ++num_colors; - } - } - - qsort(palette, num_colors, sizeof(*palette), CompareColors); - *palette_size = num_colors; - return 1; -} - -static int AnalyzeEntropy(const uint32_t* argb, - int width, int height, int argb_stride, - double* const nonpredicted_bits, - double* const predicted_bits) { - int x, y; - const uint32_t* last_line = NULL; - uint32_t last_pix = argb[0]; // so we're sure that pix_diff == 0 - - VP8LHistogram* nonpredicted = NULL; - VP8LHistogram* predicted = - (VP8LHistogram*)malloc(2 * sizeof(*predicted)); - if (predicted == NULL) return 0; - nonpredicted = predicted + 1; - - VP8LHistogramInit(predicted, 0); - VP8LHistogramInit(nonpredicted, 0); - for (y = 0; y < height; ++y) { - for (x = 0; x < width; ++x) { - const uint32_t pix = argb[x]; - const uint32_t pix_diff = VP8LSubPixels(pix, last_pix); - if (pix_diff == 0) continue; - if (last_line != NULL && pix == last_line[x]) { - continue; - } - last_pix = pix; - { - const PixOrCopy pix_token = PixOrCopyCreateLiteral(pix); - const PixOrCopy pix_diff_token = PixOrCopyCreateLiteral(pix_diff); - VP8LHistogramAddSinglePixOrCopy(nonpredicted, &pix_token); - VP8LHistogramAddSinglePixOrCopy(predicted, &pix_diff_token); - } - } - last_line = argb; - argb += argb_stride; - } - *nonpredicted_bits = VP8LHistogramEstimateBitsBulk(nonpredicted); - *predicted_bits = VP8LHistogramEstimateBitsBulk(predicted); - free(predicted); - return 1; -} - -static int VP8LEncAnalyze(VP8LEncoder* const enc, WebPImageHint image_hint) { - const WebPPicture* const pic = enc->pic_; - assert(pic != NULL && pic->argb != NULL); - - enc->use_palette_ = - AnalyzeAndCreatePalette(pic, enc->palette_, &enc->palette_size_); - - if (image_hint == WEBP_HINT_GRAPH) { - if (enc->use_palette_ && enc->palette_size_ < MAX_COLORS_FOR_GRAPH) { - enc->use_palette_ = 0; - } - } - - if (!enc->use_palette_) { - if (image_hint == WEBP_HINT_PHOTO) { - enc->use_predict_ = 1; - enc->use_cross_color_ = 1; - } else { - double non_pred_entropy, pred_entropy; - if (!AnalyzeEntropy(pic->argb, pic->width, pic->height, pic->argb_stride, - &non_pred_entropy, &pred_entropy)) { - return 0; - } - if (pred_entropy < 0.95 * non_pred_entropy) { - enc->use_predict_ = 1; - // TODO(vikasa): Observed some correlation of cross_color transform with - // predict. Need to investigate this further and add separate heuristic - // for setting use_cross_color flag. - enc->use_cross_color_ = 1; - } - } - } - - return 1; -} - -static int GetHuffBitLengthsAndCodes( - const VP8LHistogramSet* const histogram_image, - HuffmanTreeCode* const huffman_codes) { - int i, k; - int ok = 1; - uint64_t total_length_size = 0; - uint8_t* mem_buf = NULL; - const int histogram_image_size = histogram_image->size; - - // Iterate over all histograms and get the aggregate number of codes used. - for (i = 0; i < histogram_image_size; ++i) { - const VP8LHistogram* const histo = histogram_image->histograms[i]; - HuffmanTreeCode* const codes = &huffman_codes[5 * i]; - for (k = 0; k < 5; ++k) { - const int num_symbols = (k == 0) ? VP8LHistogramNumCodes(histo) - : (k == 4) ? NUM_DISTANCE_CODES - : 256; - codes[k].num_symbols = num_symbols; - total_length_size += num_symbols; - } - } - - // Allocate and Set Huffman codes. - { - uint16_t* codes; - uint8_t* lengths; - mem_buf = (uint8_t*)WebPSafeCalloc(total_length_size, - sizeof(*lengths) + sizeof(*codes)); - if (mem_buf == NULL) { - ok = 0; - goto End; - } - codes = (uint16_t*)mem_buf; - lengths = (uint8_t*)&codes[total_length_size]; - for (i = 0; i < 5 * histogram_image_size; ++i) { - const int bit_length = huffman_codes[i].num_symbols; - huffman_codes[i].codes = codes; - huffman_codes[i].code_lengths = lengths; - codes += bit_length; - lengths += bit_length; - } - } - - // Create Huffman trees. - for (i = 0; ok && (i < histogram_image_size); ++i) { - HuffmanTreeCode* const codes = &huffman_codes[5 * i]; - VP8LHistogram* const histo = histogram_image->histograms[i]; - ok = ok && VP8LCreateHuffmanTree(histo->literal_, 15, codes + 0); - ok = ok && VP8LCreateHuffmanTree(histo->red_, 15, codes + 1); - ok = ok && VP8LCreateHuffmanTree(histo->blue_, 15, codes + 2); - ok = ok && VP8LCreateHuffmanTree(histo->alpha_, 15, codes + 3); - ok = ok && VP8LCreateHuffmanTree(histo->distance_, 15, codes + 4); - } - - End: - if (!ok) { - free(mem_buf); - // If one VP8LCreateHuffmanTree() above fails, we need to clean up behind. - memset(huffman_codes, 0, 5 * histogram_image_size * sizeof(*huffman_codes)); - } - return ok; -} - -static void StoreHuffmanTreeOfHuffmanTreeToBitMask( - VP8LBitWriter* const bw, const uint8_t* code_length_bitdepth) { - // RFC 1951 will calm you down if you are worried about this funny sequence. - // This sequence is tuned from that, but more weighted for lower symbol count, - // and more spiking histograms. - static const uint8_t kStorageOrder[CODE_LENGTH_CODES] = { - 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 - }; - int i; - // Throw away trailing zeros: - int codes_to_store = CODE_LENGTH_CODES; - for (; codes_to_store > 4; --codes_to_store) { - if (code_length_bitdepth[kStorageOrder[codes_to_store - 1]] != 0) { - break; - } - } - VP8LWriteBits(bw, 4, codes_to_store - 4); - for (i = 0; i < codes_to_store; ++i) { - VP8LWriteBits(bw, 3, code_length_bitdepth[kStorageOrder[i]]); - } -} - -static void ClearHuffmanTreeIfOnlyOneSymbol( - HuffmanTreeCode* const huffman_code) { - int k; - int count = 0; - for (k = 0; k < huffman_code->num_symbols; ++k) { - if (huffman_code->code_lengths[k] != 0) { - ++count; - if (count > 1) return; - } - } - for (k = 0; k < huffman_code->num_symbols; ++k) { - huffman_code->code_lengths[k] = 0; - huffman_code->codes[k] = 0; - } -} - -static void StoreHuffmanTreeToBitMask( - VP8LBitWriter* const bw, - const HuffmanTreeToken* const tokens, const int num_tokens, - const HuffmanTreeCode* const huffman_code) { - int i; - for (i = 0; i < num_tokens; ++i) { - const int ix = tokens[i].code; - const int extra_bits = tokens[i].extra_bits; - VP8LWriteBits(bw, huffman_code->code_lengths[ix], huffman_code->codes[ix]); - switch (ix) { - case 16: - VP8LWriteBits(bw, 2, extra_bits); - break; - case 17: - VP8LWriteBits(bw, 3, extra_bits); - break; - case 18: - VP8LWriteBits(bw, 7, extra_bits); - break; - } - } -} - -static int StoreFullHuffmanCode(VP8LBitWriter* const bw, - const HuffmanTreeCode* const tree) { - int ok = 0; - uint8_t code_length_bitdepth[CODE_LENGTH_CODES] = { 0 }; - uint16_t code_length_bitdepth_symbols[CODE_LENGTH_CODES] = { 0 }; - const int max_tokens = tree->num_symbols; - int num_tokens; - HuffmanTreeCode huffman_code; - HuffmanTreeToken* const tokens = - (HuffmanTreeToken*)WebPSafeMalloc((uint64_t)max_tokens, sizeof(*tokens)); - if (tokens == NULL) return 0; - - huffman_code.num_symbols = CODE_LENGTH_CODES; - huffman_code.code_lengths = code_length_bitdepth; - huffman_code.codes = code_length_bitdepth_symbols; - - VP8LWriteBits(bw, 1, 0); - num_tokens = VP8LCreateCompressedHuffmanTree(tree, tokens, max_tokens); - { - int histogram[CODE_LENGTH_CODES] = { 0 }; - int i; - for (i = 0; i < num_tokens; ++i) { - ++histogram[tokens[i].code]; - } - - if (!VP8LCreateHuffmanTree(histogram, 7, &huffman_code)) { - goto End; - } - } - - StoreHuffmanTreeOfHuffmanTreeToBitMask(bw, code_length_bitdepth); - ClearHuffmanTreeIfOnlyOneSymbol(&huffman_code); - { - int trailing_zero_bits = 0; - int trimmed_length = num_tokens; - int write_trimmed_length; - int length; - int i = num_tokens; - while (i-- > 0) { - const int ix = tokens[i].code; - if (ix == 0 || ix == 17 || ix == 18) { - --trimmed_length; // discount trailing zeros - trailing_zero_bits += code_length_bitdepth[ix]; - if (ix == 17) { - trailing_zero_bits += 3; - } else if (ix == 18) { - trailing_zero_bits += 7; - } - } else { - break; - } - } - write_trimmed_length = (trimmed_length > 1 && trailing_zero_bits > 12); - length = write_trimmed_length ? trimmed_length : num_tokens; - VP8LWriteBits(bw, 1, write_trimmed_length); - if (write_trimmed_length) { - const int nbits = VP8LBitsLog2Ceiling(trimmed_length - 1); - const int nbitpairs = (nbits == 0) ? 1 : (nbits + 1) / 2; - VP8LWriteBits(bw, 3, nbitpairs - 1); - assert(trimmed_length >= 2); - VP8LWriteBits(bw, nbitpairs * 2, trimmed_length - 2); - } - StoreHuffmanTreeToBitMask(bw, tokens, length, &huffman_code); - } - ok = 1; - End: - free(tokens); - return ok; -} - -static int StoreHuffmanCode(VP8LBitWriter* const bw, - const HuffmanTreeCode* const huffman_code) { - int i; - int count = 0; - int symbols[2] = { 0, 0 }; - const int kMaxBits = 8; - const int kMaxSymbol = 1 << kMaxBits; - - // Check whether it's a small tree. - for (i = 0; i < huffman_code->num_symbols && count < 3; ++i) { - if (huffman_code->code_lengths[i] != 0) { - if (count < 2) symbols[count] = i; - ++count; - } - } - - if (count == 0) { // emit minimal tree for empty cases - // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 - VP8LWriteBits(bw, 4, 0x01); - return 1; - } else if (count <= 2 && symbols[0] < kMaxSymbol && symbols[1] < kMaxSymbol) { - VP8LWriteBits(bw, 1, 1); // Small tree marker to encode 1 or 2 symbols. - VP8LWriteBits(bw, 1, count - 1); - if (symbols[0] <= 1) { - VP8LWriteBits(bw, 1, 0); // Code bit for small (1 bit) symbol value. - VP8LWriteBits(bw, 1, symbols[0]); - } else { - VP8LWriteBits(bw, 1, 1); - VP8LWriteBits(bw, 8, symbols[0]); - } - if (count == 2) { - VP8LWriteBits(bw, 8, symbols[1]); - } - return 1; - } else { - return StoreFullHuffmanCode(bw, huffman_code); - } -} - -static void WriteHuffmanCode(VP8LBitWriter* const bw, - const HuffmanTreeCode* const code, - int code_index) { - const int depth = code->code_lengths[code_index]; - const int symbol = code->codes[code_index]; - VP8LWriteBits(bw, depth, symbol); -} - -static void StoreImageToBitMask( - VP8LBitWriter* const bw, int width, int histo_bits, - const VP8LBackwardRefs* const refs, - const uint16_t* histogram_symbols, - const HuffmanTreeCode* const huffman_codes) { - // x and y trace the position in the image. - int x = 0; - int y = 0; - const int histo_xsize = histo_bits ? VP8LSubSampleSize(width, histo_bits) : 1; - int i; - for (i = 0; i < refs->size; ++i) { - const PixOrCopy* const v = &refs->refs[i]; - const int histogram_ix = histogram_symbols[histo_bits ? - (y >> histo_bits) * histo_xsize + - (x >> histo_bits) : 0]; - const HuffmanTreeCode* const codes = huffman_codes + 5 * histogram_ix; - if (PixOrCopyIsCacheIdx(v)) { - const int code = PixOrCopyCacheIdx(v); - const int literal_ix = 256 + NUM_LENGTH_CODES + code; - WriteHuffmanCode(bw, codes, literal_ix); - } else if (PixOrCopyIsLiteral(v)) { - static const int order[] = { 1, 2, 0, 3 }; - int k; - for (k = 0; k < 4; ++k) { - const int code = PixOrCopyLiteral(v, order[k]); - WriteHuffmanCode(bw, codes + k, code); - } - } else { - int bits, n_bits; - int code, distance; - - PrefixEncode(v->len, &code, &n_bits, &bits); - WriteHuffmanCode(bw, codes, 256 + code); - VP8LWriteBits(bw, n_bits, bits); - - distance = PixOrCopyDistance(v); - PrefixEncode(distance, &code, &n_bits, &bits); - WriteHuffmanCode(bw, codes + 4, code); - VP8LWriteBits(bw, n_bits, bits); - } - x += PixOrCopyLength(v); - while (x >= width) { - x -= width; - ++y; - } - } -} - -// Special case of EncodeImageInternal() for cache-bits=0, histo_bits=31 -static int EncodeImageNoHuffman(VP8LBitWriter* const bw, - const uint32_t* const argb, - int width, int height, int quality) { - int i; - int ok = 0; - VP8LBackwardRefs refs; - HuffmanTreeCode huffman_codes[5] = { { 0, NULL, NULL } }; - const uint16_t histogram_symbols[1] = { 0 }; // only one tree, one symbol - VP8LHistogramSet* const histogram_image = VP8LAllocateHistogramSet(1, 0); - if (histogram_image == NULL) return 0; - - // Calculate backward references from ARGB image. - if (!VP8LGetBackwardReferences(width, height, argb, quality, 0, 1, &refs)) { - goto Error; - } - // Build histogram image and symbols from backward references. - VP8LHistogramStoreRefs(&refs, histogram_image->histograms[0]); - - // Create Huffman bit lengths and codes for each histogram image. - assert(histogram_image->size == 1); - if (!GetHuffBitLengthsAndCodes(histogram_image, huffman_codes)) { - goto Error; - } - - // No color cache, no Huffman image. - VP8LWriteBits(bw, 1, 0); - - // Store Huffman codes. - for (i = 0; i < 5; ++i) { - HuffmanTreeCode* const codes = &huffman_codes[i]; - if (!StoreHuffmanCode(bw, codes)) { - goto Error; - } - ClearHuffmanTreeIfOnlyOneSymbol(codes); - } - - // Store actual literals. - StoreImageToBitMask(bw, width, 0, &refs, histogram_symbols, huffman_codes); - ok = 1; - - Error: - free(histogram_image); - VP8LClearBackwardRefs(&refs); - free(huffman_codes[0].codes); - return ok; -} - -static int EncodeImageInternal(VP8LBitWriter* const bw, - const uint32_t* const argb, - int width, int height, int quality, - int cache_bits, int histogram_bits) { - int ok = 0; - const int use_2d_locality = 1; - const int use_color_cache = (cache_bits > 0); - const uint32_t histogram_image_xysize = - VP8LSubSampleSize(width, histogram_bits) * - VP8LSubSampleSize(height, histogram_bits); - VP8LHistogramSet* histogram_image = - VP8LAllocateHistogramSet(histogram_image_xysize, 0); - int histogram_image_size = 0; - size_t bit_array_size = 0; - HuffmanTreeCode* huffman_codes = NULL; - VP8LBackwardRefs refs; - uint16_t* const histogram_symbols = - (uint16_t*)WebPSafeMalloc((uint64_t)histogram_image_xysize, - sizeof(*histogram_symbols)); - assert(histogram_bits >= MIN_HUFFMAN_BITS); - assert(histogram_bits <= MAX_HUFFMAN_BITS); - - if (histogram_image == NULL || histogram_symbols == NULL) { - free(histogram_image); - free(histogram_symbols); - return 0; - } - - // Calculate backward references from ARGB image. - if (!VP8LGetBackwardReferences(width, height, argb, quality, cache_bits, - use_2d_locality, &refs)) { - goto Error; - } - // Build histogram image and symbols from backward references. - if (!VP8LGetHistoImageSymbols(width, height, &refs, - quality, histogram_bits, cache_bits, - histogram_image, - histogram_symbols)) { - goto Error; - } - // Create Huffman bit lengths and codes for each histogram image. - histogram_image_size = histogram_image->size; - bit_array_size = 5 * histogram_image_size; - huffman_codes = (HuffmanTreeCode*)WebPSafeCalloc(bit_array_size, - sizeof(*huffman_codes)); - if (huffman_codes == NULL || - !GetHuffBitLengthsAndCodes(histogram_image, huffman_codes)) { - goto Error; - } - // Free combined histograms. - free(histogram_image); - histogram_image = NULL; - - // Color Cache parameters. - VP8LWriteBits(bw, 1, use_color_cache); - if (use_color_cache) { - VP8LWriteBits(bw, 4, cache_bits); - } - - // Huffman image + meta huffman. - { - const int write_histogram_image = (histogram_image_size > 1); - VP8LWriteBits(bw, 1, write_histogram_image); - if (write_histogram_image) { - uint32_t* const histogram_argb = - (uint32_t*)WebPSafeMalloc((uint64_t)histogram_image_xysize, - sizeof(*histogram_argb)); - int max_index = 0; - uint32_t i; - if (histogram_argb == NULL) goto Error; - for (i = 0; i < histogram_image_xysize; ++i) { - const int symbol_index = histogram_symbols[i] & 0xffff; - histogram_argb[i] = 0xff000000 | (symbol_index << 8); - if (symbol_index >= max_index) { - max_index = symbol_index + 1; - } - } - histogram_image_size = max_index; - - VP8LWriteBits(bw, 3, histogram_bits - 2); - ok = EncodeImageNoHuffman(bw, histogram_argb, - VP8LSubSampleSize(width, histogram_bits), - VP8LSubSampleSize(height, histogram_bits), - quality); - free(histogram_argb); - if (!ok) goto Error; - } - } - - // Store Huffman codes. - { - int i; - for (i = 0; i < 5 * histogram_image_size; ++i) { - HuffmanTreeCode* const codes = &huffman_codes[i]; - if (!StoreHuffmanCode(bw, codes)) goto Error; - ClearHuffmanTreeIfOnlyOneSymbol(codes); - } - } - - // Store actual literals. - StoreImageToBitMask(bw, width, histogram_bits, &refs, - histogram_symbols, huffman_codes); - ok = 1; - - Error: - free(histogram_image); - - VP8LClearBackwardRefs(&refs); - if (huffman_codes != NULL) { - free(huffman_codes->codes); - free(huffman_codes); - } - free(histogram_symbols); - return ok; -} - -// ----------------------------------------------------------------------------- -// Transforms - -// Check if it would be a good idea to subtract green from red and blue. We -// only impact entropy in red/blue components, don't bother to look at others. -static int EvalAndApplySubtractGreen(VP8LEncoder* const enc, - int width, int height, - VP8LBitWriter* const bw) { - if (!enc->use_palette_) { - int i; - const uint32_t* const argb = enc->argb_; - double bit_cost_before, bit_cost_after; - VP8LHistogram* const histo = (VP8LHistogram*)malloc(sizeof(*histo)); - if (histo == NULL) return 0; - - VP8LHistogramInit(histo, 1); - for (i = 0; i < width * height; ++i) { - const uint32_t c = argb[i]; - ++histo->red_[(c >> 16) & 0xff]; - ++histo->blue_[(c >> 0) & 0xff]; - } - bit_cost_before = VP8LHistogramEstimateBits(histo); - - VP8LHistogramInit(histo, 1); - for (i = 0; i < width * height; ++i) { - const uint32_t c = argb[i]; - const int green = (c >> 8) & 0xff; - ++histo->red_[((c >> 16) - green) & 0xff]; - ++histo->blue_[((c >> 0) - green) & 0xff]; - } - bit_cost_after = VP8LHistogramEstimateBits(histo); - free(histo); - - // Check if subtracting green yields low entropy. - enc->use_subtract_green_ = (bit_cost_after < bit_cost_before); - if (enc->use_subtract_green_) { - VP8LWriteBits(bw, 1, TRANSFORM_PRESENT); - VP8LWriteBits(bw, 2, SUBTRACT_GREEN); - VP8LSubtractGreenFromBlueAndRed(enc->argb_, width * height); - } - } - return 1; -} - -static int ApplyPredictFilter(const VP8LEncoder* const enc, - int width, int height, int quality, - VP8LBitWriter* const bw) { - const int pred_bits = enc->transform_bits_; - const int transform_width = VP8LSubSampleSize(width, pred_bits); - const int transform_height = VP8LSubSampleSize(height, pred_bits); - - VP8LResidualImage(width, height, pred_bits, enc->argb_, enc->argb_scratch_, - enc->transform_data_); - VP8LWriteBits(bw, 1, TRANSFORM_PRESENT); - VP8LWriteBits(bw, 2, PREDICTOR_TRANSFORM); - assert(pred_bits >= 2); - VP8LWriteBits(bw, 3, pred_bits - 2); - if (!EncodeImageNoHuffman(bw, enc->transform_data_, - transform_width, transform_height, quality)) { - return 0; - } - return 1; -} - -static int ApplyCrossColorFilter(const VP8LEncoder* const enc, - int width, int height, int quality, - VP8LBitWriter* const bw) { - const int ccolor_transform_bits = enc->transform_bits_; - const int transform_width = VP8LSubSampleSize(width, ccolor_transform_bits); - const int transform_height = VP8LSubSampleSize(height, ccolor_transform_bits); - const int step = (quality == 0) ? 32 : 8; - - VP8LColorSpaceTransform(width, height, ccolor_transform_bits, step, - enc->argb_, enc->transform_data_); - VP8LWriteBits(bw, 1, TRANSFORM_PRESENT); - VP8LWriteBits(bw, 2, CROSS_COLOR_TRANSFORM); - assert(ccolor_transform_bits >= 2); - VP8LWriteBits(bw, 3, ccolor_transform_bits - 2); - if (!EncodeImageNoHuffman(bw, enc->transform_data_, - transform_width, transform_height, quality)) { - return 0; - } - return 1; -} - -// ----------------------------------------------------------------------------- - -static WebPEncodingError WriteRiffHeader(const WebPPicture* const pic, - size_t riff_size, size_t vp8l_size) { - uint8_t riff[RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE + VP8L_SIGNATURE_SIZE] = { - 'R', 'I', 'F', 'F', 0, 0, 0, 0, 'W', 'E', 'B', 'P', - 'V', 'P', '8', 'L', 0, 0, 0, 0, VP8L_MAGIC_BYTE, - }; - PutLE32(riff + TAG_SIZE, (uint32_t)riff_size); - PutLE32(riff + RIFF_HEADER_SIZE + TAG_SIZE, (uint32_t)vp8l_size); - if (!pic->writer(riff, sizeof(riff), pic)) { - return VP8_ENC_ERROR_BAD_WRITE; - } - return VP8_ENC_OK; -} - -static int WriteImageSize(const WebPPicture* const pic, - VP8LBitWriter* const bw) { - const int width = pic->width - 1; - const int height = pic->height - 1; - assert(width < WEBP_MAX_DIMENSION && height < WEBP_MAX_DIMENSION); - - VP8LWriteBits(bw, VP8L_IMAGE_SIZE_BITS, width); - VP8LWriteBits(bw, VP8L_IMAGE_SIZE_BITS, height); - return !bw->error_; -} - -static int WriteRealAlphaAndVersion(VP8LBitWriter* const bw, int has_alpha) { - VP8LWriteBits(bw, 1, has_alpha); - VP8LWriteBits(bw, VP8L_VERSION_BITS, VP8L_VERSION); - return !bw->error_; -} - -static WebPEncodingError WriteImage(const WebPPicture* const pic, - VP8LBitWriter* const bw, - size_t* const coded_size) { - WebPEncodingError err = VP8_ENC_OK; - const uint8_t* const webpll_data = VP8LBitWriterFinish(bw); - const size_t webpll_size = VP8LBitWriterNumBytes(bw); - const size_t vp8l_size = VP8L_SIGNATURE_SIZE + webpll_size; - const size_t pad = vp8l_size & 1; - const size_t riff_size = TAG_SIZE + CHUNK_HEADER_SIZE + vp8l_size + pad; - - err = WriteRiffHeader(pic, riff_size, vp8l_size); - if (err != VP8_ENC_OK) goto Error; - - if (!pic->writer(webpll_data, webpll_size, pic)) { - err = VP8_ENC_ERROR_BAD_WRITE; - goto Error; - } - - if (pad) { - const uint8_t pad_byte[1] = { 0 }; - if (!pic->writer(pad_byte, 1, pic)) { - err = VP8_ENC_ERROR_BAD_WRITE; - goto Error; - } - } - *coded_size = CHUNK_HEADER_SIZE + riff_size; - return VP8_ENC_OK; - - Error: - return err; -} - -// ----------------------------------------------------------------------------- - -// Allocates the memory for argb (W x H) buffer, 2 rows of context for -// prediction and transform data. -static WebPEncodingError AllocateTransformBuffer(VP8LEncoder* const enc, - int width, int height) { - WebPEncodingError err = VP8_ENC_OK; - const int tile_size = 1 << enc->transform_bits_; - const uint64_t image_size = width * height; - const uint64_t argb_scratch_size = tile_size * width + width; - const uint64_t transform_data_size = - (uint64_t)VP8LSubSampleSize(width, enc->transform_bits_) * - (uint64_t)VP8LSubSampleSize(height, enc->transform_bits_); - const uint64_t total_size = - image_size + argb_scratch_size + transform_data_size; - uint32_t* mem = (uint32_t*)WebPSafeMalloc(total_size, sizeof(*mem)); - if (mem == NULL) { - err = VP8_ENC_ERROR_OUT_OF_MEMORY; - goto Error; - } - enc->argb_ = mem; - mem += image_size; - enc->argb_scratch_ = mem; - mem += argb_scratch_size; - enc->transform_data_ = mem; - enc->current_width_ = width; - - Error: - return err; -} - -static void ApplyPalette(uint32_t* src, uint32_t* dst, - uint32_t src_stride, uint32_t dst_stride, - const uint32_t* palette, int palette_size, - int width, int height, int xbits, uint8_t* row) { - int i, x, y; - int use_LUT = 1; - for (i = 0; i < palette_size; ++i) { - if ((palette[i] & 0xffff00ffu) != 0) { - use_LUT = 0; - break; - } - } - - if (use_LUT) { - int inv_palette[MAX_PALETTE_SIZE] = { 0 }; - for (i = 0; i < palette_size; ++i) { - const int color = (palette[i] >> 8) & 0xff; - inv_palette[color] = i; - } - for (y = 0; y < height; ++y) { - for (x = 0; x < width; ++x) { - const int color = (src[x] >> 8) & 0xff; - row[x] = inv_palette[color]; - } - VP8LBundleColorMap(row, width, xbits, dst); - src += src_stride; - dst += dst_stride; - } - } else { - // Use 1 pixel cache for ARGB pixels. - uint32_t last_pix = palette[0]; - int last_idx = 0; - for (y = 0; y < height; ++y) { - for (x = 0; x < width; ++x) { - const uint32_t pix = src[x]; - if (pix != last_pix) { - for (i = 0; i < palette_size; ++i) { - if (pix == palette[i]) { - last_idx = i; - last_pix = pix; - break; - } - } - } - row[x] = last_idx; - } - VP8LBundleColorMap(row, width, xbits, dst); - src += src_stride; - dst += dst_stride; - } - } -} - -// Note: Expects "enc->palette_" to be set properly. -// Also, "enc->palette_" will be modified after this call and should not be used -// later. -static WebPEncodingError EncodePalette(VP8LBitWriter* const bw, - VP8LEncoder* const enc, int quality) { - WebPEncodingError err = VP8_ENC_OK; - int i; - const WebPPicture* const pic = enc->pic_; - uint32_t* src = pic->argb; - uint32_t* dst; - const int width = pic->width; - const int height = pic->height; - uint32_t* const palette = enc->palette_; - const int palette_size = enc->palette_size_; - uint8_t* row = NULL; - int xbits; - - // Replace each input pixel by corresponding palette index. - // This is done line by line. - if (palette_size <= 4) { - xbits = (palette_size <= 2) ? 3 : 2; - } else { - xbits = (palette_size <= 16) ? 1 : 0; - } - - err = AllocateTransformBuffer(enc, VP8LSubSampleSize(width, xbits), height); - if (err != VP8_ENC_OK) goto Error; - dst = enc->argb_; - - row = WebPSafeMalloc((uint64_t)width, sizeof(*row)); - if (row == NULL) return VP8_ENC_ERROR_OUT_OF_MEMORY; - - ApplyPalette(src, dst, pic->argb_stride, enc->current_width_, - palette, palette_size, width, height, xbits, row); - - // Save palette to bitstream. - VP8LWriteBits(bw, 1, TRANSFORM_PRESENT); - VP8LWriteBits(bw, 2, COLOR_INDEXING_TRANSFORM); - assert(palette_size >= 1); - VP8LWriteBits(bw, 8, palette_size - 1); - for (i = palette_size - 1; i >= 1; --i) { - palette[i] = VP8LSubPixels(palette[i], palette[i - 1]); - } - if (!EncodeImageNoHuffman(bw, palette, palette_size, 1, quality)) { - err = VP8_ENC_ERROR_INVALID_CONFIGURATION; - goto Error; - } - - Error: - free(row); - return err; -} - -// ----------------------------------------------------------------------------- - -static int GetHistoBits(int method, int use_palette, int width, int height) { - const uint64_t hist_size = sizeof(VP8LHistogram); - // Make tile size a function of encoding method (Range: 0 to 6). - int histo_bits = (use_palette ? 9 : 7) - method; - while (1) { - const uint64_t huff_image_size = VP8LSubSampleSize(width, histo_bits) * - VP8LSubSampleSize(height, histo_bits) * - hist_size; - if (huff_image_size <= MAX_HUFF_IMAGE_SIZE) break; - ++histo_bits; - } - return (histo_bits < MIN_HUFFMAN_BITS) ? MIN_HUFFMAN_BITS : - (histo_bits > MAX_HUFFMAN_BITS) ? MAX_HUFFMAN_BITS : histo_bits; -} - -static void FinishEncParams(VP8LEncoder* const enc) { - const WebPConfig* const config = enc->config_; - const WebPPicture* const pic = enc->pic_; - const int method = config->method; - const float quality = config->quality; - const int use_palette = enc->use_palette_; - enc->transform_bits_ = (method < 4) ? 5 : (method > 4) ? 3 : 4; - enc->histo_bits_ = GetHistoBits(method, use_palette, pic->width, pic->height); - enc->cache_bits_ = (quality <= 25.f) ? 0 : 7; -} - -// ----------------------------------------------------------------------------- -// VP8LEncoder - -static VP8LEncoder* VP8LEncoderNew(const WebPConfig* const config, - const WebPPicture* const picture) { - VP8LEncoder* const enc = (VP8LEncoder*)calloc(1, sizeof(*enc)); - if (enc == NULL) { - WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY); - return NULL; - } - enc->config_ = config; - enc->pic_ = picture; - return enc; -} - -static void VP8LEncoderDelete(VP8LEncoder* enc) { - free(enc->argb_); - free(enc); -} - -// ----------------------------------------------------------------------------- -// Main call - -WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, - const WebPPicture* const picture, - VP8LBitWriter* const bw) { - WebPEncodingError err = VP8_ENC_OK; - const int quality = (int)config->quality; - const int width = picture->width; - const int height = picture->height; - VP8LEncoder* const enc = VP8LEncoderNew(config, picture); - const size_t byte_position = VP8LBitWriterNumBytes(bw); - - if (enc == NULL) { - err = VP8_ENC_ERROR_OUT_OF_MEMORY; - goto Error; - } - - // --------------------------------------------------------------------------- - // Analyze image (entropy, num_palettes etc) - - if (!VP8LEncAnalyze(enc, config->image_hint)) { - err = VP8_ENC_ERROR_OUT_OF_MEMORY; - goto Error; - } - - FinishEncParams(enc); - - if (enc->use_palette_) { - err = EncodePalette(bw, enc, quality); - if (err != VP8_ENC_OK) goto Error; - // Color cache is disabled for palette. - enc->cache_bits_ = 0; - } - - // In case image is not packed. - if (enc->argb_ == NULL) { - int y; - err = AllocateTransformBuffer(enc, width, height); - if (err != VP8_ENC_OK) goto Error; - for (y = 0; y < height; ++y) { - memcpy(enc->argb_ + y * width, - picture->argb + y * picture->argb_stride, - width * sizeof(*enc->argb_)); - } - enc->current_width_ = width; - } - - // --------------------------------------------------------------------------- - // Apply transforms and write transform data. - - if (!EvalAndApplySubtractGreen(enc, enc->current_width_, height, bw)) { - err = VP8_ENC_ERROR_OUT_OF_MEMORY; - goto Error; - } - - if (enc->use_predict_) { - if (!ApplyPredictFilter(enc, enc->current_width_, height, quality, bw)) { - err = VP8_ENC_ERROR_INVALID_CONFIGURATION; - goto Error; - } - } - - if (enc->use_cross_color_) { - if (!ApplyCrossColorFilter(enc, enc->current_width_, height, quality, bw)) { - err = VP8_ENC_ERROR_INVALID_CONFIGURATION; - goto Error; - } - } - - VP8LWriteBits(bw, 1, !TRANSFORM_PRESENT); // No more transforms. - - // --------------------------------------------------------------------------- - // Estimate the color cache size. - - if (enc->cache_bits_ > 0) { - if (!VP8LCalculateEstimateForCacheSize(enc->argb_, enc->current_width_, - height, &enc->cache_bits_)) { - err = VP8_ENC_ERROR_INVALID_CONFIGURATION; - goto Error; - } - } - - // --------------------------------------------------------------------------- - // Encode and write the transformed image. - - if (!EncodeImageInternal(bw, enc->argb_, enc->current_width_, height, - quality, enc->cache_bits_, enc->histo_bits_)) { - err = VP8_ENC_ERROR_OUT_OF_MEMORY; - goto Error; - } - - if (picture->stats != NULL) { - WebPAuxStats* const stats = picture->stats; - stats->lossless_features = 0; - if (enc->use_predict_) stats->lossless_features |= 1; - if (enc->use_cross_color_) stats->lossless_features |= 2; - if (enc->use_subtract_green_) stats->lossless_features |= 4; - if (enc->use_palette_) stats->lossless_features |= 8; - stats->histogram_bits = enc->histo_bits_; - stats->transform_bits = enc->transform_bits_; - stats->cache_bits = enc->cache_bits_; - stats->palette_size = enc->palette_size_; - stats->lossless_size = (int)(VP8LBitWriterNumBytes(bw) - byte_position); - } - - Error: - VP8LEncoderDelete(enc); - return err; -} - -int VP8LEncodeImage(const WebPConfig* const config, - const WebPPicture* const picture) { - int width, height; - int has_alpha; - size_t coded_size; - int percent = 0; - WebPEncodingError err = VP8_ENC_OK; - VP8LBitWriter bw; - - if (picture == NULL) return 0; - - if (config == NULL || picture->argb == NULL) { - err = VP8_ENC_ERROR_NULL_PARAMETER; - WebPEncodingSetError(picture, err); - return 0; - } - - width = picture->width; - height = picture->height; - if (!VP8LBitWriterInit(&bw, (width * height) >> 1)) { - err = VP8_ENC_ERROR_OUT_OF_MEMORY; - goto Error; - } - - if (!WebPReportProgress(picture, 1, &percent)) { - UserAbort: - err = VP8_ENC_ERROR_USER_ABORT; - goto Error; - } - // Reset stats (for pure lossless coding) - if (picture->stats != NULL) { - WebPAuxStats* const stats = picture->stats; - memset(stats, 0, sizeof(*stats)); - stats->PSNR[0] = 99.f; - stats->PSNR[1] = 99.f; - stats->PSNR[2] = 99.f; - stats->PSNR[3] = 99.f; - stats->PSNR[4] = 99.f; - } - - // Write image size. - if (!WriteImageSize(picture, &bw)) { - err = VP8_ENC_ERROR_OUT_OF_MEMORY; - goto Error; - } - - has_alpha = WebPPictureHasTransparency(picture); - // Write the non-trivial Alpha flag and lossless version. - if (!WriteRealAlphaAndVersion(&bw, has_alpha)) { - err = VP8_ENC_ERROR_OUT_OF_MEMORY; - goto Error; - } - - if (!WebPReportProgress(picture, 5, &percent)) goto UserAbort; - - // Encode main image stream. - err = VP8LEncodeStream(config, picture, &bw); - if (err != VP8_ENC_OK) goto Error; - - // TODO(skal): have a fine-grained progress report in VP8LEncodeStream(). - if (!WebPReportProgress(picture, 90, &percent)) goto UserAbort; - - // Finish the RIFF chunk. - err = WriteImage(picture, &bw, &coded_size); - if (err != VP8_ENC_OK) goto Error; - - if (!WebPReportProgress(picture, 100, &percent)) goto UserAbort; - - // Save size. - if (picture->stats != NULL) { - picture->stats->coded_size += (int)coded_size; - picture->stats->lossless_size = (int)coded_size; - } - - if (picture->extra_info != NULL) { - const int mb_w = (width + 15) >> 4; - const int mb_h = (height + 15) >> 4; - memset(picture->extra_info, 0, mb_w * mb_h * sizeof(*picture->extra_info)); - } - - Error: - if (bw.error_) err = VP8_ENC_ERROR_OUT_OF_MEMORY; - VP8LBitWriterDestroy(&bw); - if (err != VP8_ENC_OK) { - WebPEncodingSetError(picture, err); - return 0; - } - return 1; -} - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/enc/vp8l_enc.c b/3rdparty/libwebp/enc/vp8l_enc.c new file mode 100644 index 0000000000..b1a793d956 --- /dev/null +++ b/3rdparty/libwebp/enc/vp8l_enc.c @@ -0,0 +1,1667 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// main entry for the lossless encoder. +// +// Author: Vikas Arora (vikaas.arora@gmail.com) +// + +#include +#include + +#include "./backward_references_enc.h" +#include "./histogram_enc.h" +#include "./vp8i_enc.h" +#include "./vp8li_enc.h" +#include "../dsp/lossless.h" +#include "../dsp/lossless_common.h" +#include "../utils/bit_writer_utils.h" +#include "../utils/huffman_encode_utils.h" +#include "../utils/utils.h" +#include "../webp/format_constants.h" + +#include "./delta_palettization_enc.h" + +#define PALETTE_KEY_RIGHT_SHIFT 22 // Key for 1K buffer. +// Maximum number of histogram images (sub-blocks). +#define MAX_HUFF_IMAGE_SIZE 2600 + +// Palette reordering for smaller sum of deltas (and for smaller storage). + +static int PaletteCompareColorsForQsort(const void* p1, const void* p2) { + const uint32_t a = WebPMemToUint32((uint8_t*)p1); + const uint32_t b = WebPMemToUint32((uint8_t*)p2); + assert(a != b); + return (a < b) ? -1 : 1; +} + +static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) { + return (v <= 128) ? v : (256 - v); +} + +// Computes a value that is related to the entropy created by the +// palette entry diff. +// +// Note that the last & 0xff is a no-operation in the next statement, but +// removed by most compilers and is here only for regularity of the code. +static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) { + const uint32_t diff = VP8LSubPixels(col1, col2); + const int kMoreWeightForRGBThanForAlpha = 9; + uint32_t score; + score = PaletteComponentDistance((diff >> 0) & 0xff); + score += PaletteComponentDistance((diff >> 8) & 0xff); + score += PaletteComponentDistance((diff >> 16) & 0xff); + score *= kMoreWeightForRGBThanForAlpha; + score += PaletteComponentDistance((diff >> 24) & 0xff); + return score; +} + +static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) { + const uint32_t tmp = *col1; + *col1 = *col2; + *col2 = tmp; +} + +static void GreedyMinimizeDeltas(uint32_t palette[], int num_colors) { + // Find greedily always the closest color of the predicted color to minimize + // deltas in the palette. This reduces storage needs since the + // palette is stored with delta encoding. + uint32_t predict = 0x00000000; + int i, k; + for (i = 0; i < num_colors; ++i) { + int best_ix = i; + uint32_t best_score = ~0U; + for (k = i; k < num_colors; ++k) { + const uint32_t cur_score = PaletteColorDistance(palette[k], predict); + if (best_score > cur_score) { + best_score = cur_score; + best_ix = k; + } + } + SwapColor(&palette[best_ix], &palette[i]); + predict = palette[i]; + } +} + +// The palette has been sorted by alpha. This function checks if the other +// components of the palette have a monotonic development with regards to +// position in the palette. If all have monotonic development, there is +// no benefit to re-organize them greedily. A monotonic development +// would be spotted in green-only situations (like lossy alpha) or gray-scale +// images. +static int PaletteHasNonMonotonousDeltas(uint32_t palette[], int num_colors) { + uint32_t predict = 0x000000; + int i; + uint8_t sign_found = 0x00; + for (i = 0; i < num_colors; ++i) { + const uint32_t diff = VP8LSubPixels(palette[i], predict); + const uint8_t rd = (diff >> 16) & 0xff; + const uint8_t gd = (diff >> 8) & 0xff; + const uint8_t bd = (diff >> 0) & 0xff; + if (rd != 0x00) { + sign_found |= (rd < 0x80) ? 1 : 2; + } + if (gd != 0x00) { + sign_found |= (gd < 0x80) ? 8 : 16; + } + if (bd != 0x00) { + sign_found |= (bd < 0x80) ? 64 : 128; + } + predict = palette[i]; + } + return (sign_found & (sign_found << 1)) != 0; // two consequent signs. +} + +// ----------------------------------------------------------------------------- +// Palette + +// If number of colors in the image is less than or equal to MAX_PALETTE_SIZE, +// creates a palette and returns true, else returns false. +static int AnalyzeAndCreatePalette(const WebPPicture* const pic, + int low_effort, + uint32_t palette[MAX_PALETTE_SIZE], + int* const palette_size) { + const int num_colors = WebPGetColorPalette(pic, palette); + if (num_colors > MAX_PALETTE_SIZE) return 0; + *palette_size = num_colors; + qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort); + if (!low_effort && PaletteHasNonMonotonousDeltas(palette, num_colors)) { + GreedyMinimizeDeltas(palette, num_colors); + } + return 1; +} + +// These five modes are evaluated and their respective entropy is computed. +typedef enum { + kDirect = 0, + kSpatial = 1, + kSubGreen = 2, + kSpatialSubGreen = 3, + kPalette = 4, + kNumEntropyIx = 5 +} EntropyIx; + +typedef enum { + kHistoAlpha = 0, + kHistoAlphaPred, + kHistoGreen, + kHistoGreenPred, + kHistoRed, + kHistoRedPred, + kHistoBlue, + kHistoBluePred, + kHistoRedSubGreen, + kHistoRedPredSubGreen, + kHistoBlueSubGreen, + kHistoBluePredSubGreen, + kHistoPalette, + kHistoTotal // Must be last. +} HistoIx; + +static void AddSingleSubGreen(int p, uint32_t* const r, uint32_t* const b) { + const int green = p >> 8; // The upper bits are masked away later. + ++r[((p >> 16) - green) & 0xff]; + ++b[((p >> 0) - green) & 0xff]; +} + +static void AddSingle(uint32_t p, + uint32_t* const a, uint32_t* const r, + uint32_t* const g, uint32_t* const b) { + ++a[(p >> 24) & 0xff]; + ++r[(p >> 16) & 0xff]; + ++g[(p >> 8) & 0xff]; + ++b[(p >> 0) & 0xff]; +} + +static WEBP_INLINE uint32_t HashPix(uint32_t pix) { + // Note that masking with 0xffffffffu is for preventing an + // 'unsigned int overflow' warning. Doesn't impact the compiled code. + return ((((uint64_t)pix + (pix >> 19)) * 0x39c5fba7ull) & 0xffffffffu) >> 24; +} + +static int AnalyzeEntropy(const uint32_t* argb, + int width, int height, int argb_stride, + int use_palette, + EntropyIx* const min_entropy_ix, + int* const red_and_blue_always_zero) { + // Allocate histogram set with cache_bits = 0. + uint32_t* const histo = + (uint32_t*)WebPSafeCalloc(kHistoTotal, sizeof(*histo) * 256); + if (histo != NULL) { + int i, x, y; + const uint32_t* prev_row = argb; + const uint32_t* curr_row = argb + argb_stride; + for (y = 1; y < height; ++y) { + uint32_t prev_pix = curr_row[0]; + for (x = 1; x < width; ++x) { + const uint32_t pix = curr_row[x]; + const uint32_t pix_diff = VP8LSubPixels(pix, prev_pix); + if ((pix_diff == 0) || (pix == prev_row[x])) continue; + prev_pix = pix; + AddSingle(pix, + &histo[kHistoAlpha * 256], + &histo[kHistoRed * 256], + &histo[kHistoGreen * 256], + &histo[kHistoBlue * 256]); + AddSingle(pix_diff, + &histo[kHistoAlphaPred * 256], + &histo[kHistoRedPred * 256], + &histo[kHistoGreenPred * 256], + &histo[kHistoBluePred * 256]); + AddSingleSubGreen(pix, + &histo[kHistoRedSubGreen * 256], + &histo[kHistoBlueSubGreen * 256]); + AddSingleSubGreen(pix_diff, + &histo[kHistoRedPredSubGreen * 256], + &histo[kHistoBluePredSubGreen * 256]); + { + // Approximate the palette by the entropy of the multiplicative hash. + const uint32_t hash = HashPix(pix); + ++histo[kHistoPalette * 256 + hash]; + } + } + prev_row = curr_row; + curr_row += argb_stride; + } + { + double entropy_comp[kHistoTotal]; + double entropy[kNumEntropyIx]; + int k; + int last_mode_to_analyze = use_palette ? kPalette : kSpatialSubGreen; + int j; + // Let's add one zero to the predicted histograms. The zeros are removed + // too efficiently by the pix_diff == 0 comparison, at least one of the + // zeros is likely to exist. + ++histo[kHistoRedPredSubGreen * 256]; + ++histo[kHistoBluePredSubGreen * 256]; + ++histo[kHistoRedPred * 256]; + ++histo[kHistoGreenPred * 256]; + ++histo[kHistoBluePred * 256]; + ++histo[kHistoAlphaPred * 256]; + + for (j = 0; j < kHistoTotal; ++j) { + entropy_comp[j] = VP8LBitsEntropy(&histo[j * 256], 256, NULL); + } + entropy[kDirect] = entropy_comp[kHistoAlpha] + + entropy_comp[kHistoRed] + + entropy_comp[kHistoGreen] + + entropy_comp[kHistoBlue]; + entropy[kSpatial] = entropy_comp[kHistoAlphaPred] + + entropy_comp[kHistoRedPred] + + entropy_comp[kHistoGreenPred] + + entropy_comp[kHistoBluePred]; + entropy[kSubGreen] = entropy_comp[kHistoAlpha] + + entropy_comp[kHistoRedSubGreen] + + entropy_comp[kHistoGreen] + + entropy_comp[kHistoBlueSubGreen]; + entropy[kSpatialSubGreen] = entropy_comp[kHistoAlphaPred] + + entropy_comp[kHistoRedPredSubGreen] + + entropy_comp[kHistoGreenPred] + + entropy_comp[kHistoBluePredSubGreen]; + // Palette mode seems more efficient in a breakeven case. Bias with 1.0. + entropy[kPalette] = entropy_comp[kHistoPalette] - 1.0; + + *min_entropy_ix = kDirect; + for (k = kDirect + 1; k <= last_mode_to_analyze; ++k) { + if (entropy[*min_entropy_ix] > entropy[k]) { + *min_entropy_ix = (EntropyIx)k; + } + } + *red_and_blue_always_zero = 1; + // Let's check if the histogram of the chosen entropy mode has + // non-zero red and blue values. If all are zero, we can later skip + // the cross color optimization. + { + static const uint8_t kHistoPairs[5][2] = { + { kHistoRed, kHistoBlue }, + { kHistoRedPred, kHistoBluePred }, + { kHistoRedSubGreen, kHistoBlueSubGreen }, + { kHistoRedPredSubGreen, kHistoBluePredSubGreen }, + { kHistoRed, kHistoBlue } + }; + const uint32_t* const red_histo = + &histo[256 * kHistoPairs[*min_entropy_ix][0]]; + const uint32_t* const blue_histo = + &histo[256 * kHistoPairs[*min_entropy_ix][1]]; + for (i = 1; i < 256; ++i) { + if ((red_histo[i] | blue_histo[i]) != 0) { + *red_and_blue_always_zero = 0; + break; + } + } + } + } + WebPSafeFree(histo); + return 1; + } else { + return 0; + } +} + +static int GetHistoBits(int method, int use_palette, int width, int height) { + // Make tile size a function of encoding method (Range: 0 to 6). + int histo_bits = (use_palette ? 9 : 7) - method; + while (1) { + const int huff_image_size = VP8LSubSampleSize(width, histo_bits) * + VP8LSubSampleSize(height, histo_bits); + if (huff_image_size <= MAX_HUFF_IMAGE_SIZE) break; + ++histo_bits; + } + return (histo_bits < MIN_HUFFMAN_BITS) ? MIN_HUFFMAN_BITS : + (histo_bits > MAX_HUFFMAN_BITS) ? MAX_HUFFMAN_BITS : histo_bits; +} + +static int GetTransformBits(int method, int histo_bits) { + const int max_transform_bits = (method < 4) ? 6 : (method > 4) ? 4 : 5; + const int res = + (histo_bits > max_transform_bits) ? max_transform_bits : histo_bits; + assert(res <= MAX_TRANSFORM_BITS); + return res; +} + +static int AnalyzeAndInit(VP8LEncoder* const enc) { + const WebPPicture* const pic = enc->pic_; + const int width = pic->width; + const int height = pic->height; + const int pix_cnt = width * height; + const WebPConfig* const config = enc->config_; + const int method = config->method; + const int low_effort = (config->method == 0); + // we round the block size up, so we're guaranteed to have + // at max MAX_REFS_BLOCK_PER_IMAGE blocks used: + int refs_block_size = (pix_cnt - 1) / MAX_REFS_BLOCK_PER_IMAGE + 1; + assert(pic != NULL && pic->argb != NULL); + + enc->use_cross_color_ = 0; + enc->use_predict_ = 0; + enc->use_subtract_green_ = 0; + enc->use_palette_ = + AnalyzeAndCreatePalette(pic, low_effort, + enc->palette_, &enc->palette_size_); + + // TODO(jyrki): replace the decision to be based on an actual estimate + // of entropy, or even spatial variance of entropy. + enc->histo_bits_ = GetHistoBits(method, enc->use_palette_, + pic->width, pic->height); + enc->transform_bits_ = GetTransformBits(method, enc->histo_bits_); + + if (low_effort) { + // AnalyzeEntropy is somewhat slow. + enc->use_predict_ = !enc->use_palette_; + enc->use_subtract_green_ = !enc->use_palette_; + enc->use_cross_color_ = 0; + } else { + int red_and_blue_always_zero; + EntropyIx min_entropy_ix; + if (!AnalyzeEntropy(pic->argb, width, height, pic->argb_stride, + enc->use_palette_, &min_entropy_ix, + &red_and_blue_always_zero)) { + return 0; + } + enc->use_palette_ = (min_entropy_ix == kPalette); + enc->use_subtract_green_ = + (min_entropy_ix == kSubGreen) || (min_entropy_ix == kSpatialSubGreen); + enc->use_predict_ = + (min_entropy_ix == kSpatial) || (min_entropy_ix == kSpatialSubGreen); + enc->use_cross_color_ = red_and_blue_always_zero ? 0 : enc->use_predict_; + } + + if (!VP8LHashChainInit(&enc->hash_chain_, pix_cnt)) return 0; + + // palette-friendly input typically uses less literals + // -> reduce block size a bit + if (enc->use_palette_) refs_block_size /= 2; + VP8LBackwardRefsInit(&enc->refs_[0], refs_block_size); + VP8LBackwardRefsInit(&enc->refs_[1], refs_block_size); + + return 1; +} + +// Returns false in case of memory error. +static int GetHuffBitLengthsAndCodes( + const VP8LHistogramSet* const histogram_image, + HuffmanTreeCode* const huffman_codes) { + int i, k; + int ok = 0; + uint64_t total_length_size = 0; + uint8_t* mem_buf = NULL; + const int histogram_image_size = histogram_image->size; + int max_num_symbols = 0; + uint8_t* buf_rle = NULL; + HuffmanTree* huff_tree = NULL; + + // Iterate over all histograms and get the aggregate number of codes used. + for (i = 0; i < histogram_image_size; ++i) { + const VP8LHistogram* const histo = histogram_image->histograms[i]; + HuffmanTreeCode* const codes = &huffman_codes[5 * i]; + for (k = 0; k < 5; ++k) { + const int num_symbols = + (k == 0) ? VP8LHistogramNumCodes(histo->palette_code_bits_) : + (k == 4) ? NUM_DISTANCE_CODES : 256; + codes[k].num_symbols = num_symbols; + total_length_size += num_symbols; + } + } + + // Allocate and Set Huffman codes. + { + uint16_t* codes; + uint8_t* lengths; + mem_buf = (uint8_t*)WebPSafeCalloc(total_length_size, + sizeof(*lengths) + sizeof(*codes)); + if (mem_buf == NULL) goto End; + + codes = (uint16_t*)mem_buf; + lengths = (uint8_t*)&codes[total_length_size]; + for (i = 0; i < 5 * histogram_image_size; ++i) { + const int bit_length = huffman_codes[i].num_symbols; + huffman_codes[i].codes = codes; + huffman_codes[i].code_lengths = lengths; + codes += bit_length; + lengths += bit_length; + if (max_num_symbols < bit_length) { + max_num_symbols = bit_length; + } + } + } + + buf_rle = (uint8_t*)WebPSafeMalloc(1ULL, max_num_symbols); + huff_tree = (HuffmanTree*)WebPSafeMalloc(3ULL * max_num_symbols, + sizeof(*huff_tree)); + if (buf_rle == NULL || huff_tree == NULL) goto End; + + // Create Huffman trees. + for (i = 0; i < histogram_image_size; ++i) { + HuffmanTreeCode* const codes = &huffman_codes[5 * i]; + VP8LHistogram* const histo = histogram_image->histograms[i]; + VP8LCreateHuffmanTree(histo->literal_, 15, buf_rle, huff_tree, codes + 0); + VP8LCreateHuffmanTree(histo->red_, 15, buf_rle, huff_tree, codes + 1); + VP8LCreateHuffmanTree(histo->blue_, 15, buf_rle, huff_tree, codes + 2); + VP8LCreateHuffmanTree(histo->alpha_, 15, buf_rle, huff_tree, codes + 3); + VP8LCreateHuffmanTree(histo->distance_, 15, buf_rle, huff_tree, codes + 4); + } + ok = 1; + End: + WebPSafeFree(huff_tree); + WebPSafeFree(buf_rle); + if (!ok) { + WebPSafeFree(mem_buf); + memset(huffman_codes, 0, 5 * histogram_image_size * sizeof(*huffman_codes)); + } + return ok; +} + +static void StoreHuffmanTreeOfHuffmanTreeToBitMask( + VP8LBitWriter* const bw, const uint8_t* code_length_bitdepth) { + // RFC 1951 will calm you down if you are worried about this funny sequence. + // This sequence is tuned from that, but more weighted for lower symbol count, + // and more spiking histograms. + static const uint8_t kStorageOrder[CODE_LENGTH_CODES] = { + 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }; + int i; + // Throw away trailing zeros: + int codes_to_store = CODE_LENGTH_CODES; + for (; codes_to_store > 4; --codes_to_store) { + if (code_length_bitdepth[kStorageOrder[codes_to_store - 1]] != 0) { + break; + } + } + VP8LPutBits(bw, codes_to_store - 4, 4); + for (i = 0; i < codes_to_store; ++i) { + VP8LPutBits(bw, code_length_bitdepth[kStorageOrder[i]], 3); + } +} + +static void ClearHuffmanTreeIfOnlyOneSymbol( + HuffmanTreeCode* const huffman_code) { + int k; + int count = 0; + for (k = 0; k < huffman_code->num_symbols; ++k) { + if (huffman_code->code_lengths[k] != 0) { + ++count; + if (count > 1) return; + } + } + for (k = 0; k < huffman_code->num_symbols; ++k) { + huffman_code->code_lengths[k] = 0; + huffman_code->codes[k] = 0; + } +} + +static void StoreHuffmanTreeToBitMask( + VP8LBitWriter* const bw, + const HuffmanTreeToken* const tokens, const int num_tokens, + const HuffmanTreeCode* const huffman_code) { + int i; + for (i = 0; i < num_tokens; ++i) { + const int ix = tokens[i].code; + const int extra_bits = tokens[i].extra_bits; + VP8LPutBits(bw, huffman_code->codes[ix], huffman_code->code_lengths[ix]); + switch (ix) { + case 16: + VP8LPutBits(bw, extra_bits, 2); + break; + case 17: + VP8LPutBits(bw, extra_bits, 3); + break; + case 18: + VP8LPutBits(bw, extra_bits, 7); + break; + } + } +} + +// 'huff_tree' and 'tokens' are pre-alloacted buffers. +static void StoreFullHuffmanCode(VP8LBitWriter* const bw, + HuffmanTree* const huff_tree, + HuffmanTreeToken* const tokens, + const HuffmanTreeCode* const tree) { + uint8_t code_length_bitdepth[CODE_LENGTH_CODES] = { 0 }; + uint16_t code_length_bitdepth_symbols[CODE_LENGTH_CODES] = { 0 }; + const int max_tokens = tree->num_symbols; + int num_tokens; + HuffmanTreeCode huffman_code; + huffman_code.num_symbols = CODE_LENGTH_CODES; + huffman_code.code_lengths = code_length_bitdepth; + huffman_code.codes = code_length_bitdepth_symbols; + + VP8LPutBits(bw, 0, 1); + num_tokens = VP8LCreateCompressedHuffmanTree(tree, tokens, max_tokens); + { + uint32_t histogram[CODE_LENGTH_CODES] = { 0 }; + uint8_t buf_rle[CODE_LENGTH_CODES] = { 0 }; + int i; + for (i = 0; i < num_tokens; ++i) { + ++histogram[tokens[i].code]; + } + + VP8LCreateHuffmanTree(histogram, 7, buf_rle, huff_tree, &huffman_code); + } + + StoreHuffmanTreeOfHuffmanTreeToBitMask(bw, code_length_bitdepth); + ClearHuffmanTreeIfOnlyOneSymbol(&huffman_code); + { + int trailing_zero_bits = 0; + int trimmed_length = num_tokens; + int write_trimmed_length; + int length; + int i = num_tokens; + while (i-- > 0) { + const int ix = tokens[i].code; + if (ix == 0 || ix == 17 || ix == 18) { + --trimmed_length; // discount trailing zeros + trailing_zero_bits += code_length_bitdepth[ix]; + if (ix == 17) { + trailing_zero_bits += 3; + } else if (ix == 18) { + trailing_zero_bits += 7; + } + } else { + break; + } + } + write_trimmed_length = (trimmed_length > 1 && trailing_zero_bits > 12); + length = write_trimmed_length ? trimmed_length : num_tokens; + VP8LPutBits(bw, write_trimmed_length, 1); + if (write_trimmed_length) { + const int nbits = VP8LBitsLog2Ceiling(trimmed_length - 1); + const int nbitpairs = (nbits == 0) ? 1 : (nbits + 1) / 2; + VP8LPutBits(bw, nbitpairs - 1, 3); + assert(trimmed_length >= 2); + VP8LPutBits(bw, trimmed_length - 2, nbitpairs * 2); + } + StoreHuffmanTreeToBitMask(bw, tokens, length, &huffman_code); + } +} + +// 'huff_tree' and 'tokens' are pre-alloacted buffers. +static void StoreHuffmanCode(VP8LBitWriter* const bw, + HuffmanTree* const huff_tree, + HuffmanTreeToken* const tokens, + const HuffmanTreeCode* const huffman_code) { + int i; + int count = 0; + int symbols[2] = { 0, 0 }; + const int kMaxBits = 8; + const int kMaxSymbol = 1 << kMaxBits; + + // Check whether it's a small tree. + for (i = 0; i < huffman_code->num_symbols && count < 3; ++i) { + if (huffman_code->code_lengths[i] != 0) { + if (count < 2) symbols[count] = i; + ++count; + } + } + + if (count == 0) { // emit minimal tree for empty cases + // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 + VP8LPutBits(bw, 0x01, 4); + } else if (count <= 2 && symbols[0] < kMaxSymbol && symbols[1] < kMaxSymbol) { + VP8LPutBits(bw, 1, 1); // Small tree marker to encode 1 or 2 symbols. + VP8LPutBits(bw, count - 1, 1); + if (symbols[0] <= 1) { + VP8LPutBits(bw, 0, 1); // Code bit for small (1 bit) symbol value. + VP8LPutBits(bw, symbols[0], 1); + } else { + VP8LPutBits(bw, 1, 1); + VP8LPutBits(bw, symbols[0], 8); + } + if (count == 2) { + VP8LPutBits(bw, symbols[1], 8); + } + } else { + StoreFullHuffmanCode(bw, huff_tree, tokens, huffman_code); + } +} + +static WEBP_INLINE void WriteHuffmanCode(VP8LBitWriter* const bw, + const HuffmanTreeCode* const code, + int code_index) { + const int depth = code->code_lengths[code_index]; + const int symbol = code->codes[code_index]; + VP8LPutBits(bw, symbol, depth); +} + +static WEBP_INLINE void WriteHuffmanCodeWithExtraBits( + VP8LBitWriter* const bw, + const HuffmanTreeCode* const code, + int code_index, + int bits, + int n_bits) { + const int depth = code->code_lengths[code_index]; + const int symbol = code->codes[code_index]; + VP8LPutBits(bw, (bits << depth) | symbol, depth + n_bits); +} + +static WebPEncodingError StoreImageToBitMask( + VP8LBitWriter* const bw, int width, int histo_bits, + VP8LBackwardRefs* const refs, + const uint16_t* histogram_symbols, + const HuffmanTreeCode* const huffman_codes) { + const int histo_xsize = histo_bits ? VP8LSubSampleSize(width, histo_bits) : 1; + const int tile_mask = (histo_bits == 0) ? 0 : -(1 << histo_bits); + // x and y trace the position in the image. + int x = 0; + int y = 0; + int tile_x = x & tile_mask; + int tile_y = y & tile_mask; + int histogram_ix = histogram_symbols[0]; + const HuffmanTreeCode* codes = huffman_codes + 5 * histogram_ix; + VP8LRefsCursor c = VP8LRefsCursorInit(refs); + while (VP8LRefsCursorOk(&c)) { + const PixOrCopy* const v = c.cur_pos; + if ((tile_x != (x & tile_mask)) || (tile_y != (y & tile_mask))) { + tile_x = x & tile_mask; + tile_y = y & tile_mask; + histogram_ix = histogram_symbols[(y >> histo_bits) * histo_xsize + + (x >> histo_bits)]; + codes = huffman_codes + 5 * histogram_ix; + } + if (PixOrCopyIsLiteral(v)) { + static const int order[] = { 1, 2, 0, 3 }; + int k; + for (k = 0; k < 4; ++k) { + const int code = PixOrCopyLiteral(v, order[k]); + WriteHuffmanCode(bw, codes + k, code); + } + } else if (PixOrCopyIsCacheIdx(v)) { + const int code = PixOrCopyCacheIdx(v); + const int literal_ix = 256 + NUM_LENGTH_CODES + code; + WriteHuffmanCode(bw, codes, literal_ix); + } else { + int bits, n_bits; + int code; + + const int distance = PixOrCopyDistance(v); + VP8LPrefixEncode(v->len, &code, &n_bits, &bits); + WriteHuffmanCodeWithExtraBits(bw, codes, 256 + code, bits, n_bits); + + // Don't write the distance with the extra bits code since + // the distance can be up to 18 bits of extra bits, and the prefix + // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. + // TODO(jyrki): optimize this further. + VP8LPrefixEncode(distance, &code, &n_bits, &bits); + WriteHuffmanCode(bw, codes + 4, code); + VP8LPutBits(bw, bits, n_bits); + } + x += PixOrCopyLength(v); + while (x >= width) { + x -= width; + ++y; + } + VP8LRefsCursorNext(&c); + } + return bw->error_ ? VP8_ENC_ERROR_OUT_OF_MEMORY : VP8_ENC_OK; +} + +// Special case of EncodeImageInternal() for cache-bits=0, histo_bits=31 +static WebPEncodingError EncodeImageNoHuffman(VP8LBitWriter* const bw, + const uint32_t* const argb, + VP8LHashChain* const hash_chain, + VP8LBackwardRefs refs_array[2], + int width, int height, + int quality, int low_effort) { + int i; + int max_tokens = 0; + WebPEncodingError err = VP8_ENC_OK; + VP8LBackwardRefs* refs; + HuffmanTreeToken* tokens = NULL; + HuffmanTreeCode huffman_codes[5] = { { 0, NULL, NULL } }; + const uint16_t histogram_symbols[1] = { 0 }; // only one tree, one symbol + int cache_bits = 0; + VP8LHistogramSet* histogram_image = NULL; + HuffmanTree* const huff_tree = (HuffmanTree*)WebPSafeMalloc( + 3ULL * CODE_LENGTH_CODES, sizeof(*huff_tree)); + if (huff_tree == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + // Calculate backward references from ARGB image. + if (!VP8LHashChainFill(hash_chain, quality, argb, width, height, + low_effort)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + refs = VP8LGetBackwardReferences(width, height, argb, quality, 0, &cache_bits, + hash_chain, refs_array); + if (refs == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + histogram_image = VP8LAllocateHistogramSet(1, cache_bits); + if (histogram_image == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + // Build histogram image and symbols from backward references. + VP8LHistogramStoreRefs(refs, histogram_image->histograms[0]); + + // Create Huffman bit lengths and codes for each histogram image. + assert(histogram_image->size == 1); + if (!GetHuffBitLengthsAndCodes(histogram_image, huffman_codes)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + // No color cache, no Huffman image. + VP8LPutBits(bw, 0, 1); + + // Find maximum number of symbols for the huffman tree-set. + for (i = 0; i < 5; ++i) { + HuffmanTreeCode* const codes = &huffman_codes[i]; + if (max_tokens < codes->num_symbols) { + max_tokens = codes->num_symbols; + } + } + + tokens = (HuffmanTreeToken*)WebPSafeMalloc(max_tokens, sizeof(*tokens)); + if (tokens == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + // Store Huffman codes. + for (i = 0; i < 5; ++i) { + HuffmanTreeCode* const codes = &huffman_codes[i]; + StoreHuffmanCode(bw, huff_tree, tokens, codes); + ClearHuffmanTreeIfOnlyOneSymbol(codes); + } + + // Store actual literals. + err = StoreImageToBitMask(bw, width, 0, refs, histogram_symbols, + huffman_codes); + + Error: + WebPSafeFree(tokens); + WebPSafeFree(huff_tree); + VP8LFreeHistogramSet(histogram_image); + WebPSafeFree(huffman_codes[0].codes); + return err; +} + +static WebPEncodingError EncodeImageInternal(VP8LBitWriter* const bw, + const uint32_t* const argb, + VP8LHashChain* const hash_chain, + VP8LBackwardRefs refs_array[2], + int width, int height, int quality, + int low_effort, + int use_cache, int* cache_bits, + int histogram_bits, + size_t init_byte_position, + int* const hdr_size, + int* const data_size) { + WebPEncodingError err = VP8_ENC_OK; + const uint32_t histogram_image_xysize = + VP8LSubSampleSize(width, histogram_bits) * + VP8LSubSampleSize(height, histogram_bits); + VP8LHistogramSet* histogram_image = NULL; + VP8LHistogramSet* tmp_histos = NULL; + int histogram_image_size = 0; + size_t bit_array_size = 0; + HuffmanTree* huff_tree = NULL; + HuffmanTreeToken* tokens = NULL; + HuffmanTreeCode* huffman_codes = NULL; + VP8LBackwardRefs refs; + VP8LBackwardRefs* best_refs; + uint16_t* const histogram_symbols = + (uint16_t*)WebPSafeMalloc(histogram_image_xysize, + sizeof(*histogram_symbols)); + assert(histogram_bits >= MIN_HUFFMAN_BITS); + assert(histogram_bits <= MAX_HUFFMAN_BITS); + assert(hdr_size != NULL); + assert(data_size != NULL); + + VP8LBackwardRefsInit(&refs, refs_array[0].block_size_); + if (histogram_symbols == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + if (use_cache) { + // If the value is different from zero, it has been set during the + // palette analysis. + if (*cache_bits == 0) *cache_bits = MAX_COLOR_CACHE_BITS; + } else { + *cache_bits = 0; + } + // 'best_refs' is the reference to the best backward refs and points to one + // of refs_array[0] or refs_array[1]. + // Calculate backward references from ARGB image. + if (!VP8LHashChainFill(hash_chain, quality, argb, width, height, + low_effort)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + best_refs = VP8LGetBackwardReferences(width, height, argb, quality, + low_effort, cache_bits, hash_chain, + refs_array); + if (best_refs == NULL || !VP8LBackwardRefsCopy(best_refs, &refs)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + histogram_image = + VP8LAllocateHistogramSet(histogram_image_xysize, *cache_bits); + tmp_histos = VP8LAllocateHistogramSet(2, *cache_bits); + if (histogram_image == NULL || tmp_histos == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + // Build histogram image and symbols from backward references. + if (!VP8LGetHistoImageSymbols(width, height, &refs, quality, low_effort, + histogram_bits, *cache_bits, histogram_image, + tmp_histos, histogram_symbols)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + // Create Huffman bit lengths and codes for each histogram image. + histogram_image_size = histogram_image->size; + bit_array_size = 5 * histogram_image_size; + huffman_codes = (HuffmanTreeCode*)WebPSafeCalloc(bit_array_size, + sizeof(*huffman_codes)); + // Note: some histogram_image entries may point to tmp_histos[], so the latter + // need to outlive the following call to GetHuffBitLengthsAndCodes(). + if (huffman_codes == NULL || + !GetHuffBitLengthsAndCodes(histogram_image, huffman_codes)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + // Free combined histograms. + VP8LFreeHistogramSet(histogram_image); + histogram_image = NULL; + + // Free scratch histograms. + VP8LFreeHistogramSet(tmp_histos); + tmp_histos = NULL; + + // Color Cache parameters. + if (*cache_bits > 0) { + VP8LPutBits(bw, 1, 1); + VP8LPutBits(bw, *cache_bits, 4); + } else { + VP8LPutBits(bw, 0, 1); + } + + // Huffman image + meta huffman. + { + const int write_histogram_image = (histogram_image_size > 1); + VP8LPutBits(bw, write_histogram_image, 1); + if (write_histogram_image) { + uint32_t* const histogram_argb = + (uint32_t*)WebPSafeMalloc(histogram_image_xysize, + sizeof(*histogram_argb)); + int max_index = 0; + uint32_t i; + if (histogram_argb == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + for (i = 0; i < histogram_image_xysize; ++i) { + const int symbol_index = histogram_symbols[i] & 0xffff; + histogram_argb[i] = (symbol_index << 8); + if (symbol_index >= max_index) { + max_index = symbol_index + 1; + } + } + histogram_image_size = max_index; + + VP8LPutBits(bw, histogram_bits - 2, 3); + err = EncodeImageNoHuffman(bw, histogram_argb, hash_chain, refs_array, + VP8LSubSampleSize(width, histogram_bits), + VP8LSubSampleSize(height, histogram_bits), + quality, low_effort); + WebPSafeFree(histogram_argb); + if (err != VP8_ENC_OK) goto Error; + } + } + + // Store Huffman codes. + { + int i; + int max_tokens = 0; + huff_tree = (HuffmanTree*)WebPSafeMalloc(3ULL * CODE_LENGTH_CODES, + sizeof(*huff_tree)); + if (huff_tree == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + // Find maximum number of symbols for the huffman tree-set. + for (i = 0; i < 5 * histogram_image_size; ++i) { + HuffmanTreeCode* const codes = &huffman_codes[i]; + if (max_tokens < codes->num_symbols) { + max_tokens = codes->num_symbols; + } + } + tokens = (HuffmanTreeToken*)WebPSafeMalloc(max_tokens, + sizeof(*tokens)); + if (tokens == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + for (i = 0; i < 5 * histogram_image_size; ++i) { + HuffmanTreeCode* const codes = &huffman_codes[i]; + StoreHuffmanCode(bw, huff_tree, tokens, codes); + ClearHuffmanTreeIfOnlyOneSymbol(codes); + } + } + + *hdr_size = (int)(VP8LBitWriterNumBytes(bw) - init_byte_position); + // Store actual literals. + err = StoreImageToBitMask(bw, width, histogram_bits, &refs, + histogram_symbols, huffman_codes); + *data_size = + (int)(VP8LBitWriterNumBytes(bw) - init_byte_position - *hdr_size); + + Error: + WebPSafeFree(tokens); + WebPSafeFree(huff_tree); + VP8LFreeHistogramSet(histogram_image); + VP8LFreeHistogramSet(tmp_histos); + VP8LBackwardRefsClear(&refs); + if (huffman_codes != NULL) { + WebPSafeFree(huffman_codes->codes); + WebPSafeFree(huffman_codes); + } + WebPSafeFree(histogram_symbols); + return err; +} + +// ----------------------------------------------------------------------------- +// Transforms + +static void ApplySubtractGreen(VP8LEncoder* const enc, int width, int height, + VP8LBitWriter* const bw) { + VP8LPutBits(bw, TRANSFORM_PRESENT, 1); + VP8LPutBits(bw, SUBTRACT_GREEN, 2); + VP8LSubtractGreenFromBlueAndRed(enc->argb_, width * height); +} + +static WebPEncodingError ApplyPredictFilter(const VP8LEncoder* const enc, + int width, int height, + int quality, int low_effort, + int used_subtract_green, + VP8LBitWriter* const bw) { + const int pred_bits = enc->transform_bits_; + const int transform_width = VP8LSubSampleSize(width, pred_bits); + const int transform_height = VP8LSubSampleSize(height, pred_bits); + // we disable near-lossless quantization if palette is used. + const int near_lossless_strength = enc->use_palette_ ? 100 + : enc->config_->near_lossless; + + VP8LResidualImage(width, height, pred_bits, low_effort, enc->argb_, + enc->argb_scratch_, enc->transform_data_, + near_lossless_strength, enc->config_->exact, + used_subtract_green); + VP8LPutBits(bw, TRANSFORM_PRESENT, 1); + VP8LPutBits(bw, PREDICTOR_TRANSFORM, 2); + assert(pred_bits >= 2); + VP8LPutBits(bw, pred_bits - 2, 3); + return EncodeImageNoHuffman(bw, enc->transform_data_, + (VP8LHashChain*)&enc->hash_chain_, + (VP8LBackwardRefs*)enc->refs_, // cast const away + transform_width, transform_height, + quality, low_effort); +} + +static WebPEncodingError ApplyCrossColorFilter(const VP8LEncoder* const enc, + int width, int height, + int quality, int low_effort, + VP8LBitWriter* const bw) { + const int ccolor_transform_bits = enc->transform_bits_; + const int transform_width = VP8LSubSampleSize(width, ccolor_transform_bits); + const int transform_height = VP8LSubSampleSize(height, ccolor_transform_bits); + + VP8LColorSpaceTransform(width, height, ccolor_transform_bits, quality, + enc->argb_, enc->transform_data_); + VP8LPutBits(bw, TRANSFORM_PRESENT, 1); + VP8LPutBits(bw, CROSS_COLOR_TRANSFORM, 2); + assert(ccolor_transform_bits >= 2); + VP8LPutBits(bw, ccolor_transform_bits - 2, 3); + return EncodeImageNoHuffman(bw, enc->transform_data_, + (VP8LHashChain*)&enc->hash_chain_, + (VP8LBackwardRefs*)enc->refs_, // cast const away + transform_width, transform_height, + quality, low_effort); +} + +// ----------------------------------------------------------------------------- + +static WebPEncodingError WriteRiffHeader(const WebPPicture* const pic, + size_t riff_size, size_t vp8l_size) { + uint8_t riff[RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE + VP8L_SIGNATURE_SIZE] = { + 'R', 'I', 'F', 'F', 0, 0, 0, 0, 'W', 'E', 'B', 'P', + 'V', 'P', '8', 'L', 0, 0, 0, 0, VP8L_MAGIC_BYTE, + }; + PutLE32(riff + TAG_SIZE, (uint32_t)riff_size); + PutLE32(riff + RIFF_HEADER_SIZE + TAG_SIZE, (uint32_t)vp8l_size); + if (!pic->writer(riff, sizeof(riff), pic)) { + return VP8_ENC_ERROR_BAD_WRITE; + } + return VP8_ENC_OK; +} + +static int WriteImageSize(const WebPPicture* const pic, + VP8LBitWriter* const bw) { + const int width = pic->width - 1; + const int height = pic->height - 1; + assert(width < WEBP_MAX_DIMENSION && height < WEBP_MAX_DIMENSION); + + VP8LPutBits(bw, width, VP8L_IMAGE_SIZE_BITS); + VP8LPutBits(bw, height, VP8L_IMAGE_SIZE_BITS); + return !bw->error_; +} + +static int WriteRealAlphaAndVersion(VP8LBitWriter* const bw, int has_alpha) { + VP8LPutBits(bw, has_alpha, 1); + VP8LPutBits(bw, VP8L_VERSION, VP8L_VERSION_BITS); + return !bw->error_; +} + +static WebPEncodingError WriteImage(const WebPPicture* const pic, + VP8LBitWriter* const bw, + size_t* const coded_size) { + WebPEncodingError err = VP8_ENC_OK; + const uint8_t* const webpll_data = VP8LBitWriterFinish(bw); + const size_t webpll_size = VP8LBitWriterNumBytes(bw); + const size_t vp8l_size = VP8L_SIGNATURE_SIZE + webpll_size; + const size_t pad = vp8l_size & 1; + const size_t riff_size = TAG_SIZE + CHUNK_HEADER_SIZE + vp8l_size + pad; + + err = WriteRiffHeader(pic, riff_size, vp8l_size); + if (err != VP8_ENC_OK) goto Error; + + if (!pic->writer(webpll_data, webpll_size, pic)) { + err = VP8_ENC_ERROR_BAD_WRITE; + goto Error; + } + + if (pad) { + const uint8_t pad_byte[1] = { 0 }; + if (!pic->writer(pad_byte, 1, pic)) { + err = VP8_ENC_ERROR_BAD_WRITE; + goto Error; + } + } + *coded_size = CHUNK_HEADER_SIZE + riff_size; + return VP8_ENC_OK; + + Error: + return err; +} + +// ----------------------------------------------------------------------------- + +static void ClearTransformBuffer(VP8LEncoder* const enc) { + WebPSafeFree(enc->transform_mem_); + enc->transform_mem_ = NULL; + enc->transform_mem_size_ = 0; +} + +// Allocates the memory for argb (W x H) buffer, 2 rows of context for +// prediction and transform data. +// Flags influencing the memory allocated: +// enc->transform_bits_ +// enc->use_predict_, enc->use_cross_color_ +static WebPEncodingError AllocateTransformBuffer(VP8LEncoder* const enc, + int width, int height) { + WebPEncodingError err = VP8_ENC_OK; + const uint64_t image_size = width * height; + // VP8LResidualImage needs room for 2 scanlines of uint32 pixels with an extra + // pixel in each, plus 2 regular scanlines of bytes. + // TODO(skal): Clean up by using arithmetic in bytes instead of words. + const uint64_t argb_scratch_size = + enc->use_predict_ + ? (width + 1) * 2 + + (width * 2 + sizeof(uint32_t) - 1) / sizeof(uint32_t) + : 0; + const uint64_t transform_data_size = + (enc->use_predict_ || enc->use_cross_color_) + ? VP8LSubSampleSize(width, enc->transform_bits_) * + VP8LSubSampleSize(height, enc->transform_bits_) + : 0; + const uint64_t max_alignment_in_words = + (WEBP_ALIGN_CST + sizeof(uint32_t) - 1) / sizeof(uint32_t); + const uint64_t mem_size = + image_size + max_alignment_in_words + + argb_scratch_size + max_alignment_in_words + + transform_data_size; + uint32_t* mem = enc->transform_mem_; + if (mem == NULL || mem_size > enc->transform_mem_size_) { + ClearTransformBuffer(enc); + mem = (uint32_t*)WebPSafeMalloc(mem_size, sizeof(*mem)); + if (mem == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + enc->transform_mem_ = mem; + enc->transform_mem_size_ = (size_t)mem_size; + } + enc->argb_ = mem; + mem = (uint32_t*)WEBP_ALIGN(mem + image_size); + enc->argb_scratch_ = mem; + mem = (uint32_t*)WEBP_ALIGN(mem + argb_scratch_size); + enc->transform_data_ = mem; + + enc->current_width_ = width; + Error: + return err; +} + +static WebPEncodingError MakeInputImageCopy(VP8LEncoder* const enc) { + WebPEncodingError err = VP8_ENC_OK; + const WebPPicture* const picture = enc->pic_; + const int width = picture->width; + const int height = picture->height; + int y; + err = AllocateTransformBuffer(enc, width, height); + if (err != VP8_ENC_OK) return err; + for (y = 0; y < height; ++y) { + memcpy(enc->argb_ + y * width, + picture->argb + y * picture->argb_stride, + width * sizeof(*enc->argb_)); + } + assert(enc->current_width_ == width); + return VP8_ENC_OK; +} + +// ----------------------------------------------------------------------------- + +static WEBP_INLINE int SearchColorNoIdx(const uint32_t sorted[], uint32_t color, + int hi) { + int low = 0; + if (sorted[low] == color) return low; // loop invariant: sorted[low] != color + while (1) { + const int mid = (low + hi) >> 1; + if (sorted[mid] == color) { + return mid; + } else if (sorted[mid] < color) { + low = mid; + } else { + hi = mid; + } + } +} + +#define APPLY_PALETTE_GREEDY_MAX 4 + +static WEBP_INLINE uint32_t SearchColorGreedy(const uint32_t palette[], + int palette_size, + uint32_t color) { + (void)palette_size; + assert(palette_size < APPLY_PALETTE_GREEDY_MAX); + assert(3 == APPLY_PALETTE_GREEDY_MAX - 1); + if (color == palette[0]) return 0; + if (color == palette[1]) return 1; + if (color == palette[2]) return 2; + return 3; +} + +static WEBP_INLINE uint32_t ApplyPaletteHash0(uint32_t color) { + // Focus on the green color. + return (color >> 8) & 0xff; +} + +#define PALETTE_INV_SIZE_BITS 11 +#define PALETTE_INV_SIZE (1 << PALETTE_INV_SIZE_BITS) + +static WEBP_INLINE uint32_t ApplyPaletteHash1(uint32_t color) { + // Forget about alpha. + return ((color & 0x00ffffffu) * 4222244071u) >> (32 - PALETTE_INV_SIZE_BITS); +} + +static WEBP_INLINE uint32_t ApplyPaletteHash2(uint32_t color) { + // Forget about alpha. + return (color & 0x00ffffffu) * ((1u << 31) - 1) >> + (32 - PALETTE_INV_SIZE_BITS); +} + +// Sort palette in increasing order and prepare an inverse mapping array. +static void PrepareMapToPalette(const uint32_t palette[], int num_colors, + uint32_t sorted[], uint32_t idx_map[]) { + int i; + memcpy(sorted, palette, num_colors * sizeof(*sorted)); + qsort(sorted, num_colors, sizeof(*sorted), PaletteCompareColorsForQsort); + for (i = 0; i < num_colors; ++i) { + idx_map[SearchColorNoIdx(sorted, palette[i], num_colors)] = i; + } +} + +// Use 1 pixel cache for ARGB pixels. +#define APPLY_PALETTE_FOR(COLOR_INDEX) do { \ + uint32_t prev_pix = palette[0]; \ + uint32_t prev_idx = 0; \ + for (y = 0; y < height; ++y) { \ + for (x = 0; x < width; ++x) { \ + const uint32_t pix = src[x]; \ + if (pix != prev_pix) { \ + prev_idx = COLOR_INDEX; \ + prev_pix = pix; \ + } \ + tmp_row[x] = prev_idx; \ + } \ + VP8LBundleColorMap(tmp_row, width, xbits, dst); \ + src += src_stride; \ + dst += dst_stride; \ + } \ +} while (0) + +// Remap argb values in src[] to packed palettes entries in dst[] +// using 'row' as a temporary buffer of size 'width'. +// We assume that all src[] values have a corresponding entry in the palette. +// Note: src[] can be the same as dst[] +static WebPEncodingError ApplyPalette(const uint32_t* src, uint32_t src_stride, + uint32_t* dst, uint32_t dst_stride, + const uint32_t* palette, int palette_size, + int width, int height, int xbits) { + // TODO(skal): this tmp buffer is not needed if VP8LBundleColorMap() can be + // made to work in-place. + uint8_t* const tmp_row = (uint8_t*)WebPSafeMalloc(width, sizeof(*tmp_row)); + int x, y; + + if (tmp_row == NULL) return VP8_ENC_ERROR_OUT_OF_MEMORY; + + if (palette_size < APPLY_PALETTE_GREEDY_MAX) { + APPLY_PALETTE_FOR(SearchColorGreedy(palette, palette_size, pix)); + } else { + int i, j; + uint16_t buffer[PALETTE_INV_SIZE]; + uint32_t (*const hash_functions[])(uint32_t) = { + ApplyPaletteHash0, ApplyPaletteHash1, ApplyPaletteHash2 + }; + + // Try to find a perfect hash function able to go from a color to an index + // within 1 << PALETTE_INV_SIZE_BITS in order to build a hash map to go + // from color to index in palette. + for (i = 0; i < 3; ++i) { + int use_LUT = 1; + // Set each element in buffer to max uint16_t. + memset(buffer, 0xff, sizeof(buffer)); + for (j = 0; j < palette_size; ++j) { + const uint32_t ind = hash_functions[i](palette[j]); + if (buffer[ind] != 0xffffu) { + use_LUT = 0; + break; + } else { + buffer[ind] = j; + } + } + if (use_LUT) break; + } + + if (i == 0) { + APPLY_PALETTE_FOR(buffer[ApplyPaletteHash0(pix)]); + } else if (i == 1) { + APPLY_PALETTE_FOR(buffer[ApplyPaletteHash1(pix)]); + } else if (i == 2) { + APPLY_PALETTE_FOR(buffer[ApplyPaletteHash2(pix)]); + } else { + uint32_t idx_map[MAX_PALETTE_SIZE]; + uint32_t palette_sorted[MAX_PALETTE_SIZE]; + PrepareMapToPalette(palette, palette_size, palette_sorted, idx_map); + APPLY_PALETTE_FOR( + idx_map[SearchColorNoIdx(palette_sorted, pix, palette_size)]); + } + } + WebPSafeFree(tmp_row); + return VP8_ENC_OK; +} +#undef APPLY_PALETTE_FOR +#undef PALETTE_INV_SIZE_BITS +#undef PALETTE_INV_SIZE +#undef APPLY_PALETTE_GREEDY_MAX + +// Note: Expects "enc->palette_" to be set properly. +static WebPEncodingError MapImageFromPalette(VP8LEncoder* const enc, + int in_place) { + WebPEncodingError err = VP8_ENC_OK; + const WebPPicture* const pic = enc->pic_; + const int width = pic->width; + const int height = pic->height; + const uint32_t* const palette = enc->palette_; + const uint32_t* src = in_place ? enc->argb_ : pic->argb; + const int src_stride = in_place ? enc->current_width_ : pic->argb_stride; + const int palette_size = enc->palette_size_; + int xbits; + + // Replace each input pixel by corresponding palette index. + // This is done line by line. + if (palette_size <= 4) { + xbits = (palette_size <= 2) ? 3 : 2; + } else { + xbits = (palette_size <= 16) ? 1 : 0; + } + + err = AllocateTransformBuffer(enc, VP8LSubSampleSize(width, xbits), height); + if (err != VP8_ENC_OK) return err; + + err = ApplyPalette(src, src_stride, + enc->argb_, enc->current_width_, + palette, palette_size, width, height, xbits); + return err; +} + +// Save palette_[] to bitstream. +static WebPEncodingError EncodePalette(VP8LBitWriter* const bw, int low_effort, + VP8LEncoder* const enc) { + int i; + uint32_t tmp_palette[MAX_PALETTE_SIZE]; + const int palette_size = enc->palette_size_; + const uint32_t* const palette = enc->palette_; + VP8LPutBits(bw, TRANSFORM_PRESENT, 1); + VP8LPutBits(bw, COLOR_INDEXING_TRANSFORM, 2); + assert(palette_size >= 1 && palette_size <= MAX_PALETTE_SIZE); + VP8LPutBits(bw, palette_size - 1, 8); + for (i = palette_size - 1; i >= 1; --i) { + tmp_palette[i] = VP8LSubPixels(palette[i], palette[i - 1]); + } + tmp_palette[0] = palette[0]; + return EncodeImageNoHuffman(bw, tmp_palette, &enc->hash_chain_, enc->refs_, + palette_size, 1, 20 /* quality */, low_effort); +} + +#ifdef WEBP_EXPERIMENTAL_FEATURES + +static WebPEncodingError EncodeDeltaPalettePredictorImage( + VP8LBitWriter* const bw, VP8LEncoder* const enc, int quality, + int low_effort) { + const WebPPicture* const pic = enc->pic_; + const int width = pic->width; + const int height = pic->height; + + const int pred_bits = 5; + const int transform_width = VP8LSubSampleSize(width, pred_bits); + const int transform_height = VP8LSubSampleSize(height, pred_bits); + const int pred = 7; // default is Predictor7 (Top/Left Average) + const int tiles_per_row = VP8LSubSampleSize(width, pred_bits); + const int tiles_per_col = VP8LSubSampleSize(height, pred_bits); + uint32_t* predictors; + int tile_x, tile_y; + WebPEncodingError err = VP8_ENC_OK; + + predictors = (uint32_t*)WebPSafeMalloc(tiles_per_col * tiles_per_row, + sizeof(*predictors)); + if (predictors == NULL) return VP8_ENC_ERROR_OUT_OF_MEMORY; + + for (tile_y = 0; tile_y < tiles_per_col; ++tile_y) { + for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) { + predictors[tile_y * tiles_per_row + tile_x] = 0xff000000u | (pred << 8); + } + } + + VP8LPutBits(bw, TRANSFORM_PRESENT, 1); + VP8LPutBits(bw, PREDICTOR_TRANSFORM, 2); + VP8LPutBits(bw, pred_bits - 2, 3); + err = EncodeImageNoHuffman(bw, predictors, &enc->hash_chain_, + (VP8LBackwardRefs*)enc->refs_, // cast const away + transform_width, transform_height, + quality, low_effort); + WebPSafeFree(predictors); + return err; +} + +#endif // WEBP_EXPERIMENTAL_FEATURES + +// ----------------------------------------------------------------------------- +// VP8LEncoder + +static VP8LEncoder* VP8LEncoderNew(const WebPConfig* const config, + const WebPPicture* const picture) { + VP8LEncoder* const enc = (VP8LEncoder*)WebPSafeCalloc(1ULL, sizeof(*enc)); + if (enc == NULL) { + WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY); + return NULL; + } + enc->config_ = config; + enc->pic_ = picture; + + VP8LEncDspInit(); + + return enc; +} + +static void VP8LEncoderDelete(VP8LEncoder* enc) { + if (enc != NULL) { + VP8LHashChainClear(&enc->hash_chain_); + VP8LBackwardRefsClear(&enc->refs_[0]); + VP8LBackwardRefsClear(&enc->refs_[1]); + ClearTransformBuffer(enc); + WebPSafeFree(enc); + } +} + +// ----------------------------------------------------------------------------- +// Main call + +WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, + const WebPPicture* const picture, + VP8LBitWriter* const bw, int use_cache) { + WebPEncodingError err = VP8_ENC_OK; + const int quality = (int)config->quality; + const int low_effort = (config->method == 0); + const int width = picture->width; + const int height = picture->height; + VP8LEncoder* const enc = VP8LEncoderNew(config, picture); + const size_t byte_position = VP8LBitWriterNumBytes(bw); + int use_near_lossless = 0; + int hdr_size = 0; + int data_size = 0; + int use_delta_palette = 0; + + if (enc == NULL) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + // --------------------------------------------------------------------------- + // Analyze image (entropy, num_palettes etc) + + if (!AnalyzeAndInit(enc)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + // Apply near-lossless preprocessing. + use_near_lossless = + (config->near_lossless < 100) && !enc->use_palette_ && !enc->use_predict_; + if (use_near_lossless) { + if (!VP8ApplyNearLossless(width, height, picture->argb, + config->near_lossless)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + } + +#ifdef WEBP_EXPERIMENTAL_FEATURES + if (config->use_delta_palette) { + enc->use_predict_ = 1; + enc->use_cross_color_ = 0; + enc->use_subtract_green_ = 0; + enc->use_palette_ = 1; + err = MakeInputImageCopy(enc); + if (err != VP8_ENC_OK) goto Error; + err = WebPSearchOptimalDeltaPalette(enc); + if (err != VP8_ENC_OK) goto Error; + if (enc->use_palette_) { + err = AllocateTransformBuffer(enc, width, height); + if (err != VP8_ENC_OK) goto Error; + err = EncodeDeltaPalettePredictorImage(bw, enc, quality, low_effort); + if (err != VP8_ENC_OK) goto Error; + use_delta_palette = 1; + } + } +#endif // WEBP_EXPERIMENTAL_FEATURES + + // Encode palette + if (enc->use_palette_) { + err = EncodePalette(bw, low_effort, enc); + if (err != VP8_ENC_OK) goto Error; + err = MapImageFromPalette(enc, use_delta_palette); + if (err != VP8_ENC_OK) goto Error; + // If using a color cache, do not have it bigger than the number of colors. + if (use_cache && enc->palette_size_ < (1 << MAX_COLOR_CACHE_BITS)) { + enc->cache_bits_ = BitsLog2Floor(enc->palette_size_) + 1; + } + } + if (!use_delta_palette) { + // In case image is not packed. + if (enc->argb_ == NULL) { + err = MakeInputImageCopy(enc); + if (err != VP8_ENC_OK) goto Error; + } + + // ------------------------------------------------------------------------- + // Apply transforms and write transform data. + + if (enc->use_subtract_green_) { + ApplySubtractGreen(enc, enc->current_width_, height, bw); + } + + if (enc->use_predict_) { + err = ApplyPredictFilter(enc, enc->current_width_, height, quality, + low_effort, enc->use_subtract_green_, bw); + if (err != VP8_ENC_OK) goto Error; + } + + if (enc->use_cross_color_) { + err = ApplyCrossColorFilter(enc, enc->current_width_, + height, quality, low_effort, bw); + if (err != VP8_ENC_OK) goto Error; + } + } + + VP8LPutBits(bw, !TRANSFORM_PRESENT, 1); // No more transforms. + + // --------------------------------------------------------------------------- + // Encode and write the transformed image. + err = EncodeImageInternal(bw, enc->argb_, &enc->hash_chain_, enc->refs_, + enc->current_width_, height, quality, low_effort, + use_cache, &enc->cache_bits_, enc->histo_bits_, + byte_position, &hdr_size, &data_size); + if (err != VP8_ENC_OK) goto Error; + + if (picture->stats != NULL) { + WebPAuxStats* const stats = picture->stats; + stats->lossless_features = 0; + if (enc->use_predict_) stats->lossless_features |= 1; + if (enc->use_cross_color_) stats->lossless_features |= 2; + if (enc->use_subtract_green_) stats->lossless_features |= 4; + if (enc->use_palette_) stats->lossless_features |= 8; + stats->histogram_bits = enc->histo_bits_; + stats->transform_bits = enc->transform_bits_; + stats->cache_bits = enc->cache_bits_; + stats->palette_size = enc->palette_size_; + stats->lossless_size = (int)(VP8LBitWriterNumBytes(bw) - byte_position); + stats->lossless_hdr_size = hdr_size; + stats->lossless_data_size = data_size; + } + + Error: + VP8LEncoderDelete(enc); + return err; +} + +int VP8LEncodeImage(const WebPConfig* const config, + const WebPPicture* const picture) { + int width, height; + int has_alpha; + size_t coded_size; + int percent = 0; + int initial_size; + WebPEncodingError err = VP8_ENC_OK; + VP8LBitWriter bw; + + if (picture == NULL) return 0; + + if (config == NULL || picture->argb == NULL) { + err = VP8_ENC_ERROR_NULL_PARAMETER; + WebPEncodingSetError(picture, err); + return 0; + } + + width = picture->width; + height = picture->height; + // Initialize BitWriter with size corresponding to 16 bpp to photo images and + // 8 bpp for graphical images. + initial_size = (config->image_hint == WEBP_HINT_GRAPH) ? + width * height : width * height * 2; + if (!VP8LBitWriterInit(&bw, initial_size)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + if (!WebPReportProgress(picture, 1, &percent)) { + UserAbort: + err = VP8_ENC_ERROR_USER_ABORT; + goto Error; + } + // Reset stats (for pure lossless coding) + if (picture->stats != NULL) { + WebPAuxStats* const stats = picture->stats; + memset(stats, 0, sizeof(*stats)); + stats->PSNR[0] = 99.f; + stats->PSNR[1] = 99.f; + stats->PSNR[2] = 99.f; + stats->PSNR[3] = 99.f; + stats->PSNR[4] = 99.f; + } + + // Write image size. + if (!WriteImageSize(picture, &bw)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + has_alpha = WebPPictureHasTransparency(picture); + // Write the non-trivial Alpha flag and lossless version. + if (!WriteRealAlphaAndVersion(&bw, has_alpha)) { + err = VP8_ENC_ERROR_OUT_OF_MEMORY; + goto Error; + } + + if (!WebPReportProgress(picture, 5, &percent)) goto UserAbort; + + // Encode main image stream. + err = VP8LEncodeStream(config, picture, &bw, 1 /*use_cache*/); + if (err != VP8_ENC_OK) goto Error; + + // TODO(skal): have a fine-grained progress report in VP8LEncodeStream(). + if (!WebPReportProgress(picture, 90, &percent)) goto UserAbort; + + // Finish the RIFF chunk. + err = WriteImage(picture, &bw, &coded_size); + if (err != VP8_ENC_OK) goto Error; + + if (!WebPReportProgress(picture, 100, &percent)) goto UserAbort; + + // Save size. + if (picture->stats != NULL) { + picture->stats->coded_size += (int)coded_size; + picture->stats->lossless_size = (int)coded_size; + } + + if (picture->extra_info != NULL) { + const int mb_w = (width + 15) >> 4; + const int mb_h = (height + 15) >> 4; + memset(picture->extra_info, 0, mb_w * mb_h * sizeof(*picture->extra_info)); + } + + Error: + if (bw.error_) err = VP8_ENC_ERROR_OUT_OF_MEMORY; + VP8LBitWriterWipeOut(&bw); + if (err != VP8_ENC_OK) { + WebPEncodingSetError(picture, err); + return 0; + } + return 1; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/enc/vp8li.h b/3rdparty/libwebp/enc/vp8li.h deleted file mode 100644 index 01f01f57e5..0000000000 --- a/3rdparty/libwebp/enc/vp8li.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Lossless encoder: internal header. -// -// Author: Vikas Arora (vikaas.arora@gmail.com) - -#ifndef WEBP_ENC_VP8LI_H_ -#define WEBP_ENC_VP8LI_H_ - -#include "./histogram.h" -#include "../utils/bit_writer.h" -#include "../webp/encode.h" -#include "../webp/format_constants.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -typedef struct { - const WebPConfig* config_; // user configuration and parameters - const WebPPicture* pic_; // input picture. - - uint32_t* argb_; // Transformed argb image data. - uint32_t* argb_scratch_; // Scratch memory for argb rows - // (used for prediction). - uint32_t* transform_data_; // Scratch memory for transform data. - int current_width_; // Corresponds to packed image width. - - // Encoding parameters derived from quality parameter. - int histo_bits_; - int transform_bits_; - int cache_bits_; // If equal to 0, don't use color cache. - - // Encoding parameters derived from image characteristics. - int use_cross_color_; - int use_subtract_green_; - int use_predict_; - int use_palette_; - int palette_size_; - uint32_t palette_[MAX_PALETTE_SIZE]; -} VP8LEncoder; - -//------------------------------------------------------------------------------ -// internal functions. Not public. - -// Encodes the picture. -// Returns 0 if config or picture is NULL or picture doesn't have valid argb -// input. -int VP8LEncodeImage(const WebPConfig* const config, - const WebPPicture* const picture); - -// Encodes the main image stream using the supplied bit writer. -WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, - const WebPPicture* const picture, - VP8LBitWriter* const bw); - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif - -#endif /* WEBP_ENC_VP8LI_H_ */ diff --git a/3rdparty/libwebp/enc/vp8li_enc.h b/3rdparty/libwebp/enc/vp8li_enc.h new file mode 100644 index 0000000000..8c5fbcbb2e --- /dev/null +++ b/3rdparty/libwebp/enc/vp8li_enc.h @@ -0,0 +1,95 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Lossless encoder: internal header. +// +// Author: Vikas Arora (vikaas.arora@gmail.com) + +#ifndef WEBP_ENC_VP8LI_H_ +#define WEBP_ENC_VP8LI_H_ + +#include "./backward_references_enc.h" +#include "./histogram_enc.h" +#include "../utils/bit_writer_utils.h" +#include "../webp/encode.h" +#include "../webp/format_constants.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// maximum value of transform_bits_ in VP8LEncoder. +#define MAX_TRANSFORM_BITS 6 + +typedef struct { + const WebPConfig* config_; // user configuration and parameters + const WebPPicture* pic_; // input picture. + + uint32_t* argb_; // Transformed argb image data. + uint32_t* argb_scratch_; // Scratch memory for argb rows + // (used for prediction). + uint32_t* transform_data_; // Scratch memory for transform data. + uint32_t* transform_mem_; // Currently allocated memory. + size_t transform_mem_size_; // Currently allocated memory size. + + int current_width_; // Corresponds to packed image width. + + // Encoding parameters derived from quality parameter. + int histo_bits_; + int transform_bits_; // <= MAX_TRANSFORM_BITS. + int cache_bits_; // If equal to 0, don't use color cache. + + // Encoding parameters derived from image characteristics. + int use_cross_color_; + int use_subtract_green_; + int use_predict_; + int use_palette_; + int palette_size_; + uint32_t palette_[MAX_PALETTE_SIZE]; + + // Some 'scratch' (potentially large) objects. + struct VP8LBackwardRefs refs_[2]; // Backward Refs array corresponding to + // LZ77 & RLE coding. + VP8LHashChain hash_chain_; // HashChain data for constructing + // backward references. +} VP8LEncoder; + +//------------------------------------------------------------------------------ +// internal functions. Not public. + +// Encodes the picture. +// Returns 0 if config or picture is NULL or picture doesn't have valid argb +// input. +int VP8LEncodeImage(const WebPConfig* const config, + const WebPPicture* const picture); + +// Encodes the main image stream using the supplied bit writer. +// If 'use_cache' is false, disables the use of color cache. +WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, + const WebPPicture* const picture, + VP8LBitWriter* const bw, int use_cache); + +//------------------------------------------------------------------------------ +// Image transforms in predictor.c. + +void VP8LResidualImage(int width, int height, int bits, int low_effort, + uint32_t* const argb, uint32_t* const argb_scratch, + uint32_t* const image, int near_lossless, int exact, + int used_subtract_green); + +void VP8LColorSpaceTransform(int width, int height, int bits, int quality, + uint32_t* const argb, uint32_t* image); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_ENC_VP8LI_H_ */ diff --git a/3rdparty/libwebp/enc/webpenc.c b/3rdparty/libwebp/enc/webp_enc.c similarity index 69% rename from 3rdparty/libwebp/enc/webpenc.c rename to 3rdparty/libwebp/enc/webp_enc.c index d420d063ea..f18461ef92 100644 --- a/3rdparty/libwebp/enc/webpenc.c +++ b/3rdparty/libwebp/enc/webp_enc.c @@ -16,16 +16,13 @@ #include #include -#include "./vp8enci.h" -#include "./vp8li.h" +#include "./cost_enc.h" +#include "./vp8i_enc.h" +#include "./vp8li_enc.h" #include "../utils/utils.h" // #define PRINT_MEMORY_INFO -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - #ifdef PRINT_MEMORY_INFO #include #endif @@ -36,44 +33,19 @@ int WebPGetEncoderVersion(void) { return (ENC_MAJ_VERSION << 16) | (ENC_MIN_VERSION << 8) | ENC_REV_VERSION; } -//------------------------------------------------------------------------------ -// WebPPicture -//------------------------------------------------------------------------------ - -static int DummyWriter(const uint8_t* data, size_t data_size, - const WebPPicture* const picture) { - // The following are to prevent 'unused variable' error message. - (void)data; - (void)data_size; - (void)picture; - return 1; -} - -int WebPPictureInitInternal(WebPPicture* picture, int version) { - if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_ENCODER_ABI_VERSION)) { - return 0; // caller/system version mismatch! - } - if (picture != NULL) { - memset(picture, 0, sizeof(*picture)); - picture->writer = DummyWriter; - WebPEncodingSetError(picture, VP8_ENC_OK); - } - return 1; -} - //------------------------------------------------------------------------------ // VP8Encoder //------------------------------------------------------------------------------ static void ResetSegmentHeader(VP8Encoder* const enc) { - VP8SegmentHeader* const hdr = &enc->segment_hdr_; + VP8EncSegmentHeader* const hdr = &enc->segment_hdr_; hdr->num_segments_ = enc->config_->segments; hdr->update_map_ = (hdr->num_segments_ > 1); hdr->size_ = 0; } static void ResetFilterHeader(VP8Encoder* const enc) { - VP8FilterHeader* const hdr = &enc->filter_hdr_; + VP8EncFilterHeader* const hdr = &enc->filter_hdr_; hdr->simple_ = 1; hdr->level_ = 0; hdr->sharpness_ = 0; @@ -103,11 +75,13 @@ static void ResetBoundaryPredictions(VP8Encoder* const enc) { //-------------------+---+---+---+---+---+---+---+ // dynamic proba | ~ | x | x | x | x | x | x | //-------------------+---+---+---+---+---+---+---+ -// fast mode analysis| | | | | x | x | x | +// fast mode analysis|[x]|[x]| | | x | x | x | //-------------------+---+---+---+---+---+---+---+ // basic rd-opt | | | | x | x | x | x | //-------------------+---+---+---+---+---+---+---+ -// disto-score i4/16 | | | x | | | | | +// disto-refine i4/16| x | x | x | | | | | +//-------------------+---+---+---+---+---+---+---+ +// disto-refine uv | | x | x | | | | | //-------------------+---+---+---+---+---+---+---+ // rd-opt i4/16 | | | ~ | x | x | x | x | //-------------------+---+---+---+---+---+---+---+ @@ -131,12 +105,16 @@ static void MapConfigToTools(VP8Encoder* const enc) { 256 * 16 * 16 * // upper bound: up to 16bit per 4x4 block (limit * limit) / (100 * 100); // ... modulated with a quadratic curve. + // partition0 = 512k max. + enc->mb_header_limit_ = + (score_t)256 * 510 * 8 * 1024 / (enc->mb_w_ * enc->mb_h_); + enc->thread_level_ = config->thread_level; enc->do_search_ = (config->target_size > 0 || config->target_PSNR > 0); if (!config->low_memory) { #if !defined(DISABLE_TOKEN_BUFFER) - enc->use_tokens_ = (method >= 3) && !enc->do_search_; + enc->use_tokens_ = (enc->rd_opt_level_ >= RD_OPT_BASIC); // need rd stats #endif if (enc->use_tokens_) { enc->num_parts_ = 1; // doesn't work with multi-partition @@ -147,75 +125,69 @@ static void MapConfigToTools(VP8Encoder* const enc) { // Memory scaling with dimensions: // memory (bytes) ~= 2.25 * w + 0.0625 * w * h // -// Typical memory footprint (768x510 picture) -// Memory used: -// encoder: 33919 -// block cache: 2880 -// info: 3072 -// preds: 24897 -// top samples: 1623 -// non-zero: 196 -// lf-stats: 2048 -// total: 68635 -// Transcient object sizes: -// VP8EncIterator: 352 -// VP8ModeScore: 912 -// VP8SegmentInfo: 532 -// VP8Proba: 31032 +// Typical memory footprint (614x440 picture) +// encoder: 22111 +// info: 4368 +// preds: 17741 +// top samples: 1263 +// non-zero: 175 +// lf-stats: 0 +// total: 45658 +// Transient object sizes: +// VP8EncIterator: 3360 +// VP8ModeScore: 872 +// VP8SegmentInfo: 732 +// VP8EncProba: 18352 // LFStats: 2048 -// Picture size (yuv): 589824 +// Picture size (yuv): 419328 static VP8Encoder* InitVP8Encoder(const WebPConfig* const config, WebPPicture* const picture) { + VP8Encoder* enc; const int use_filter = (config->filter_strength > 0) || (config->autofilter > 0); const int mb_w = (picture->width + 15) >> 4; const int mb_h = (picture->height + 15) >> 4; const int preds_w = 4 * mb_w + 1; const int preds_h = 4 * mb_h + 1; - const size_t preds_size = preds_w * preds_h * sizeof(uint8_t); + const size_t preds_size = preds_w * preds_h * sizeof(*enc->preds_); const int top_stride = mb_w * 16; - const size_t nz_size = (mb_w + 1) * sizeof(uint32_t); - const size_t cache_size = (3 * YUV_SIZE + PRED_SIZE) * sizeof(uint8_t); - const size_t info_size = mb_w * mb_h * sizeof(VP8MBInfo); - const size_t samples_size = (2 * top_stride + // top-luma/u/v - 16 + 16 + 16 + 8 + 1 + // left y/u/v - 2 * ALIGN_CST) // align all - * sizeof(uint8_t); + const size_t nz_size = (mb_w + 1) * sizeof(*enc->nz_) + WEBP_ALIGN_CST; + const size_t info_size = mb_w * mb_h * sizeof(*enc->mb_info_); + const size_t samples_size = + 2 * top_stride * sizeof(*enc->y_top_) // top-luma/u/v + + WEBP_ALIGN_CST; // align all const size_t lf_stats_size = - config->autofilter ? sizeof(LFStats) + ALIGN_CST : 0; - VP8Encoder* enc; + config->autofilter ? sizeof(*enc->lf_stats_) + WEBP_ALIGN_CST : 0; uint8_t* mem; - const uint64_t size = (uint64_t)sizeof(VP8Encoder) // main struct - + ALIGN_CST // cache alignment - + cache_size // working caches - + info_size // modes info - + preds_size // prediction modes - + samples_size // top/left samples - + nz_size // coeff context bits - + lf_stats_size; // autofilter stats + const uint64_t size = (uint64_t)sizeof(*enc) // main struct + + WEBP_ALIGN_CST // cache alignment + + info_size // modes info + + preds_size // prediction modes + + samples_size // top/left samples + + nz_size // coeff context bits + + lf_stats_size; // autofilter stats #ifdef PRINT_MEMORY_INFO printf("===================================\n"); printf("Memory used:\n" " encoder: %ld\n" - " block cache: %ld\n" " info: %ld\n" " preds: %ld\n" " top samples: %ld\n" " non-zero: %ld\n" " lf-stats: %ld\n" " total: %ld\n", - sizeof(VP8Encoder) + ALIGN_CST, cache_size, info_size, + sizeof(*enc) + WEBP_ALIGN_CST, info_size, preds_size, samples_size, nz_size, lf_stats_size, size); - printf("Transcient object sizes:\n" + printf("Transient object sizes:\n" " VP8EncIterator: %ld\n" " VP8ModeScore: %ld\n" " VP8SegmentInfo: %ld\n" - " VP8Proba: %ld\n" + " VP8EncProba: %ld\n" " LFStats: %ld\n", sizeof(VP8EncIterator), sizeof(VP8ModeScore), - sizeof(VP8SegmentInfo), sizeof(VP8Proba), + sizeof(VP8SegmentInfo), sizeof(VP8EncProba), sizeof(LFStats)); printf("Picture size (yuv): %ld\n", mb_w * mb_h * 384 * sizeof(uint8_t)); @@ -227,41 +199,27 @@ static VP8Encoder* InitVP8Encoder(const WebPConfig* const config, return NULL; } enc = (VP8Encoder*)mem; - mem = (uint8_t*)DO_ALIGN(mem + sizeof(*enc)); + mem = (uint8_t*)WEBP_ALIGN(mem + sizeof(*enc)); memset(enc, 0, sizeof(*enc)); enc->num_parts_ = 1 << config->partitions; enc->mb_w_ = mb_w; enc->mb_h_ = mb_h; enc->preds_w_ = preds_w; - enc->yuv_in_ = (uint8_t*)mem; - mem += YUV_SIZE; - enc->yuv_out_ = (uint8_t*)mem; - mem += YUV_SIZE; - enc->yuv_out2_ = (uint8_t*)mem; - mem += YUV_SIZE; - enc->yuv_p_ = (uint8_t*)mem; - mem += PRED_SIZE; enc->mb_info_ = (VP8MBInfo*)mem; mem += info_size; enc->preds_ = ((uint8_t*)mem) + 1 + enc->preds_w_; - mem += preds_w * preds_h * sizeof(uint8_t); - enc->nz_ = 1 + (uint32_t*)mem; + mem += preds_size; + enc->nz_ = 1 + (uint32_t*)WEBP_ALIGN(mem); mem += nz_size; - enc->lf_stats_ = lf_stats_size ? (LFStats*)DO_ALIGN(mem) : NULL; + enc->lf_stats_ = lf_stats_size ? (LFStats*)WEBP_ALIGN(mem) : NULL; mem += lf_stats_size; // top samples (all 16-aligned) - mem = (uint8_t*)DO_ALIGN(mem); + mem = (uint8_t*)WEBP_ALIGN(mem); enc->y_top_ = (uint8_t*)mem; enc->uv_top_ = enc->y_top_ + top_stride; mem += 2 * top_stride; - mem = (uint8_t*)DO_ALIGN(mem + 1); - enc->y_left_ = (uint8_t*)mem; - mem += 16 + 16; - enc->u_left_ = (uint8_t*)mem; - mem += 16; - enc->v_left_ = (uint8_t*)mem; - mem += 8; + assert(mem <= (uint8_t*)enc + size); enc->config_ = config; enc->profile_ = use_filter ? ((config->filter_type == 1) ? 0 : 1) : 2; @@ -274,13 +232,15 @@ static VP8Encoder* InitVP8Encoder(const WebPConfig* const config, ResetSegmentHeader(enc); ResetFilterHeader(enc); ResetBoundaryPredictions(enc); - + VP8EncDspCostInit(); VP8EncInitAlpha(enc); -#ifdef WEBP_EXPERIMENTAL_FEATURES - VP8EncInitLayer(enc); -#endif - VP8TBufferInit(&enc->tokens_); + // lower quality means smaller output -> we modulate a little the page + // size based on quality. This is just a crude 1rst-order prediction. + { + const float scale = 1.f + config->quality * 5.f / 100.f; // in [1,6] + VP8TBufferInit(&enc->tokens_, (int)(mb_w * mb_h * 4 * scale)); + } return enc; } @@ -288,11 +248,8 @@ static int DeleteVP8Encoder(VP8Encoder* enc) { int ok = 1; if (enc != NULL) { ok = VP8EncDeleteAlpha(enc); -#ifdef WEBP_EXPERIMENTAL_FEATURES - VP8EncDeleteLayer(enc); -#endif VP8TBufferClear(&enc->tokens_); - free(enc); + WebPSafeFree(enc); } return ok; } @@ -300,7 +257,7 @@ static int DeleteVP8Encoder(VP8Encoder* enc) { //------------------------------------------------------------------------------ static double GetPSNR(uint64_t err, uint64_t size) { - return err ? 10. * log10(255. * 255. * size / err) : 99.; + return (err > 0 && size > 0) ? 10. * log10(255. * 255. * size / err) : 99.; } static void FinalizePSNR(const VP8Encoder* const enc) { @@ -358,26 +315,50 @@ int WebPReportProgress(const WebPPicture* const pic, int WebPEncode(const WebPConfig* config, WebPPicture* pic) { int ok = 0; + if (pic == NULL) return 0; - if (pic == NULL) - return 0; WebPEncodingSetError(pic, VP8_ENC_OK); // all ok so far - if (config == NULL) // bad params + if (config == NULL) { // bad params return WebPEncodingSetError(pic, VP8_ENC_ERROR_NULL_PARAMETER); - if (!WebPValidateConfig(config)) + } + if (!WebPValidateConfig(config)) { return WebPEncodingSetError(pic, VP8_ENC_ERROR_INVALID_CONFIGURATION); - if (pic->width <= 0 || pic->height <= 0) + } + if (pic->width <= 0 || pic->height <= 0) { return WebPEncodingSetError(pic, VP8_ENC_ERROR_BAD_DIMENSION); - if (pic->width > WEBP_MAX_DIMENSION || pic->height > WEBP_MAX_DIMENSION) + } + if (pic->width > WEBP_MAX_DIMENSION || pic->height > WEBP_MAX_DIMENSION) { return WebPEncodingSetError(pic, VP8_ENC_ERROR_BAD_DIMENSION); + } if (pic->stats != NULL) memset(pic->stats, 0, sizeof(*pic->stats)); if (!config->lossless) { VP8Encoder* enc = NULL; - if (pic->y == NULL || pic->u == NULL || pic->v == NULL) { + + if (!config->exact) { + WebPCleanupTransparentArea(pic); + } + + if (pic->use_argb || pic->y == NULL || pic->u == NULL || pic->v == NULL) { // Make sure we have YUVA samples. - if (!WebPPictureARGBToYUVA(pic, WEBP_YUV420)) return 0; + if (config->use_sharp_yuv || (config->preprocessing & 4)) { + if (!WebPPictureSharpARGBToYUVA(pic)) { + return 0; + } + } else { + float dithering = 0.f; + if (config->preprocessing & 2) { + const float x = config->quality / 100.f; + const float x2 = x * x; + // slowly decreasing from max dithering at low quality (q->0) + // to 0.5 dithering amplitude at high quality (q->100) + dithering = 1.0f + (0.5f - 1.0f) * x2 * x2; + } + if (!WebPPictureARGBToYUVADithered(pic, WEBP_YUV420, dithering)) { + return 0; + } + } } enc = InitVP8Encoder(config, pic); @@ -393,9 +374,6 @@ int WebPEncode(const WebPConfig* config, WebPPicture* pic) { ok = ok && VP8EncTokenLoop(enc); } ok = ok && VP8EncFinishAlpha(enc); -#ifdef WEBP_EXPERIMENTAL_FEATURES - ok = ok && VP8EncFinishLayer(enc); -#endif ok = ok && VP8EncWrite(enc); StoreStats(enc); @@ -409,12 +387,12 @@ int WebPEncode(const WebPConfig* config, WebPPicture* pic) { return 0; } + if (!config->exact) { + WebPCleanupTransparentAreaLossless(pic); + } + ok = VP8LEncodeImage(config, pic); // Sets pic->error in case of problem. } return ok; } - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/mux/Makefile.am b/3rdparty/libwebp/mux/Makefile.am new file mode 100644 index 0000000000..47b1fbe12b --- /dev/null +++ b/3rdparty/libwebp/mux/Makefile.am @@ -0,0 +1,19 @@ +lib_LTLIBRARIES = libwebpmux.la + +libwebpmux_la_SOURCES = +libwebpmux_la_SOURCES += anim_encode.c +libwebpmux_la_SOURCES += animi.h +libwebpmux_la_SOURCES += muxedit.c +libwebpmux_la_SOURCES += muxi.h +libwebpmux_la_SOURCES += muxinternal.c +libwebpmux_la_SOURCES += muxread.c + +libwebpmuxinclude_HEADERS = +libwebpmuxinclude_HEADERS += ../webp/mux.h +libwebpmuxinclude_HEADERS += ../webp/mux_types.h +libwebpmuxinclude_HEADERS += ../webp/types.h + +libwebpmux_la_LIBADD = ../libwebp.la +libwebpmux_la_LDFLAGS = -no-undefined -version-info 3:0:0 -lm +libwebpmuxincludedir = $(includedir)/webp +pkgconfig_DATA = libwebpmux.pc diff --git a/3rdparty/libwebp/mux/anim_encode.c b/3rdparty/libwebp/mux/anim_encode.c new file mode 100644 index 0000000000..6066388727 --- /dev/null +++ b/3rdparty/libwebp/mux/anim_encode.c @@ -0,0 +1,1579 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// AnimEncoder implementation. +// + +#include +#include +#include // for pow() +#include +#include // for abs() + +#include "../mux/animi.h" +#include "../utils/utils.h" +#include "../webp/decode.h" +#include "../webp/encode.h" +#include "../webp/format_constants.h" +#include "../webp/mux.h" + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define snprintf _snprintf +#endif + +#define ERROR_STR_MAX_LENGTH 100 + +//------------------------------------------------------------------------------ +// Internal structs. + +// Stores frame rectangle dimensions. +typedef struct { + int x_offset_, y_offset_, width_, height_; +} FrameRect; + +// Used to store two candidates of encoded data for an animation frame. One of +// the two will be chosen later. +typedef struct { + WebPMuxFrameInfo sub_frame_; // Encoded frame rectangle. + WebPMuxFrameInfo key_frame_; // Encoded frame if it is a key-frame. + int is_key_frame_; // True if 'key_frame' has been chosen. +} EncodedFrame; + +struct WebPAnimEncoder { + const int canvas_width_; // Canvas width. + const int canvas_height_; // Canvas height. + const WebPAnimEncoderOptions options_; // Global encoding options. + + FrameRect prev_rect_; // Previous WebP frame rectangle. + WebPConfig last_config_; // Cached in case a re-encode is needed. + WebPConfig last_config_reversed_; // If 'last_config_' uses lossless, then + // this config uses lossy and vice versa; + // only valid if 'options_.allow_mixed' + // is true. + + WebPPicture* curr_canvas_; // Only pointer; we don't own memory. + + // Canvas buffers. + WebPPicture curr_canvas_copy_; // Possibly modified current canvas. + int curr_canvas_copy_modified_; // True if pixels in 'curr_canvas_copy_' + // differ from those in 'curr_canvas_'. + + WebPPicture prev_canvas_; // Previous canvas. + WebPPicture prev_canvas_disposed_; // Previous canvas disposed to background. + + // Encoded data. + EncodedFrame* encoded_frames_; // Array of encoded frames. + size_t size_; // Number of allocated frames. + size_t start_; // Frame start index. + size_t count_; // Number of valid frames. + size_t flush_count_; // If >0, 'flush_count' frames starting from + // 'start' are ready to be added to mux. + + // key-frame related. + int64_t best_delta_; // min(canvas size - frame size) over the frames. + // Can be negative in certain cases due to + // transparent pixels in a frame. + int keyframe_; // Index of selected key-frame relative to 'start_'. + int count_since_key_frame_; // Frames seen since the last key-frame. + + int first_timestamp_; // Timestamp of the first frame. + int prev_timestamp_; // Timestamp of the last added frame. + int prev_candidate_undecided_; // True if it's not yet decided if previous + // frame would be a sub-frame or a key-frame. + + // Misc. + int is_first_frame_; // True if first frame is yet to be added/being added. + int got_null_frame_; // True if WebPAnimEncoderAdd() has already been called + // with a NULL frame. + + size_t in_frame_count_; // Number of input frames processed so far. + size_t out_frame_count_; // Number of frames added to mux so far. This may be + // different from 'in_frame_count_' due to merging. + + WebPMux* mux_; // Muxer to assemble the WebP bitstream. + char error_str_[ERROR_STR_MAX_LENGTH]; // Error string. Empty if no error. +}; + +// ----------------------------------------------------------------------------- +// Life of WebPAnimEncoder object. + +#define DELTA_INFINITY (1ULL << 32) +#define KEYFRAME_NONE (-1) + +// Reset the counters in the WebPAnimEncoder. +static void ResetCounters(WebPAnimEncoder* const enc) { + enc->start_ = 0; + enc->count_ = 0; + enc->flush_count_ = 0; + enc->best_delta_ = DELTA_INFINITY; + enc->keyframe_ = KEYFRAME_NONE; +} + +static void DisableKeyframes(WebPAnimEncoderOptions* const enc_options) { + enc_options->kmax = INT_MAX; + enc_options->kmin = enc_options->kmax - 1; +} + +#define MAX_CACHED_FRAMES 30 + +static void SanitizeEncoderOptions(WebPAnimEncoderOptions* const enc_options) { + int print_warning = enc_options->verbose; + + if (enc_options->minimize_size) { + DisableKeyframes(enc_options); + } + + if (enc_options->kmax == 1) { // All frames will be key-frames. + enc_options->kmin = 0; + enc_options->kmax = 0; + return; + } else if (enc_options->kmax <= 0) { + DisableKeyframes(enc_options); + print_warning = 0; + } + + if (enc_options->kmin >= enc_options->kmax) { + enc_options->kmin = enc_options->kmax - 1; + if (print_warning) { + fprintf(stderr, "WARNING: Setting kmin = %d, so that kmin < kmax.\n", + enc_options->kmin); + } + } else { + const int kmin_limit = enc_options->kmax / 2 + 1; + if (enc_options->kmin < kmin_limit && kmin_limit < enc_options->kmax) { + // This ensures that enc.keyframe + kmin >= kmax is always true. So, we + // can flush all the frames in the 'count_since_key_frame == kmax' case. + enc_options->kmin = kmin_limit; + if (print_warning) { + fprintf(stderr, + "WARNING: Setting kmin = %d, so that kmin >= kmax / 2 + 1.\n", + enc_options->kmin); + } + } + } + // Limit the max number of frames that are allocated. + if (enc_options->kmax - enc_options->kmin > MAX_CACHED_FRAMES) { + enc_options->kmin = enc_options->kmax - MAX_CACHED_FRAMES; + if (print_warning) { + fprintf(stderr, + "WARNING: Setting kmin = %d, so that kmax - kmin <= %d.\n", + enc_options->kmin, MAX_CACHED_FRAMES); + } + } + assert(enc_options->kmin < enc_options->kmax); +} + +#undef MAX_CACHED_FRAMES + +static void DefaultEncoderOptions(WebPAnimEncoderOptions* const enc_options) { + enc_options->anim_params.loop_count = 0; + enc_options->anim_params.bgcolor = 0xffffffff; // White. + enc_options->minimize_size = 0; + DisableKeyframes(enc_options); + enc_options->allow_mixed = 0; + enc_options->verbose = 0; +} + +int WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions* enc_options, + int abi_version) { + if (enc_options == NULL || + WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) { + return 0; + } + DefaultEncoderOptions(enc_options); + return 1; +} + +// This starting value is more fit to WebPCleanupTransparentAreaLossless(). +#define TRANSPARENT_COLOR 0x00000000 + +static void ClearRectangle(WebPPicture* const picture, + int left, int top, int width, int height) { + int j; + for (j = top; j < top + height; ++j) { + uint32_t* const dst = picture->argb + j * picture->argb_stride; + int i; + for (i = left; i < left + width; ++i) { + dst[i] = TRANSPARENT_COLOR; + } + } +} + +static void WebPUtilClearPic(WebPPicture* const picture, + const FrameRect* const rect) { + if (rect != NULL) { + ClearRectangle(picture, rect->x_offset_, rect->y_offset_, + rect->width_, rect->height_); + } else { + ClearRectangle(picture, 0, 0, picture->width, picture->height); + } +} + +static void MarkNoError(WebPAnimEncoder* const enc) { + enc->error_str_[0] = '\0'; // Empty string. +} + +static void MarkError(WebPAnimEncoder* const enc, const char* str) { + if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s.", str) < 0) { + assert(0); // FIX ME! + } +} + +static void MarkError2(WebPAnimEncoder* const enc, + const char* str, int error_code) { + if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s: %d.", str, + error_code) < 0) { + assert(0); // FIX ME! + } +} + +WebPAnimEncoder* WebPAnimEncoderNewInternal( + int width, int height, const WebPAnimEncoderOptions* enc_options, + int abi_version) { + WebPAnimEncoder* enc; + + if (WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) { + return NULL; + } + if (width <= 0 || height <= 0 || + (width * (uint64_t)height) >= MAX_IMAGE_AREA) { + return NULL; + } + + enc = (WebPAnimEncoder*)WebPSafeCalloc(1, sizeof(*enc)); + if (enc == NULL) return NULL; + // sanity inits, so we can call WebPAnimEncoderDelete(): + enc->encoded_frames_ = NULL; + enc->mux_ = NULL; + MarkNoError(enc); + + // Dimensions and options. + *(int*)&enc->canvas_width_ = width; + *(int*)&enc->canvas_height_ = height; + if (enc_options != NULL) { + *(WebPAnimEncoderOptions*)&enc->options_ = *enc_options; + SanitizeEncoderOptions((WebPAnimEncoderOptions*)&enc->options_); + } else { + DefaultEncoderOptions((WebPAnimEncoderOptions*)&enc->options_); + } + + // Canvas buffers. + if (!WebPPictureInit(&enc->curr_canvas_copy_) || + !WebPPictureInit(&enc->prev_canvas_) || + !WebPPictureInit(&enc->prev_canvas_disposed_)) { + goto Err; + } + enc->curr_canvas_copy_.width = width; + enc->curr_canvas_copy_.height = height; + enc->curr_canvas_copy_.use_argb = 1; + if (!WebPPictureAlloc(&enc->curr_canvas_copy_) || + !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_) || + !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_disposed_)) { + goto Err; + } + WebPUtilClearPic(&enc->prev_canvas_, NULL); + enc->curr_canvas_copy_modified_ = 1; + + // Encoded frames. + ResetCounters(enc); + // Note: one extra storage is for the previous frame. + enc->size_ = enc->options_.kmax - enc->options_.kmin + 1; + // We need space for at least 2 frames. But when kmin, kmax are both zero, + // enc->size_ will be 1. So we handle that special case below. + if (enc->size_ < 2) enc->size_ = 2; + enc->encoded_frames_ = + (EncodedFrame*)WebPSafeCalloc(enc->size_, sizeof(*enc->encoded_frames_)); + if (enc->encoded_frames_ == NULL) goto Err; + + enc->mux_ = WebPMuxNew(); + if (enc->mux_ == NULL) goto Err; + + enc->count_since_key_frame_ = 0; + enc->first_timestamp_ = 0; + enc->prev_timestamp_ = 0; + enc->prev_candidate_undecided_ = 0; + enc->is_first_frame_ = 1; + enc->got_null_frame_ = 0; + + return enc; // All OK. + + Err: + WebPAnimEncoderDelete(enc); + return NULL; +} + +// Release the data contained by 'encoded_frame'. +static void FrameRelease(EncodedFrame* const encoded_frame) { + if (encoded_frame != NULL) { + WebPDataClear(&encoded_frame->sub_frame_.bitstream); + WebPDataClear(&encoded_frame->key_frame_.bitstream); + memset(encoded_frame, 0, sizeof(*encoded_frame)); + } +} + +void WebPAnimEncoderDelete(WebPAnimEncoder* enc) { + if (enc != NULL) { + WebPPictureFree(&enc->curr_canvas_copy_); + WebPPictureFree(&enc->prev_canvas_); + WebPPictureFree(&enc->prev_canvas_disposed_); + if (enc->encoded_frames_ != NULL) { + size_t i; + for (i = 0; i < enc->size_; ++i) { + FrameRelease(&enc->encoded_frames_[i]); + } + WebPSafeFree(enc->encoded_frames_); + } + WebPMuxDelete(enc->mux_); + WebPSafeFree(enc); + } +} + +// ----------------------------------------------------------------------------- +// Frame addition. + +// Returns cached frame at the given 'position'. +static EncodedFrame* GetFrame(const WebPAnimEncoder* const enc, + size_t position) { + assert(enc->start_ + position < enc->size_); + return &enc->encoded_frames_[enc->start_ + position]; +} + +typedef int (*ComparePixelsFunc)(const uint32_t*, int, const uint32_t*, int, + int, int); + +// Returns true if 'length' number of pixels in 'src' and 'dst' are equal, +// assuming the given step sizes between pixels. +// 'max_allowed_diff' is unused and only there to allow function pointer use. +static WEBP_INLINE int ComparePixelsLossless(const uint32_t* src, int src_step, + const uint32_t* dst, int dst_step, + int length, int max_allowed_diff) { + (void)max_allowed_diff; + assert(length > 0); + while (length-- > 0) { + if (*src != *dst) { + return 0; + } + src += src_step; + dst += dst_step; + } + return 1; +} + +// Helper to check if each channel in 'src' and 'dst' is at most off by +// 'max_allowed_diff'. +static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst, + int max_allowed_diff) { + const int src_a = (src >> 24) & 0xff; + const int src_r = (src >> 16) & 0xff; + const int src_g = (src >> 8) & 0xff; + const int src_b = (src >> 0) & 0xff; + const int dst_a = (dst >> 24) & 0xff; + const int dst_r = (dst >> 16) & 0xff; + const int dst_g = (dst >> 8) & 0xff; + const int dst_b = (dst >> 0) & 0xff; + + return (src_a == dst_a) && + (abs(src_r - dst_r) * dst_a <= (max_allowed_diff * 255)) && + (abs(src_g - dst_g) * dst_a <= (max_allowed_diff * 255)) && + (abs(src_b - dst_b) * dst_a <= (max_allowed_diff * 255)); +} + +// Returns true if 'length' number of pixels in 'src' and 'dst' are within an +// error bound, assuming the given step sizes between pixels. +static WEBP_INLINE int ComparePixelsLossy(const uint32_t* src, int src_step, + const uint32_t* dst, int dst_step, + int length, int max_allowed_diff) { + assert(length > 0); + while (length-- > 0) { + if (!PixelsAreSimilar(*src, *dst, max_allowed_diff)) { + return 0; + } + src += src_step; + dst += dst_step; + } + return 1; +} + +static int IsEmptyRect(const FrameRect* const rect) { + return (rect->width_ == 0) || (rect->height_ == 0); +} + +static int QualityToMaxDiff(float quality) { + const double val = pow(quality / 100., 0.5); + const double max_diff = 31 * (1 - val) + 1 * val; + return (int)(max_diff + 0.5); +} + +// Assumes that an initial valid guess of change rectangle 'rect' is passed. +static void MinimizeChangeRectangle(const WebPPicture* const src, + const WebPPicture* const dst, + FrameRect* const rect, + int is_lossless, float quality) { + int i, j; + const ComparePixelsFunc compare_pixels = + is_lossless ? ComparePixelsLossless : ComparePixelsLossy; + const int max_allowed_diff_lossy = QualityToMaxDiff(quality); + const int max_allowed_diff = is_lossless ? 0 : max_allowed_diff_lossy; + + // Sanity checks. + assert(src->width == dst->width && src->height == dst->height); + assert(rect->x_offset_ + rect->width_ <= dst->width); + assert(rect->y_offset_ + rect->height_ <= dst->height); + + // Left boundary. + for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) { + const uint32_t* const src_argb = + &src->argb[rect->y_offset_ * src->argb_stride + i]; + const uint32_t* const dst_argb = + &dst->argb[rect->y_offset_ * dst->argb_stride + i]; + if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride, + rect->height_, max_allowed_diff)) { + --rect->width_; // Redundant column. + ++rect->x_offset_; + } else { + break; + } + } + if (rect->width_ == 0) goto NoChange; + + // Right boundary. + for (i = rect->x_offset_ + rect->width_ - 1; i >= rect->x_offset_; --i) { + const uint32_t* const src_argb = + &src->argb[rect->y_offset_ * src->argb_stride + i]; + const uint32_t* const dst_argb = + &dst->argb[rect->y_offset_ * dst->argb_stride + i]; + if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride, + rect->height_, max_allowed_diff)) { + --rect->width_; // Redundant column. + } else { + break; + } + } + if (rect->width_ == 0) goto NoChange; + + // Top boundary. + for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) { + const uint32_t* const src_argb = + &src->argb[j * src->argb_stride + rect->x_offset_]; + const uint32_t* const dst_argb = + &dst->argb[j * dst->argb_stride + rect->x_offset_]; + if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_, + max_allowed_diff)) { + --rect->height_; // Redundant row. + ++rect->y_offset_; + } else { + break; + } + } + if (rect->height_ == 0) goto NoChange; + + // Bottom boundary. + for (j = rect->y_offset_ + rect->height_ - 1; j >= rect->y_offset_; --j) { + const uint32_t* const src_argb = + &src->argb[j * src->argb_stride + rect->x_offset_]; + const uint32_t* const dst_argb = + &dst->argb[j * dst->argb_stride + rect->x_offset_]; + if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_, + max_allowed_diff)) { + --rect->height_; // Redundant row. + } else { + break; + } + } + if (rect->height_ == 0) goto NoChange; + + if (IsEmptyRect(rect)) { + NoChange: + rect->x_offset_ = 0; + rect->y_offset_ = 0; + rect->width_ = 0; + rect->height_ = 0; + } +} + +// Snap rectangle to even offsets (and adjust dimensions if needed). +static WEBP_INLINE void SnapToEvenOffsets(FrameRect* const rect) { + rect->width_ += (rect->x_offset_ & 1); + rect->height_ += (rect->y_offset_ & 1); + rect->x_offset_ &= ~1; + rect->y_offset_ &= ~1; +} + +typedef struct { + int should_try_; // Should try this set of parameters. + int empty_rect_allowed_; // Frame with empty rectangle can be skipped. + FrameRect rect_ll_; // Frame rectangle for lossless compression. + WebPPicture sub_frame_ll_; // Sub-frame pic for lossless compression. + FrameRect rect_lossy_; // Frame rectangle for lossy compression. + // Could be smaller than rect_ll_ as pixels + // with small diffs can be ignored. + WebPPicture sub_frame_lossy_; // Sub-frame pic for lossless compression. +} SubFrameParams; + +static int SubFrameParamsInit(SubFrameParams* const params, + int should_try, int empty_rect_allowed) { + params->should_try_ = should_try; + params->empty_rect_allowed_ = empty_rect_allowed; + if (!WebPPictureInit(¶ms->sub_frame_ll_) || + !WebPPictureInit(¶ms->sub_frame_lossy_)) { + return 0; + } + return 1; +} + +static void SubFrameParamsFree(SubFrameParams* const params) { + WebPPictureFree(¶ms->sub_frame_ll_); + WebPPictureFree(¶ms->sub_frame_lossy_); +} + +// Given previous and current canvas, picks the optimal rectangle for the +// current frame based on 'is_lossless' and other parameters. Assumes that the +// initial guess 'rect' is valid. +static int GetSubRect(const WebPPicture* const prev_canvas, + const WebPPicture* const curr_canvas, int is_key_frame, + int is_first_frame, int empty_rect_allowed, + int is_lossless, float quality, FrameRect* const rect, + WebPPicture* const sub_frame) { + if (!is_key_frame || is_first_frame) { // Optimize frame rectangle. + // Note: This behaves as expected for first frame, as 'prev_canvas' is + // initialized to a fully transparent canvas in the beginning. + MinimizeChangeRectangle(prev_canvas, curr_canvas, rect, + is_lossless, quality); + } + + if (IsEmptyRect(rect)) { + if (empty_rect_allowed) { // No need to get 'sub_frame'. + return 1; + } else { // Force a 1x1 rectangle. + rect->width_ = 1; + rect->height_ = 1; + assert(rect->x_offset_ == 0); + assert(rect->y_offset_ == 0); + } + } + + SnapToEvenOffsets(rect); + return WebPPictureView(curr_canvas, rect->x_offset_, rect->y_offset_, + rect->width_, rect->height_, sub_frame); +} + +// Picks optimal frame rectangle for both lossless and lossy compression. The +// initial guess for frame rectangles will be the full canvas. +static int GetSubRects(const WebPPicture* const prev_canvas, + const WebPPicture* const curr_canvas, int is_key_frame, + int is_first_frame, float quality, + SubFrameParams* const params) { + // Lossless frame rectangle. + params->rect_ll_.x_offset_ = 0; + params->rect_ll_.y_offset_ = 0; + params->rect_ll_.width_ = curr_canvas->width; + params->rect_ll_.height_ = curr_canvas->height; + if (!GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame, + params->empty_rect_allowed_, 1, quality, + ¶ms->rect_ll_, ¶ms->sub_frame_ll_)) { + return 0; + } + // Lossy frame rectangle. + params->rect_lossy_ = params->rect_ll_; // seed with lossless rect. + return GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame, + params->empty_rect_allowed_, 0, quality, + ¶ms->rect_lossy_, ¶ms->sub_frame_lossy_); +} + +static WEBP_INLINE int clip(int v, int min_v, int max_v) { + return (v < min_v) ? min_v : (v > max_v) ? max_v : v; +} + +int WebPAnimEncoderRefineRect( + const WebPPicture* const prev_canvas, const WebPPicture* const curr_canvas, + int is_lossless, float quality, int* const x_offset, int* const y_offset, + int* const width, int* const height) { + FrameRect rect; + const int right = clip(*x_offset + *width, 0, curr_canvas->width); + const int left = clip(*x_offset, 0, curr_canvas->width - 1); + const int bottom = clip(*y_offset + *height, 0, curr_canvas->height); + const int top = clip(*y_offset, 0, curr_canvas->height - 1); + if (prev_canvas == NULL || curr_canvas == NULL || + prev_canvas->width != curr_canvas->width || + prev_canvas->height != curr_canvas->height || + !prev_canvas->use_argb || !curr_canvas->use_argb) { + return 0; + } + rect.x_offset_ = left; + rect.y_offset_ = top; + rect.width_ = clip(right - left, 0, curr_canvas->width - rect.x_offset_); + rect.height_ = clip(bottom - top, 0, curr_canvas->height - rect.y_offset_); + MinimizeChangeRectangle(prev_canvas, curr_canvas, &rect, is_lossless, + quality); + SnapToEvenOffsets(&rect); + *x_offset = rect.x_offset_; + *y_offset = rect.y_offset_; + *width = rect.width_; + *height = rect.height_; + return 1; +} + +static void DisposeFrameRectangle(int dispose_method, + const FrameRect* const rect, + WebPPicture* const curr_canvas) { + assert(rect != NULL); + if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { + WebPUtilClearPic(curr_canvas, rect); + } +} + +static uint32_t RectArea(const FrameRect* const rect) { + return (uint32_t)rect->width_ * rect->height_; +} + +static int IsLosslessBlendingPossible(const WebPPicture* const src, + const WebPPicture* const dst, + const FrameRect* const rect) { + int i, j; + assert(src->width == dst->width && src->height == dst->height); + assert(rect->x_offset_ + rect->width_ <= dst->width); + assert(rect->y_offset_ + rect->height_ <= dst->height); + for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) { + for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) { + const uint32_t src_pixel = src->argb[j * src->argb_stride + i]; + const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i]; + const uint32_t dst_alpha = dst_pixel >> 24; + if (dst_alpha != 0xff && src_pixel != dst_pixel) { + // In this case, if we use blending, we can't attain the desired + // 'dst_pixel' value for this pixel. So, blending is not possible. + return 0; + } + } + } + return 1; +} + +static int IsLossyBlendingPossible(const WebPPicture* const src, + const WebPPicture* const dst, + const FrameRect* const rect, + float quality) { + const int max_allowed_diff_lossy = QualityToMaxDiff(quality); + int i, j; + assert(src->width == dst->width && src->height == dst->height); + assert(rect->x_offset_ + rect->width_ <= dst->width); + assert(rect->y_offset_ + rect->height_ <= dst->height); + for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) { + for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) { + const uint32_t src_pixel = src->argb[j * src->argb_stride + i]; + const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i]; + const uint32_t dst_alpha = dst_pixel >> 24; + if (dst_alpha != 0xff && + !PixelsAreSimilar(src_pixel, dst_pixel, max_allowed_diff_lossy)) { + // In this case, if we use blending, we can't attain the desired + // 'dst_pixel' value for this pixel. So, blending is not possible. + return 0; + } + } + } + return 1; +} + +// For pixels in 'rect', replace those pixels in 'dst' that are same as 'src' by +// transparent pixels. +// Returns true if at least one pixel gets modified. +static int IncreaseTransparency(const WebPPicture* const src, + const FrameRect* const rect, + WebPPicture* const dst) { + int i, j; + int modified = 0; + assert(src != NULL && dst != NULL && rect != NULL); + assert(src->width == dst->width && src->height == dst->height); + for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) { + const uint32_t* const psrc = src->argb + j * src->argb_stride; + uint32_t* const pdst = dst->argb + j * dst->argb_stride; + for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) { + if (psrc[i] == pdst[i] && pdst[i] != TRANSPARENT_COLOR) { + pdst[i] = TRANSPARENT_COLOR; + modified = 1; + } + } + } + return modified; +} + +#undef TRANSPARENT_COLOR + +// Replace similar blocks of pixels by a 'see-through' transparent block +// with uniform average color. +// Assumes lossy compression is being used. +// Returns true if at least one pixel gets modified. +static int FlattenSimilarBlocks(const WebPPicture* const src, + const FrameRect* const rect, + WebPPicture* const dst, float quality) { + const int max_allowed_diff_lossy = QualityToMaxDiff(quality); + int i, j; + int modified = 0; + const int block_size = 8; + const int y_start = (rect->y_offset_ + block_size) & ~(block_size - 1); + const int y_end = (rect->y_offset_ + rect->height_) & ~(block_size - 1); + const int x_start = (rect->x_offset_ + block_size) & ~(block_size - 1); + const int x_end = (rect->x_offset_ + rect->width_) & ~(block_size - 1); + assert(src != NULL && dst != NULL && rect != NULL); + assert(src->width == dst->width && src->height == dst->height); + assert((block_size & (block_size - 1)) == 0); // must be a power of 2 + // Iterate over each block and count similar pixels. + for (j = y_start; j < y_end; j += block_size) { + for (i = x_start; i < x_end; i += block_size) { + int cnt = 0; + int avg_r = 0, avg_g = 0, avg_b = 0; + int x, y; + const uint32_t* const psrc = src->argb + j * src->argb_stride + i; + uint32_t* const pdst = dst->argb + j * dst->argb_stride + i; + for (y = 0; y < block_size; ++y) { + for (x = 0; x < block_size; ++x) { + const uint32_t src_pixel = psrc[x + y * src->argb_stride]; + const int alpha = src_pixel >> 24; + if (alpha == 0xff && + PixelsAreSimilar(src_pixel, pdst[x + y * dst->argb_stride], + max_allowed_diff_lossy)) { + ++cnt; + avg_r += (src_pixel >> 16) & 0xff; + avg_g += (src_pixel >> 8) & 0xff; + avg_b += (src_pixel >> 0) & 0xff; + } + } + } + // If we have a fully similar block, we replace it with an + // average transparent block. This compresses better in lossy mode. + if (cnt == block_size * block_size) { + const uint32_t color = (0x00 << 24) | + ((avg_r / cnt) << 16) | + ((avg_g / cnt) << 8) | + ((avg_b / cnt) << 0); + for (y = 0; y < block_size; ++y) { + for (x = 0; x < block_size; ++x) { + pdst[x + y * dst->argb_stride] = color; + } + } + modified = 1; + } + } + } + return modified; +} + +static int EncodeFrame(const WebPConfig* const config, WebPPicture* const pic, + WebPMemoryWriter* const memory) { + pic->use_argb = 1; + pic->writer = WebPMemoryWrite; + pic->custom_ptr = memory; + if (!WebPEncode(config, pic)) { + return 0; + } + return 1; +} + +// Struct representing a candidate encoded frame including its metadata. +typedef struct { + WebPMemoryWriter mem_; + WebPMuxFrameInfo info_; + FrameRect rect_; + int evaluate_; // True if this candidate should be evaluated. +} Candidate; + +// Generates a candidate encoded frame given a picture and metadata. +static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame, + const FrameRect* const rect, + const WebPConfig* const encoder_config, + int use_blending, + Candidate* const candidate) { + WebPConfig config = *encoder_config; + WebPEncodingError error_code = VP8_ENC_OK; + assert(candidate != NULL); + memset(candidate, 0, sizeof(*candidate)); + + // Set frame rect and info. + candidate->rect_ = *rect; + candidate->info_.id = WEBP_CHUNK_ANMF; + candidate->info_.x_offset = rect->x_offset_; + candidate->info_.y_offset = rect->y_offset_; + candidate->info_.dispose_method = WEBP_MUX_DISPOSE_NONE; // Set later. + candidate->info_.blend_method = + use_blending ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND; + candidate->info_.duration = 0; // Set in next call to WebPAnimEncoderAdd(). + + // Encode picture. + WebPMemoryWriterInit(&candidate->mem_); + + if (!config.lossless && use_blending) { + // Disable filtering to avoid blockiness in reconstructed frames at the + // time of decoding. + config.autofilter = 0; + config.filter_strength = 0; + } + if (!EncodeFrame(&config, sub_frame, &candidate->mem_)) { + error_code = sub_frame->error_code; + goto Err; + } + + candidate->evaluate_ = 1; + return error_code; + + Err: + WebPMemoryWriterClear(&candidate->mem_); + return error_code; +} + +static void CopyCurrentCanvas(WebPAnimEncoder* const enc) { + if (enc->curr_canvas_copy_modified_) { + WebPCopyPixels(enc->curr_canvas_, &enc->curr_canvas_copy_); + enc->curr_canvas_copy_.progress_hook = enc->curr_canvas_->progress_hook; + enc->curr_canvas_copy_.user_data = enc->curr_canvas_->user_data; + enc->curr_canvas_copy_modified_ = 0; + } +} + +enum { + LL_DISP_NONE = 0, + LL_DISP_BG, + LOSSY_DISP_NONE, + LOSSY_DISP_BG, + CANDIDATE_COUNT +}; + +#define MIN_COLORS_LOSSY 31 // Don't try lossy below this threshold. +#define MAX_COLORS_LOSSLESS 194 // Don't try lossless above this threshold. + +// Generates candidates for a given dispose method given pre-filled sub-frame +// 'params'. +static WebPEncodingError GenerateCandidates( + WebPAnimEncoder* const enc, Candidate candidates[CANDIDATE_COUNT], + WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame, + SubFrameParams* const params, + const WebPConfig* const config_ll, const WebPConfig* const config_lossy) { + WebPEncodingError error_code = VP8_ENC_OK; + const int is_dispose_none = (dispose_method == WEBP_MUX_DISPOSE_NONE); + Candidate* const candidate_ll = + is_dispose_none ? &candidates[LL_DISP_NONE] : &candidates[LL_DISP_BG]; + Candidate* const candidate_lossy = is_dispose_none + ? &candidates[LOSSY_DISP_NONE] + : &candidates[LOSSY_DISP_BG]; + WebPPicture* const curr_canvas = &enc->curr_canvas_copy_; + const WebPPicture* const prev_canvas = + is_dispose_none ? &enc->prev_canvas_ : &enc->prev_canvas_disposed_; + int use_blending_ll, use_blending_lossy; + int evaluate_ll, evaluate_lossy; + + CopyCurrentCanvas(enc); + use_blending_ll = + !is_key_frame && + IsLosslessBlendingPossible(prev_canvas, curr_canvas, ¶ms->rect_ll_); + use_blending_lossy = + !is_key_frame && + IsLossyBlendingPossible(prev_canvas, curr_canvas, ¶ms->rect_lossy_, + config_lossy->quality); + + // Pick candidates to be tried. + if (!enc->options_.allow_mixed) { + evaluate_ll = is_lossless; + evaluate_lossy = !is_lossless; + } else if (enc->options_.minimize_size) { + evaluate_ll = 1; + evaluate_lossy = 1; + } else { // Use a heuristic for trying lossless and/or lossy compression. + const int num_colors = WebPGetColorPalette(¶ms->sub_frame_ll_, NULL); + evaluate_ll = (num_colors < MAX_COLORS_LOSSLESS); + evaluate_lossy = (num_colors >= MIN_COLORS_LOSSY); + } + + // Generate candidates. + if (evaluate_ll) { + CopyCurrentCanvas(enc); + if (use_blending_ll) { + enc->curr_canvas_copy_modified_ = + IncreaseTransparency(prev_canvas, ¶ms->rect_ll_, curr_canvas); + } + error_code = EncodeCandidate(¶ms->sub_frame_ll_, ¶ms->rect_ll_, + config_ll, use_blending_ll, candidate_ll); + if (error_code != VP8_ENC_OK) return error_code; + } + if (evaluate_lossy) { + CopyCurrentCanvas(enc); + if (use_blending_lossy) { + enc->curr_canvas_copy_modified_ = + FlattenSimilarBlocks(prev_canvas, ¶ms->rect_lossy_, curr_canvas, + config_lossy->quality); + } + error_code = + EncodeCandidate(¶ms->sub_frame_lossy_, ¶ms->rect_lossy_, + config_lossy, use_blending_lossy, candidate_lossy); + if (error_code != VP8_ENC_OK) return error_code; + enc->curr_canvas_copy_modified_ = 1; + } + return error_code; +} + +#undef MIN_COLORS_LOSSY +#undef MAX_COLORS_LOSSLESS + +static void GetEncodedData(const WebPMemoryWriter* const memory, + WebPData* const encoded_data) { + encoded_data->bytes = memory->mem; + encoded_data->size = memory->size; +} + +// Sets dispose method of the previous frame to be 'dispose_method'. +static void SetPreviousDisposeMethod(WebPAnimEncoder* const enc, + WebPMuxAnimDispose dispose_method) { + const size_t position = enc->count_ - 2; + EncodedFrame* const prev_enc_frame = GetFrame(enc, position); + assert(enc->count_ >= 2); // As current and previous frames are in enc. + + if (enc->prev_candidate_undecided_) { + assert(dispose_method == WEBP_MUX_DISPOSE_NONE); + prev_enc_frame->sub_frame_.dispose_method = dispose_method; + prev_enc_frame->key_frame_.dispose_method = dispose_method; + } else { + WebPMuxFrameInfo* const prev_info = prev_enc_frame->is_key_frame_ + ? &prev_enc_frame->key_frame_ + : &prev_enc_frame->sub_frame_; + prev_info->dispose_method = dispose_method; + } +} + +static int IncreasePreviousDuration(WebPAnimEncoder* const enc, int duration) { + const size_t position = enc->count_ - 1; + EncodedFrame* const prev_enc_frame = GetFrame(enc, position); + int new_duration; + + assert(enc->count_ >= 1); + assert(prev_enc_frame->sub_frame_.duration == + prev_enc_frame->key_frame_.duration); + assert(prev_enc_frame->sub_frame_.duration == + (prev_enc_frame->sub_frame_.duration & (MAX_DURATION - 1))); + assert(duration == (duration & (MAX_DURATION - 1))); + + new_duration = prev_enc_frame->sub_frame_.duration + duration; + if (new_duration >= MAX_DURATION) { // Special case. + // Separate out previous frame from earlier merged frames to avoid overflow. + // We add a 1x1 transparent frame for the previous frame, with blending on. + const FrameRect rect = { 0, 0, 1, 1 }; + const uint8_t lossless_1x1_bytes[] = { + 0x52, 0x49, 0x46, 0x46, 0x14, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, + 0x56, 0x50, 0x38, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x10, 0x88, 0x88, 0x08 + }; + const WebPData lossless_1x1 = { + lossless_1x1_bytes, sizeof(lossless_1x1_bytes) + }; + const uint8_t lossy_1x1_bytes[] = { + 0x52, 0x49, 0x46, 0x46, 0x40, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, + 0x56, 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x4c, 0x50, 0x48, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x50, 0x38, 0x20, 0x18, 0x00, 0x00, 0x00, + 0x30, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x34, 0x25, 0xa4, 0x00, 0x03, 0x70, 0x00, 0xfe, 0xfb, 0xfd, 0x50, 0x00 + }; + const WebPData lossy_1x1 = { lossy_1x1_bytes, sizeof(lossy_1x1_bytes) }; + const int can_use_lossless = + (enc->last_config_.lossless || enc->options_.allow_mixed); + EncodedFrame* const curr_enc_frame = GetFrame(enc, enc->count_); + curr_enc_frame->is_key_frame_ = 0; + curr_enc_frame->sub_frame_.id = WEBP_CHUNK_ANMF; + curr_enc_frame->sub_frame_.x_offset = 0; + curr_enc_frame->sub_frame_.y_offset = 0; + curr_enc_frame->sub_frame_.dispose_method = WEBP_MUX_DISPOSE_NONE; + curr_enc_frame->sub_frame_.blend_method = WEBP_MUX_BLEND; + curr_enc_frame->sub_frame_.duration = duration; + if (!WebPDataCopy(can_use_lossless ? &lossless_1x1 : &lossy_1x1, + &curr_enc_frame->sub_frame_.bitstream)) { + return 0; + } + ++enc->count_; + ++enc->count_since_key_frame_; + enc->flush_count_ = enc->count_ - 1; + enc->prev_candidate_undecided_ = 0; + enc->prev_rect_ = rect; + } else { // Regular case. + // Increase duration of the previous frame by 'duration'. + prev_enc_frame->sub_frame_.duration = new_duration; + prev_enc_frame->key_frame_.duration = new_duration; + } + return 1; +} + +// Pick the candidate encoded frame with smallest size and release other +// candidates. +// TODO(later): Perhaps a rough SSIM/PSNR produced by the encoder should +// also be a criteria, in addition to sizes. +static void PickBestCandidate(WebPAnimEncoder* const enc, + Candidate* const candidates, int is_key_frame, + EncodedFrame* const encoded_frame) { + int i; + int best_idx = -1; + size_t best_size = ~0; + for (i = 0; i < CANDIDATE_COUNT; ++i) { + if (candidates[i].evaluate_) { + const size_t candidate_size = candidates[i].mem_.size; + if (candidate_size < best_size) { + best_idx = i; + best_size = candidate_size; + } + } + } + assert(best_idx != -1); + for (i = 0; i < CANDIDATE_COUNT; ++i) { + if (candidates[i].evaluate_) { + if (i == best_idx) { + WebPMuxFrameInfo* const dst = is_key_frame + ? &encoded_frame->key_frame_ + : &encoded_frame->sub_frame_; + *dst = candidates[i].info_; + GetEncodedData(&candidates[i].mem_, &dst->bitstream); + if (!is_key_frame) { + // Note: Previous dispose method only matters for non-keyframes. + // Also, we don't want to modify previous dispose method that was + // selected when a non key-frame was assumed. + const WebPMuxAnimDispose prev_dispose_method = + (best_idx == LL_DISP_NONE || best_idx == LOSSY_DISP_NONE) + ? WEBP_MUX_DISPOSE_NONE + : WEBP_MUX_DISPOSE_BACKGROUND; + SetPreviousDisposeMethod(enc, prev_dispose_method); + } + enc->prev_rect_ = candidates[i].rect_; // save for next frame. + } else { + WebPMemoryWriterClear(&candidates[i].mem_); + candidates[i].evaluate_ = 0; + } + } + } +} + +// Depending on the configuration, tries different compressions +// (lossy/lossless), dispose methods, blending methods etc to encode the current +// frame and outputs the best one in 'encoded_frame'. +// 'frame_skipped' will be set to true if this frame should actually be skipped. +static WebPEncodingError SetFrame(WebPAnimEncoder* const enc, + const WebPConfig* const config, + int is_key_frame, + EncodedFrame* const encoded_frame, + int* const frame_skipped) { + int i; + WebPEncodingError error_code = VP8_ENC_OK; + const WebPPicture* const curr_canvas = &enc->curr_canvas_copy_; + const WebPPicture* const prev_canvas = &enc->prev_canvas_; + Candidate candidates[CANDIDATE_COUNT]; + const int is_lossless = config->lossless; + const int consider_lossless = is_lossless || enc->options_.allow_mixed; + const int consider_lossy = !is_lossless || enc->options_.allow_mixed; + const int is_first_frame = enc->is_first_frame_; + + // First frame cannot be skipped as there is no 'previous frame' to merge it + // to. So, empty rectangle is not allowed for the first frame. + const int empty_rect_allowed_none = !is_first_frame; + + // Even if there is exact pixel match between 'disposed previous canvas' and + // 'current canvas', we can't skip current frame, as there may not be exact + // pixel match between 'previous canvas' and 'current canvas'. So, we don't + // allow empty rectangle in this case. + const int empty_rect_allowed_bg = 0; + + // If current frame is a key-frame, dispose method of previous frame doesn't + // matter, so we don't try dispose to background. + // Also, if key-frame insertion is on, and previous frame could be picked as + // either a sub-frame or a key-frame, then we can't be sure about what frame + // rectangle would be disposed. In that case too, we don't try dispose to + // background. + const int dispose_bg_possible = + !is_key_frame && !enc->prev_candidate_undecided_; + + SubFrameParams dispose_none_params; + SubFrameParams dispose_bg_params; + + WebPConfig config_ll = *config; + WebPConfig config_lossy = *config; + config_ll.lossless = 1; + config_lossy.lossless = 0; + enc->last_config_ = *config; + enc->last_config_reversed_ = config->lossless ? config_lossy : config_ll; + *frame_skipped = 0; + + if (!SubFrameParamsInit(&dispose_none_params, 1, empty_rect_allowed_none) || + !SubFrameParamsInit(&dispose_bg_params, 0, empty_rect_allowed_bg)) { + return VP8_ENC_ERROR_INVALID_CONFIGURATION; + } + + memset(candidates, 0, sizeof(candidates)); + + // Change-rectangle assuming previous frame was DISPOSE_NONE. + if (!GetSubRects(prev_canvas, curr_canvas, is_key_frame, is_first_frame, + config_lossy.quality, &dispose_none_params)) { + error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION; + goto Err; + } + + if ((consider_lossless && IsEmptyRect(&dispose_none_params.rect_ll_)) || + (consider_lossy && IsEmptyRect(&dispose_none_params.rect_lossy_))) { + // Don't encode the frame at all. Instead, the duration of the previous + // frame will be increased later. + assert(empty_rect_allowed_none); + *frame_skipped = 1; + goto End; + } + + if (dispose_bg_possible) { + // Change-rectangle assuming previous frame was DISPOSE_BACKGROUND. + WebPPicture* const prev_canvas_disposed = &enc->prev_canvas_disposed_; + WebPCopyPixels(prev_canvas, prev_canvas_disposed); + DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &enc->prev_rect_, + prev_canvas_disposed); + + if (!GetSubRects(prev_canvas_disposed, curr_canvas, is_key_frame, + is_first_frame, config_lossy.quality, + &dispose_bg_params)) { + error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION; + goto Err; + } + assert(!IsEmptyRect(&dispose_bg_params.rect_ll_)); + assert(!IsEmptyRect(&dispose_bg_params.rect_lossy_)); + + if (enc->options_.minimize_size) { // Try both dispose methods. + dispose_bg_params.should_try_ = 1; + dispose_none_params.should_try_ = 1; + } else if ((is_lossless && + RectArea(&dispose_bg_params.rect_ll_) < + RectArea(&dispose_none_params.rect_ll_)) || + (!is_lossless && + RectArea(&dispose_bg_params.rect_lossy_) < + RectArea(&dispose_none_params.rect_lossy_))) { + dispose_bg_params.should_try_ = 1; // Pick DISPOSE_BACKGROUND. + dispose_none_params.should_try_ = 0; + } + } + + if (dispose_none_params.should_try_) { + error_code = GenerateCandidates( + enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame, + &dispose_none_params, &config_ll, &config_lossy); + if (error_code != VP8_ENC_OK) goto Err; + } + + if (dispose_bg_params.should_try_) { + assert(!enc->is_first_frame_); + assert(dispose_bg_possible); + error_code = GenerateCandidates( + enc, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, is_key_frame, + &dispose_bg_params, &config_ll, &config_lossy); + if (error_code != VP8_ENC_OK) goto Err; + } + + PickBestCandidate(enc, candidates, is_key_frame, encoded_frame); + + goto End; + + Err: + for (i = 0; i < CANDIDATE_COUNT; ++i) { + if (candidates[i].evaluate_) { + WebPMemoryWriterClear(&candidates[i].mem_); + } + } + + End: + SubFrameParamsFree(&dispose_none_params); + SubFrameParamsFree(&dispose_bg_params); + return error_code; +} + +// Calculate the penalty incurred if we encode given frame as a key frame +// instead of a sub-frame. +static int64_t KeyFramePenalty(const EncodedFrame* const encoded_frame) { + return ((int64_t)encoded_frame->key_frame_.bitstream.size - + encoded_frame->sub_frame_.bitstream.size); +} + +static int CacheFrame(WebPAnimEncoder* const enc, + const WebPConfig* const config) { + int ok = 0; + int frame_skipped = 0; + WebPEncodingError error_code = VP8_ENC_OK; + const size_t position = enc->count_; + EncodedFrame* const encoded_frame = GetFrame(enc, position); + + ++enc->count_; + + if (enc->is_first_frame_) { // Add this as a key-frame. + error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped); + if (error_code != VP8_ENC_OK) goto End; + assert(frame_skipped == 0); // First frame can't be skipped, even if empty. + assert(position == 0 && enc->count_ == 1); + encoded_frame->is_key_frame_ = 1; + enc->flush_count_ = 0; + enc->count_since_key_frame_ = 0; + enc->prev_candidate_undecided_ = 0; + } else { + ++enc->count_since_key_frame_; + if (enc->count_since_key_frame_ <= enc->options_.kmin) { + // Add this as a frame rectangle. + error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped); + if (error_code != VP8_ENC_OK) goto End; + if (frame_skipped) goto Skip; + encoded_frame->is_key_frame_ = 0; + enc->flush_count_ = enc->count_ - 1; + enc->prev_candidate_undecided_ = 0; + } else { + int64_t curr_delta; + FrameRect prev_rect_key, prev_rect_sub; + + // Add this as a frame rectangle to enc. + error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped); + if (error_code != VP8_ENC_OK) goto End; + if (frame_skipped) goto Skip; + prev_rect_sub = enc->prev_rect_; + + + // Add this as a key-frame to enc, too. + error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped); + if (error_code != VP8_ENC_OK) goto End; + assert(frame_skipped == 0); // Key-frame cannot be an empty rectangle. + prev_rect_key = enc->prev_rect_; + + // Analyze size difference of the two variants. + curr_delta = KeyFramePenalty(encoded_frame); + if (curr_delta <= enc->best_delta_) { // Pick this as the key-frame. + if (enc->keyframe_ != KEYFRAME_NONE) { + EncodedFrame* const old_keyframe = GetFrame(enc, enc->keyframe_); + assert(old_keyframe->is_key_frame_); + old_keyframe->is_key_frame_ = 0; + } + encoded_frame->is_key_frame_ = 1; + enc->prev_candidate_undecided_ = 1; + enc->keyframe_ = (int)position; + enc->best_delta_ = curr_delta; + enc->flush_count_ = enc->count_ - 1; // We can flush previous frames. + } else { + encoded_frame->is_key_frame_ = 0; + enc->prev_candidate_undecided_ = 0; + } + // Note: We need '>=' below because when kmin and kmax are both zero, + // count_since_key_frame will always be > kmax. + if (enc->count_since_key_frame_ >= enc->options_.kmax) { + enc->flush_count_ = enc->count_ - 1; + enc->count_since_key_frame_ = 0; + enc->keyframe_ = KEYFRAME_NONE; + enc->best_delta_ = DELTA_INFINITY; + } + if (!enc->prev_candidate_undecided_) { + enc->prev_rect_ = + encoded_frame->is_key_frame_ ? prev_rect_key : prev_rect_sub; + } + } + } + + // Update previous to previous and previous canvases for next call. + WebPCopyPixels(enc->curr_canvas_, &enc->prev_canvas_); + enc->is_first_frame_ = 0; + + Skip: + ok = 1; + ++enc->in_frame_count_; + + End: + if (!ok || frame_skipped) { + FrameRelease(encoded_frame); + // We reset some counters, as the frame addition failed/was skipped. + --enc->count_; + if (!enc->is_first_frame_) --enc->count_since_key_frame_; + if (!ok) { + MarkError2(enc, "ERROR adding frame. WebPEncodingError", error_code); + } + } + enc->curr_canvas_->error_code = error_code; // report error_code + assert(ok || error_code != VP8_ENC_OK); + return ok; +} + +static int FlushFrames(WebPAnimEncoder* const enc) { + while (enc->flush_count_ > 0) { + WebPMuxError err; + EncodedFrame* const curr = GetFrame(enc, 0); + const WebPMuxFrameInfo* const info = + curr->is_key_frame_ ? &curr->key_frame_ : &curr->sub_frame_; + assert(enc->mux_ != NULL); + err = WebPMuxPushFrame(enc->mux_, info, 1); + if (err != WEBP_MUX_OK) { + MarkError2(enc, "ERROR adding frame. WebPMuxError", err); + return 0; + } + if (enc->options_.verbose) { + fprintf(stderr, "INFO: Added frame. offset:%d,%d dispose:%d blend:%d\n", + info->x_offset, info->y_offset, info->dispose_method, + info->blend_method); + } + ++enc->out_frame_count_; + FrameRelease(curr); + ++enc->start_; + --enc->flush_count_; + --enc->count_; + if (enc->keyframe_ != KEYFRAME_NONE) --enc->keyframe_; + } + + if (enc->count_ == 1 && enc->start_ != 0) { + // Move enc->start to index 0. + const int enc_start_tmp = (int)enc->start_; + EncodedFrame temp = enc->encoded_frames_[0]; + enc->encoded_frames_[0] = enc->encoded_frames_[enc_start_tmp]; + enc->encoded_frames_[enc_start_tmp] = temp; + FrameRelease(&enc->encoded_frames_[enc_start_tmp]); + enc->start_ = 0; + } + return 1; +} + +#undef DELTA_INFINITY +#undef KEYFRAME_NONE + +int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp, + const WebPConfig* encoder_config) { + WebPConfig config; + int ok; + + if (enc == NULL) { + return 0; + } + MarkNoError(enc); + + if (!enc->is_first_frame_) { + // Make sure timestamps are non-decreasing (integer wrap-around is OK). + const uint32_t prev_frame_duration = + (uint32_t)timestamp - enc->prev_timestamp_; + if (prev_frame_duration >= MAX_DURATION) { + if (frame != NULL) { + frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION; + } + MarkError(enc, "ERROR adding frame: timestamps must be non-decreasing"); + return 0; + } + if (!IncreasePreviousDuration(enc, (int)prev_frame_duration)) { + return 0; + } + } else { + enc->first_timestamp_ = timestamp; + } + + if (frame == NULL) { // Special: last call. + enc->got_null_frame_ = 1; + enc->prev_timestamp_ = timestamp; + return 1; + } + + if (frame->width != enc->canvas_width_ || + frame->height != enc->canvas_height_) { + frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION; + MarkError(enc, "ERROR adding frame: Invalid frame dimensions"); + return 0; + } + + if (!frame->use_argb) { // Convert frame from YUV(A) to ARGB. + if (enc->options_.verbose) { + fprintf(stderr, "WARNING: Converting frame from YUV(A) to ARGB format; " + "this incurs a small loss.\n"); + } + if (!WebPPictureYUVAToARGB(frame)) { + MarkError(enc, "ERROR converting frame from YUV(A) to ARGB"); + return 0; + } + } + + if (encoder_config != NULL) { + if (!WebPValidateConfig(encoder_config)) { + MarkError(enc, "ERROR adding frame: Invalid WebPConfig"); + return 0; + } + config = *encoder_config; + } else { + WebPConfigInit(&config); + config.lossless = 1; + } + assert(enc->curr_canvas_ == NULL); + enc->curr_canvas_ = frame; // Store reference. + assert(enc->curr_canvas_copy_modified_ == 1); + CopyCurrentCanvas(enc); + + ok = CacheFrame(enc, &config) && FlushFrames(enc); + + enc->curr_canvas_ = NULL; + enc->curr_canvas_copy_modified_ = 1; + if (ok) { + enc->prev_timestamp_ = timestamp; + } + return ok; +} + +// ----------------------------------------------------------------------------- +// Bitstream assembly. + +static int DecodeFrameOntoCanvas(const WebPMuxFrameInfo* const frame, + WebPPicture* const canvas) { + const WebPData* const image = &frame->bitstream; + WebPPicture sub_image; + WebPDecoderConfig config; + WebPInitDecoderConfig(&config); + WebPUtilClearPic(canvas, NULL); + if (WebPGetFeatures(image->bytes, image->size, &config.input) != + VP8_STATUS_OK) { + return 0; + } + if (!WebPPictureView(canvas, frame->x_offset, frame->y_offset, + config.input.width, config.input.height, &sub_image)) { + return 0; + } + config.output.is_external_memory = 1; + config.output.colorspace = MODE_BGRA; + config.output.u.RGBA.rgba = (uint8_t*)sub_image.argb; + config.output.u.RGBA.stride = sub_image.argb_stride * 4; + config.output.u.RGBA.size = config.output.u.RGBA.stride * sub_image.height; + + if (WebPDecode(image->bytes, image->size, &config) != VP8_STATUS_OK) { + return 0; + } + return 1; +} + +static int FrameToFullCanvas(WebPAnimEncoder* const enc, + const WebPMuxFrameInfo* const frame, + WebPData* const full_image) { + WebPPicture* const canvas_buf = &enc->curr_canvas_copy_; + WebPMemoryWriter mem1, mem2; + WebPMemoryWriterInit(&mem1); + WebPMemoryWriterInit(&mem2); + + if (!DecodeFrameOntoCanvas(frame, canvas_buf)) goto Err; + if (!EncodeFrame(&enc->last_config_, canvas_buf, &mem1)) goto Err; + GetEncodedData(&mem1, full_image); + + if (enc->options_.allow_mixed) { + if (!EncodeFrame(&enc->last_config_reversed_, canvas_buf, &mem2)) goto Err; + if (mem2.size < mem1.size) { + GetEncodedData(&mem2, full_image); + WebPMemoryWriterClear(&mem1); + } else { + WebPMemoryWriterClear(&mem2); + } + } + return 1; + + Err: + WebPMemoryWriterClear(&mem1); + WebPMemoryWriterClear(&mem2); + return 0; +} + +// Convert a single-frame animation to a non-animated image if appropriate. +// TODO(urvang): Can we pick one of the two heuristically (based on frame +// rectangle and/or presence of alpha)? +static WebPMuxError OptimizeSingleFrame(WebPAnimEncoder* const enc, + WebPData* const webp_data) { + WebPMuxError err = WEBP_MUX_OK; + int canvas_width, canvas_height; + WebPMuxFrameInfo frame; + WebPData full_image; + WebPData webp_data2; + WebPMux* const mux = WebPMuxCreate(webp_data, 0); + if (mux == NULL) return WEBP_MUX_BAD_DATA; + assert(enc->out_frame_count_ == 1); + WebPDataInit(&frame.bitstream); + WebPDataInit(&full_image); + WebPDataInit(&webp_data2); + + err = WebPMuxGetFrame(mux, 1, &frame); + if (err != WEBP_MUX_OK) goto End; + if (frame.id != WEBP_CHUNK_ANMF) goto End; // Non-animation: nothing to do. + err = WebPMuxGetCanvasSize(mux, &canvas_width, &canvas_height); + if (err != WEBP_MUX_OK) goto End; + if (!FrameToFullCanvas(enc, &frame, &full_image)) { + err = WEBP_MUX_BAD_DATA; + goto End; + } + err = WebPMuxSetImage(mux, &full_image, 1); + if (err != WEBP_MUX_OK) goto End; + err = WebPMuxAssemble(mux, &webp_data2); + if (err != WEBP_MUX_OK) goto End; + + if (webp_data2.size < webp_data->size) { // Pick 'webp_data2' if smaller. + WebPDataClear(webp_data); + *webp_data = webp_data2; + WebPDataInit(&webp_data2); + } + + End: + WebPDataClear(&frame.bitstream); + WebPDataClear(&full_image); + WebPMuxDelete(mux); + WebPDataClear(&webp_data2); + return err; +} + +int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) { + WebPMux* mux; + WebPMuxError err; + + if (enc == NULL) { + return 0; + } + MarkNoError(enc); + + if (webp_data == NULL) { + MarkError(enc, "ERROR assembling: NULL input"); + return 0; + } + + if (enc->in_frame_count_ == 0) { + MarkError(enc, "ERROR: No frames to assemble"); + return 0; + } + + if (!enc->got_null_frame_ && enc->in_frame_count_ > 1 && enc->count_ > 0) { + // set duration of the last frame to be avg of durations of previous frames. + const double delta_time = enc->prev_timestamp_ - enc->first_timestamp_; + const int average_duration = (int)(delta_time / (enc->in_frame_count_ - 1)); + if (!IncreasePreviousDuration(enc, average_duration)) { + return 0; + } + } + + // Flush any remaining frames. + enc->flush_count_ = enc->count_; + if (!FlushFrames(enc)) { + return 0; + } + + // Set definitive canvas size. + mux = enc->mux_; + err = WebPMuxSetCanvasSize(mux, enc->canvas_width_, enc->canvas_height_); + if (err != WEBP_MUX_OK) goto Err; + + err = WebPMuxSetAnimationParams(mux, &enc->options_.anim_params); + if (err != WEBP_MUX_OK) goto Err; + + // Assemble into a WebP bitstream. + err = WebPMuxAssemble(mux, webp_data); + if (err != WEBP_MUX_OK) goto Err; + + if (enc->out_frame_count_ == 1) { + err = OptimizeSingleFrame(enc, webp_data); + if (err != WEBP_MUX_OK) goto Err; + } + return 1; + + Err: + MarkError2(enc, "ERROR assembling WebP", err); + return 0; +} + +const char* WebPAnimEncoderGetError(WebPAnimEncoder* enc) { + if (enc == NULL) return NULL; + return enc->error_str_; +} + +// ----------------------------------------------------------------------------- diff --git a/3rdparty/libwebp/mux/animi.h b/3rdparty/libwebp/mux/animi.h new file mode 100644 index 0000000000..cecaf1fee5 --- /dev/null +++ b/3rdparty/libwebp/mux/animi.h @@ -0,0 +1,43 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Internal header for animation related functions. +// +// Author: Hui Su (huisu@google.com) + +#ifndef WEBP_MUX_ANIMI_H_ +#define WEBP_MUX_ANIMI_H_ + +#include "../webp/mux.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Picks the optimal rectangle between two pictures, starting with initial +// values of offsets and dimensions that are passed in. The initial +// values will be clipped, if necessary, to make sure the rectangle is +// within the canvas. "use_argb" must be true for both pictures. +// Parameters: +// prev_canvas, curr_canvas - (in) two input pictures to compare. +// is_lossless, quality - (in) encoding settings. +// x_offset, y_offset, width, height - (in/out) rectangle between the two +// input pictures. +// Returns true on success. +int WebPAnimEncoderRefineRect( + const struct WebPPicture* const prev_canvas, + const struct WebPPicture* const curr_canvas, + int is_lossless, float quality, int* const x_offset, int* const y_offset, + int* const width, int* const height); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_MUX_ANIMI_H_ */ diff --git a/3rdparty/libwebp/mux/libwebpmux.pc.in b/3rdparty/libwebp/mux/libwebpmux.pc.in new file mode 100644 index 0000000000..a96fac7850 --- /dev/null +++ b/3rdparty/libwebp/mux/libwebpmux.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libwebpmux +Description: Library for manipulating the WebP graphics format container +Version: @PACKAGE_VERSION@ +Requires: libwebp >= 0.2.0 +Cflags: -I${includedir} +Libs: -L${libdir} -lwebpmux +Libs.private: -lm diff --git a/3rdparty/libwebp/mux/libwebpmux.rc b/3rdparty/libwebp/mux/libwebpmux.rc new file mode 100644 index 0000000000..7f9a33a07a --- /dev/null +++ b/3rdparty/libwebp/mux/libwebpmux.rc @@ -0,0 +1,41 @@ +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,4,0,0 + PRODUCTVERSION 0,4,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Google, Inc." + VALUE "FileDescription", "libwebpmux DLL" + VALUE "FileVersion", "0.4.0" + VALUE "InternalName", "libwebpmux.dll" + VALUE "LegalCopyright", "Copyright (C) 2017" + VALUE "OriginalFilename", "libwebpmux.dll" + VALUE "ProductName", "WebP Image Muxer" + VALUE "ProductVersion", "0.4.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources diff --git a/3rdparty/libwebp/mux/muxedit.c b/3rdparty/libwebp/mux/muxedit.c index 2d25a14d04..d2c5305372 100644 --- a/3rdparty/libwebp/mux/muxedit.c +++ b/3rdparty/libwebp/mux/muxedit.c @@ -16,50 +16,49 @@ #include "./muxi.h" #include "../utils/utils.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - //------------------------------------------------------------------------------ // Life of a mux object. static void MuxInit(WebPMux* const mux) { - if (mux == NULL) return; + assert(mux != NULL); memset(mux, 0, sizeof(*mux)); + mux->canvas_width_ = 0; // just to be explicit + mux->canvas_height_ = 0; } WebPMux* WebPNewInternal(int version) { if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) { return NULL; } else { - WebPMux* const mux = (WebPMux*)malloc(sizeof(WebPMux)); - // If mux is NULL MuxInit is a noop. - MuxInit(mux); + WebPMux* const mux = (WebPMux*)WebPSafeMalloc(1ULL, sizeof(WebPMux)); + if (mux != NULL) MuxInit(mux); return mux; } } -static void DeleteAllChunks(WebPChunk** const chunk_list) { - while (*chunk_list) { - *chunk_list = ChunkDelete(*chunk_list); +// Delete all images in 'wpi_list'. +static void DeleteAllImages(WebPMuxImage** const wpi_list) { + while (*wpi_list != NULL) { + *wpi_list = MuxImageDelete(*wpi_list); } } static void MuxRelease(WebPMux* const mux) { - if (mux == NULL) return; - MuxImageDeleteAll(&mux->images_); - DeleteAllChunks(&mux->vp8x_); - DeleteAllChunks(&mux->iccp_); - DeleteAllChunks(&mux->anim_); - DeleteAllChunks(&mux->exif_); - DeleteAllChunks(&mux->xmp_); - DeleteAllChunks(&mux->unknown_); + assert(mux != NULL); + DeleteAllImages(&mux->images_); + ChunkListDelete(&mux->vp8x_); + ChunkListDelete(&mux->iccp_); + ChunkListDelete(&mux->anim_); + ChunkListDelete(&mux->exif_); + ChunkListDelete(&mux->xmp_); + ChunkListDelete(&mux->unknown_); } void WebPMuxDelete(WebPMux* mux) { - // If mux is NULL MuxRelease is a noop. - MuxRelease(mux); - free(mux); + if (mux != NULL) { + MuxRelease(mux); + WebPSafeFree(mux); + } } //------------------------------------------------------------------------------ @@ -68,83 +67,58 @@ void WebPMuxDelete(WebPMux* mux) { // Handy MACRO, makes MuxSet() very symmetric to MuxGet(). #define SWITCH_ID_LIST(INDEX, LIST) \ if (idx == (INDEX)) { \ - err = ChunkAssignData(&chunk, data, copy_data, kChunks[(INDEX)].tag); \ + err = ChunkAssignData(&chunk, data, copy_data, tag); \ if (err == WEBP_MUX_OK) { \ err = ChunkSetNth(&chunk, (LIST), nth); \ } \ return err; \ } -static WebPMuxError MuxSet(WebPMux* const mux, CHUNK_INDEX idx, uint32_t nth, +static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, uint32_t nth, const WebPData* const data, int copy_data) { WebPChunk chunk; WebPMuxError err = WEBP_MUX_NOT_FOUND; + const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag); assert(mux != NULL); assert(!IsWPI(kChunks[idx].id)); ChunkInit(&chunk); - SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_); - SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_); - SWITCH_ID_LIST(IDX_ANIM, &mux->anim_); - SWITCH_ID_LIST(IDX_EXIF, &mux->exif_); - SWITCH_ID_LIST(IDX_XMP, &mux->xmp_); - if (idx == IDX_UNKNOWN && data->size > TAG_SIZE) { - // For raw-data unknown chunk, the first four bytes should be the tag to be - // used for the chunk. - const WebPData tmp = { data->bytes + TAG_SIZE, data->size - TAG_SIZE }; - err = ChunkAssignData(&chunk, &tmp, copy_data, GetLE32(data->bytes + 0)); - if (err == WEBP_MUX_OK) - err = ChunkSetNth(&chunk, &mux->unknown_, nth); - } + SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_); + SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_); + SWITCH_ID_LIST(IDX_ANIM, &mux->anim_); + SWITCH_ID_LIST(IDX_EXIF, &mux->exif_); + SWITCH_ID_LIST(IDX_XMP, &mux->xmp_); + SWITCH_ID_LIST(IDX_UNKNOWN, &mux->unknown_); return err; } #undef SWITCH_ID_LIST -static WebPMuxError MuxAddChunk(WebPMux* const mux, uint32_t nth, uint32_t tag, - const uint8_t* data, size_t size, - int copy_data) { - const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag); - const WebPData chunk_data = { data, size }; - assert(mux != NULL); - assert(size <= MAX_CHUNK_PAYLOAD); - assert(idx != IDX_NIL); - return MuxSet(mux, idx, nth, &chunk_data, copy_data); -} +// Create data for frame given image data, offsets and duration. +static WebPMuxError CreateFrameData( + int width, int height, const WebPMuxFrameInfo* const info, + WebPData* const frame) { + uint8_t* frame_bytes; + const size_t frame_size = kChunks[IDX_ANMF].size; -// Create data for frame/fragment given image data, offsets and duration. -static WebPMuxError CreateFrameFragmentData( - const WebPData* const image, int x_offset, int y_offset, int duration, - WebPMuxAnimDispose dispose_method, int is_lossless, int is_frame, - WebPData* const frame_frgm) { - int width; - int height; - uint8_t* frame_frgm_bytes; - const size_t frame_frgm_size = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].size; - - const int ok = is_lossless ? - VP8LGetInfo(image->bytes, image->size, &width, &height, NULL) : - VP8GetInfo(image->bytes, image->size, image->size, &width, &height); - if (!ok) return WEBP_MUX_INVALID_ARGUMENT; - - assert(width > 0 && height > 0 && duration >= 0); - assert(dispose_method == (dispose_method & 1)); + assert(width > 0 && height > 0 && info->duration >= 0); + assert(info->dispose_method == (info->dispose_method & 1)); // Note: assertion on upper bounds is done in PutLE24(). - frame_frgm_bytes = (uint8_t*)malloc(frame_frgm_size); - if (frame_frgm_bytes == NULL) return WEBP_MUX_MEMORY_ERROR; + frame_bytes = (uint8_t*)WebPSafeMalloc(1ULL, frame_size); + if (frame_bytes == NULL) return WEBP_MUX_MEMORY_ERROR; - PutLE24(frame_frgm_bytes + 0, x_offset / 2); - PutLE24(frame_frgm_bytes + 3, y_offset / 2); + PutLE24(frame_bytes + 0, info->x_offset / 2); + PutLE24(frame_bytes + 3, info->y_offset / 2); - if (is_frame) { - PutLE24(frame_frgm_bytes + 6, width - 1); - PutLE24(frame_frgm_bytes + 9, height - 1); - PutLE24(frame_frgm_bytes + 12, duration); - frame_frgm_bytes[15] = (dispose_method & 1); - } + PutLE24(frame_bytes + 6, width - 1); + PutLE24(frame_bytes + 9, height - 1); + PutLE24(frame_bytes + 12, info->duration); + frame_bytes[15] = + (info->blend_method == WEBP_MUX_NO_BLEND ? 2 : 0) | + (info->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? 1 : 0); - frame_frgm->bytes = frame_frgm_bytes; - frame_frgm->size = frame_frgm_size; + frame->bytes = frame_bytes; + frame->size = frame_size; return WEBP_MUX_OK; } @@ -193,15 +167,9 @@ static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) { static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) { const WebPChunkId id = ChunkGetIdFromTag(tag); - WebPChunk** chunk_list; - assert(mux != NULL); if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT; - - chunk_list = MuxGetChunkListFromId(mux, id); - if (chunk_list == NULL) return WEBP_MUX_INVALID_ARGUMENT; - - return DeleteChunks(chunk_list, tag); + return DeleteChunks(MuxGetChunkListFromId(mux, id), tag); } //------------------------------------------------------------------------------ @@ -209,14 +177,12 @@ static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) { WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4], const WebPData* chunk_data, int copy_data) { - CHUNK_INDEX idx; uint32_t tag; WebPMuxError err; if (mux == NULL || fourcc == NULL || chunk_data == NULL || chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) { return WEBP_MUX_INVALID_ARGUMENT; } - idx = ChunkGetIndexFromFourCC(fourcc); tag = ChunkGetTagFromFourCC(fourcc); // Delete existing chunk(s) with the same 'fourcc'. @@ -224,7 +190,7 @@ WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4], if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; // Add the given chunk. - return MuxSet(mux, idx, 1, chunk_data, copy_data); + return MuxSet(mux, tag, 1, chunk_data, copy_data); } // Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'. @@ -259,7 +225,9 @@ static WebPMuxError SetAlphaAndImageChunks( &wpi->alpha_); if (err != WEBP_MUX_OK) return err; } - return AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_); + err = AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_); + if (err != WEBP_MUX_OK) return err; + return MuxImageFinalize(wpi) ? WEBP_MUX_OK : WEBP_MUX_INVALID_ARGUMENT; } WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream, @@ -275,7 +243,7 @@ WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream, if (mux->images_ != NULL) { // Only one 'simple image' can be added in mux. So, remove present images. - MuxImageDeleteAll(&mux->images_); + DeleteAllImages(&mux->images_); } MuxImageInit(&wpi); @@ -294,25 +262,16 @@ WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream, return err; } -WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame, +WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* info, int copy_data) { WebPMuxImage wpi; WebPMuxError err; - int is_frame; - const WebPData* const bitstream = &frame->bitstream; + const WebPData* const bitstream = &info->bitstream; // Sanity checks. - if (mux == NULL || frame == NULL) return WEBP_MUX_INVALID_ARGUMENT; + if (mux == NULL || info == NULL) return WEBP_MUX_INVALID_ARGUMENT; - is_frame = (frame->id == WEBP_CHUNK_ANMF); - if (!(is_frame || (frame->id == WEBP_CHUNK_FRGM))) { - return WEBP_MUX_INVALID_ARGUMENT; - } -#ifndef WEBP_EXPERIMENTAL_FEATURES - if (frame->id == WEBP_CHUNK_FRGM) { // disabled for now. - return WEBP_MUX_INVALID_ARGUMENT; - } -#endif + if (info->id != WEBP_CHUNK_ANMF) return WEBP_MUX_INVALID_ARGUMENT; if (bitstream->bytes == NULL || bitstream->size > MAX_CHUNK_PAYLOAD) { return WEBP_MUX_INVALID_ARGUMENT; @@ -322,7 +281,7 @@ WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame, const WebPMuxImage* const image = mux->images_; const uint32_t image_id = (image->header_ != NULL) ? ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE; - if (image_id != frame->id) { + if (image_id != info->id) { return WEBP_MUX_INVALID_ARGUMENT; // Conflicting frame types. } } @@ -333,28 +292,23 @@ WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame, assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful. { - const int is_lossless = (wpi.img_->tag_ == kChunks[IDX_VP8L].tag); - const int x_offset = frame->x_offset & ~1; // Snap offsets to even. - const int y_offset = frame->y_offset & ~1; - const int duration = is_frame ? frame->duration : 1 /* unused */; - const WebPMuxAnimDispose dispose_method = - is_frame ? frame->dispose_method : 0 /* unused */; - const uint32_t tag = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].tag; - WebPData frame_frgm; - if (x_offset < 0 || x_offset >= MAX_POSITION_OFFSET || - y_offset < 0 || y_offset >= MAX_POSITION_OFFSET || - (duration < 0 || duration >= MAX_DURATION) || - dispose_method != (dispose_method & 1)) { + WebPData frame; + const uint32_t tag = kChunks[IDX_ANMF].tag; + WebPMuxFrameInfo tmp = *info; + tmp.x_offset &= ~1; // Snap offsets to even. + tmp.y_offset &= ~1; + if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET || + tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET || + (tmp.duration < 0 || tmp.duration >= MAX_DURATION) || + tmp.dispose_method != (tmp.dispose_method & 1)) { err = WEBP_MUX_INVALID_ARGUMENT; goto Err; } - err = CreateFrameFragmentData(&wpi.img_->data_, x_offset, y_offset, - duration, dispose_method, is_lossless, - is_frame, &frame_frgm); + err = CreateFrameData(wpi.width_, wpi.height_, &tmp, &frame); if (err != WEBP_MUX_OK) goto Err; - // Add frame/fragment chunk (with copy_data = 1). - err = AddDataToChunkList(&frame_frgm, 1, tag, &wpi.header_); - WebPDataClear(&frame_frgm); // frame_frgm owned by wpi.header_ now. + // Add frame chunk (with copy_data = 1). + err = AddDataToChunkList(&frame, 1, tag, &wpi.header_); + WebPDataClear(&frame); // frame owned by wpi.header_ now. if (err != WEBP_MUX_OK) goto Err; } @@ -374,6 +328,7 @@ WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux, const WebPMuxAnimParams* params) { WebPMuxError err; uint8_t data[ANIM_CHUNK_SIZE]; + const WebPData anim = { data, ANIM_CHUNK_SIZE }; if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT; if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) { @@ -387,7 +342,33 @@ WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux, // Set the animation parameters. PutLE32(data, params->bgcolor); PutLE16(data + 4, params->loop_count); - return MuxAddChunk(mux, 1, kChunks[IDX_ANIM].tag, data, sizeof(data), 1); + return MuxSet(mux, kChunks[IDX_ANIM].tag, 1, &anim, 1); +} + +WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux, + int width, int height) { + WebPMuxError err; + if (mux == NULL) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if (width < 0 || height < 0 || + width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if (width * (uint64_t)height >= MAX_IMAGE_AREA) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if ((width * height) == 0 && (width | height) != 0) { + // one of width / height is zero, but not both -> invalid! + return WEBP_MUX_INVALID_ARGUMENT; + } + // If we already assembled a VP8X chunk, invalidate it. + err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag); + if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; + + mux->canvas_width_ = width; + mux->canvas_height_ = height; + return WEBP_MUX_OK; } //------------------------------------------------------------------------------ @@ -406,63 +387,43 @@ WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) { //------------------------------------------------------------------------------ // Assembly of the WebP RIFF file. -static WebPMuxError GetFrameFragmentInfo( - const WebPChunk* const frame_frgm_chunk, +static WebPMuxError GetFrameInfo( + const WebPChunk* const frame_chunk, int* const x_offset, int* const y_offset, int* const duration) { - const uint32_t tag = frame_frgm_chunk->tag_; - const int is_frame = (tag == kChunks[IDX_ANMF].tag); - const WebPData* const data = &frame_frgm_chunk->data_; - const size_t expected_data_size = - is_frame ? ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE; - assert(frame_frgm_chunk != NULL); - assert(tag == kChunks[IDX_ANMF].tag || tag == kChunks[IDX_FRGM].tag); + const WebPData* const data = &frame_chunk->data_; + const size_t expected_data_size = ANMF_CHUNK_SIZE; + assert(frame_chunk->tag_ == kChunks[IDX_ANMF].tag); + assert(frame_chunk != NULL); if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT; *x_offset = 2 * GetLE24(data->bytes + 0); *y_offset = 2 * GetLE24(data->bytes + 3); - if (is_frame) *duration = GetLE24(data->bytes + 12); + *duration = GetLE24(data->bytes + 12); return WEBP_MUX_OK; } -WebPMuxError MuxGetImageWidthHeight(const WebPChunk* const image_chunk, - int* const width, int* const height) { - const uint32_t tag = image_chunk->tag_; - const WebPData* const data = &image_chunk->data_; - int w, h; - int ok; - assert(image_chunk != NULL); - assert(tag == kChunks[IDX_VP8].tag || tag == kChunks[IDX_VP8L].tag); - ok = (tag == kChunks[IDX_VP8].tag) ? - VP8GetInfo(data->bytes, data->size, data->size, &w, &h) : - VP8LGetInfo(data->bytes, data->size, &w, &h, NULL); - if (ok) { - *width = w; - *height = h; - return WEBP_MUX_OK; - } else { - return WEBP_MUX_BAD_DATA; - } -} - static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi, int* const x_offset, int* const y_offset, int* const duration, int* const width, int* const height) { - const WebPChunk* const image_chunk = wpi->img_; - const WebPChunk* const frame_frgm_chunk = wpi->header_; + const WebPChunk* const frame_chunk = wpi->header_; + WebPMuxError err; + assert(wpi != NULL); + assert(frame_chunk != NULL); - // Get offsets and duration from ANMF/FRGM chunk. - const WebPMuxError err = - GetFrameFragmentInfo(frame_frgm_chunk, x_offset, y_offset, duration); + // Get offsets and duration from ANMF chunk. + err = GetFrameInfo(frame_chunk, x_offset, y_offset, duration); if (err != WEBP_MUX_OK) return err; // Get width and height from VP8/VP8L chunk. - return MuxGetImageWidthHeight(image_chunk, width, height); + if (width != NULL) *width = wpi->width_; + if (height != NULL) *height = wpi->height_; + return WEBP_MUX_OK; } -static WebPMuxError GetImageCanvasWidthHeight( - const WebPMux* const mux, uint32_t flags, - int* const width, int* const height) { +// Returns the tightest dimension for the canvas considering the image list. +static WebPMuxError GetAdjustedCanvasSize(const WebPMux* const mux, + int* const width, int* const height) { WebPMuxImage* wpi = NULL; assert(mux != NULL); assert(width != NULL && height != NULL); @@ -471,11 +432,11 @@ static WebPMuxError GetImageCanvasWidthHeight( assert(wpi != NULL); assert(wpi->img_ != NULL); - if (wpi->next_) { - int max_x = 0; - int max_y = 0; - int64_t image_area = 0; - // Aggregate the bounding box for animation frames & fragmented images. + if (wpi->next_ != NULL) { + int max_x = 0, max_y = 0; + // if we have a chain of wpi's, header_ is necessarily set + assert(wpi->header_ != NULL); + // Aggregate the bounding box for animation frames. for (; wpi != NULL; wpi = wpi->next_) { int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0; const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset, @@ -488,27 +449,13 @@ static WebPMuxError GetImageCanvasWidthHeight( if (max_x_pos > max_x) max_x = max_x_pos; if (max_y_pos > max_y) max_y = max_y_pos; - image_area += w * h; } *width = max_x; *height = max_y; - // Crude check to validate that there are no image overlaps/holes for - // fragmented images. Check that the aggregated image area for individual - // fragments exactly matches the image area of the constructed canvas. - // However, the area-match is necessary but not sufficient condition. - if ((flags & FRAGMENTS_FLAG) && (image_area != (max_x * max_y))) { - *width = 0; - *height = 0; - return WEBP_MUX_INVALID_ARGUMENT; - } } else { - // For a single image, extract the width & height from VP8/VP8L image-data. - int w, h; - const WebPChunk* const image_chunk = wpi->img_; - const WebPMuxError err = MuxGetImageWidthHeight(image_chunk, &w, &h); - if (err != WEBP_MUX_OK) return err; - *width = w; - *height = h; + // For a single image, canvas dimensions are same as image dimensions. + *width = wpi->width_; + *height = wpi->height_; } return WEBP_MUX_OK; } @@ -524,7 +471,7 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { int width = 0; int height = 0; uint8_t data[VP8X_CHUNK_SIZE]; - const size_t data_size = VP8X_CHUNK_SIZE; + const WebPData vp8x = { data, VP8X_CHUNK_SIZE }; const WebPMuxImage* images = NULL; assert(mux != NULL); @@ -550,10 +497,7 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { flags |= XMP_FLAG; } if (images->header_ != NULL) { - if (images->header_->tag_ == kChunks[IDX_FRGM].tag) { - // This is a fragmented image. - flags |= FRAGMENTS_FLAG; - } else if (images->header_->tag_ == kChunks[IDX_ANMF].tag) { + if (images->header_->tag_ == kChunks[IDX_ANMF].tag) { // This is an image with animation. flags |= ANIMATION_FLAG; } @@ -562,12 +506,7 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { flags |= ALPHA_FLAG; // Some images have an alpha channel. } - if (flags == 0) { - // For Simple Image, VP8X chunk should not be added. - return WEBP_MUX_OK; - } - - err = GetImageCanvasWidthHeight(mux, flags, &width, &height); + err = GetAdjustedCanvasSize(mux, &width, &height); if (err != WEBP_MUX_OK) return err; if (width <= 0 || height <= 0) { @@ -577,9 +516,21 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { return WEBP_MUX_INVALID_ARGUMENT; } - if (MuxHasLosslessImages(images)) { - // We have a file with a VP8X chunk having some lossless images. - // As lossless images implicitly contain alpha, force ALPHA_FLAG to be true. + if (mux->canvas_width_ != 0 || mux->canvas_height_ != 0) { + if (width > mux->canvas_width_ || height > mux->canvas_height_) { + return WEBP_MUX_INVALID_ARGUMENT; + } + width = mux->canvas_width_; + height = mux->canvas_height_; + } + + if (flags == 0 && mux->unknown_ == NULL) { + // For simple file format, VP8X chunk should not be added. + return WEBP_MUX_OK; + } + + if (MuxHasAlpha(images)) { + // This means some frames explicitly/implicitly contain alpha. // Note: This 'flags' update must NOT be done for a lossless image // without a VP8X chunk! flags |= ALPHA_FLAG; @@ -589,35 +540,33 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { PutLE24(data + 4, width - 1); // canvas width. PutLE24(data + 7, height - 1); // canvas height. - err = MuxAddChunk(mux, 1, kChunks[IDX_VP8X].tag, data, data_size, 1); - return err; + return MuxSet(mux, kChunks[IDX_VP8X].tag, 1, &vp8x, 1); } // Cleans up 'mux' by removing any unnecessary chunks. static WebPMuxError MuxCleanup(WebPMux* const mux) { int num_frames; - int num_fragments; int num_anim_chunks; - // If we have an image with single fragment or frame, convert it to a - // non-animated non-fragmented image (to avoid writing FRGM/ANMF chunk - // unnecessarily). + // If we have an image with a single frame, and its rectangle + // covers the whole canvas, convert it to a non-animated image + // (to avoid writing ANMF chunk unnecessarily). WebPMuxError err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames); if (err != WEBP_MUX_OK) return err; - err = WebPMuxNumChunks(mux, kChunks[IDX_FRGM].id, &num_fragments); - if (err != WEBP_MUX_OK) return err; - if (num_frames == 1 || num_fragments == 1) { - WebPMuxImage* frame_frag; - err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame_frag); - assert(err == WEBP_MUX_OK); // We know that one frame/fragment does exist. - if (frame_frag->header_ != NULL) { - assert(frame_frag->header_->tag_ == kChunks[IDX_ANMF].tag || - frame_frag->header_->tag_ == kChunks[IDX_FRGM].tag); - ChunkDelete(frame_frag->header_); // Removes ANMF/FRGM chunk. - frame_frag->header_ = NULL; + if (num_frames == 1) { + WebPMuxImage* frame = NULL; + err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame); + assert(err == WEBP_MUX_OK); // We know that one frame does exist. + assert(frame != NULL); + if (frame->header_ != NULL && + ((mux->canvas_width_ == 0 && mux->canvas_height_ == 0) || + (frame->width_ == mux->canvas_width_ && + frame->height_ == mux->canvas_height_))) { + assert(frame->header_->tag_ == kChunks[IDX_ANMF].tag); + ChunkDelete(frame->header_); // Removes ANMF chunk. + frame->header_ = NULL; + num_frames = 0; } - num_frames = 0; - num_fragments = 0; } // Remove ANIM chunk if this is a non-animated image. err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks); @@ -629,13 +578,38 @@ static WebPMuxError MuxCleanup(WebPMux* const mux) { return WEBP_MUX_OK; } +// Total size of a list of images. +static size_t ImageListDiskSize(const WebPMuxImage* wpi_list) { + size_t size = 0; + while (wpi_list != NULL) { + size += MuxImageDiskSize(wpi_list); + wpi_list = wpi_list->next_; + } + return size; +} + +// Write out the given list of images into 'dst'. +static uint8_t* ImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) { + while (wpi_list != NULL) { + dst = MuxImageEmit(wpi_list, dst); + wpi_list = wpi_list->next_; + } + return dst; +} + WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { size_t size = 0; uint8_t* data = NULL; uint8_t* dst = NULL; WebPMuxError err; - if (mux == NULL || assembled_data == NULL) { + if (assembled_data == NULL) { + return WEBP_MUX_INVALID_ARGUMENT; + } + // Clean up returned data, in case something goes wrong. + memset(assembled_data, 0, sizeof(*assembled_data)); + + if (mux == NULL) { return WEBP_MUX_INVALID_ARGUMENT; } @@ -646,12 +620,12 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { if (err != WEBP_MUX_OK) return err; // Allocate data. - size = ChunksListDiskSize(mux->vp8x_) + ChunksListDiskSize(mux->iccp_) - + ChunksListDiskSize(mux->anim_) + MuxImageListDiskSize(mux->images_) - + ChunksListDiskSize(mux->exif_) + ChunksListDiskSize(mux->xmp_) - + ChunksListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE; + size = ChunkListDiskSize(mux->vp8x_) + ChunkListDiskSize(mux->iccp_) + + ChunkListDiskSize(mux->anim_) + ImageListDiskSize(mux->images_) + + ChunkListDiskSize(mux->exif_) + ChunkListDiskSize(mux->xmp_) + + ChunkListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE; - data = (uint8_t*)malloc(size); + data = (uint8_t*)WebPSafeMalloc(1ULL, size); if (data == NULL) return WEBP_MUX_MEMORY_ERROR; // Emit header & chunks. @@ -659,7 +633,7 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { dst = ChunkListEmit(mux->vp8x_, dst); dst = ChunkListEmit(mux->iccp_, dst); dst = ChunkListEmit(mux->anim_, dst); - dst = MuxImageListEmit(mux->images_, dst); + dst = ImageListEmit(mux->images_, dst); dst = ChunkListEmit(mux->exif_, dst); dst = ChunkListEmit(mux->xmp_, dst); dst = ChunkListEmit(mux->unknown_, dst); @@ -668,7 +642,7 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { // Validate mux. err = MuxValidate(mux); if (err != WEBP_MUX_OK) { - free(data); + WebPSafeFree(data); data = NULL; size = 0; } @@ -681,7 +655,3 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { } //------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/mux/muxi.h b/3rdparty/libwebp/mux/muxi.h index eaed558b47..e6606aa5d1 100644 --- a/3rdparty/libwebp/mux/muxi.h +++ b/3rdparty/libwebp/mux/muxi.h @@ -15,11 +15,11 @@ #define WEBP_MUX_MUXI_H_ #include -#include "../dec/vp8i.h" -#include "../dec/vp8li.h" +#include "../dec/vp8i_dec.h" +#include "../dec/vp8li_dec.h" #include "../webp/mux.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -27,8 +27,8 @@ extern "C" { // Defines and constants. #define MUX_MAJ_VERSION 0 -#define MUX_MIN_VERSION 1 -#define MUX_REV_VERSION 1 +#define MUX_MIN_VERSION 4 +#define MUX_REV_VERSION 0 // Chunk object. typedef struct WebPChunk WebPChunk; @@ -36,18 +36,22 @@ struct WebPChunk { uint32_t tag_; int owner_; // True if *data_ memory is owned internally. // VP8X, ANIM, and other internally created chunks - // like ANMF/FRGM are always owned. + // like ANMF are always owned. WebPData data_; WebPChunk* next_; }; -// MuxImage object. Store a full WebP image (including ANMF/FRGM chunk, ALPH +// MuxImage object. Store a full WebP image (including ANMF chunk, ALPH // chunk and VP8/VP8L chunk), typedef struct WebPMuxImage WebPMuxImage; struct WebPMuxImage { - WebPChunk* header_; // Corresponds to WEBP_CHUNK_ANMF/WEBP_CHUNK_FRGM. + WebPChunk* header_; // Corresponds to WEBP_CHUNK_ANMF. WebPChunk* alpha_; // Corresponds to WEBP_CHUNK_ALPHA. WebPChunk* img_; // Corresponds to WEBP_CHUNK_IMAGE. + WebPChunk* unknown_; // Corresponds to WEBP_CHUNK_UNKNOWN. + int width_; + int height_; + int has_alpha_; // Through ALPH chunk or as part of VP8L. int is_partial_; // True if only some of the chunks are filled. WebPMuxImage* next_; }; @@ -61,7 +65,9 @@ struct WebPMux { WebPChunk* anim_; WebPChunk* vp8x_; - WebPChunk* unknown_; + WebPChunk* unknown_; + int canvas_width_; + int canvas_height_; }; // CHUNK_INDEX enum: used for indexing within 'kChunks' (defined below) only. @@ -73,7 +79,6 @@ typedef enum { IDX_ICCP, IDX_ANIM, IDX_ANMF, - IDX_FRGM, IDX_ALPHA, IDX_VP8, IDX_VP8L, @@ -101,10 +106,10 @@ extern const ChunkInfo kChunks[IDX_LAST_CHUNK]; // Initialize. void ChunkInit(WebPChunk* const chunk); -// Get chunk index from chunk tag. Returns IDX_NIL if not found. +// Get chunk index from chunk tag. Returns IDX_UNKNOWN if not found. CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag); -// Get chunk id from chunk tag. Returns WEBP_CHUNK_NIL if not found. +// Get chunk id from chunk tag. Returns WEBP_CHUNK_UNKNOWN if not found. WebPChunkId ChunkGetIdFromTag(uint32_t tag); // Convert a fourcc string to a tag. @@ -133,6 +138,9 @@ WebPChunk* ChunkRelease(WebPChunk* const chunk); // Deletes given chunk & returns chunk->next_. WebPChunk* ChunkDelete(WebPChunk* const chunk); +// Deletes all chunks in the given chunk list. +void ChunkListDelete(WebPChunk** const chunk_list); + // Returns size of the chunk including chunk header and padding byte (if any). static WEBP_INLINE size_t SizeWithPadding(size_t chunk_size) { return CHUNK_HEADER_SIZE + ((chunk_size + 1) & ~1U); @@ -146,15 +154,11 @@ static WEBP_INLINE size_t ChunkDiskSize(const WebPChunk* chunk) { } // Total size of a list of chunks. -size_t ChunksListDiskSize(const WebPChunk* chunk_list); +size_t ChunkListDiskSize(const WebPChunk* chunk_list); // Write out the given list of chunks into 'dst'. uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst); -// Get the width & height of image stored in 'image_chunk'. -WebPMuxError MuxGetImageWidthHeight(const WebPChunk* const image_chunk, - int* const width, int* const height); - //------------------------------------------------------------------------------ // MuxImage object management. @@ -168,37 +172,24 @@ WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi); // 'wpi' can be NULL. WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi); -// Delete all images in 'wpi_list'. -void MuxImageDeleteAll(WebPMuxImage** const wpi_list); - // Count number of images matching the given tag id in the 'wpi_list'. // If id == WEBP_CHUNK_NIL, all images will be matched. int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id); +// Update width/height/has_alpha info from chunks within wpi. +// Also remove ALPH chunk if not needed. +int MuxImageFinalize(WebPMuxImage* const wpi); + // Check if given ID corresponds to an image related chunk. static WEBP_INLINE int IsWPI(WebPChunkId id) { switch (id) { case WEBP_CHUNK_ANMF: - case WEBP_CHUNK_FRGM: case WEBP_CHUNK_ALPHA: case WEBP_CHUNK_IMAGE: return 1; default: return 0; } } -// Get a reference to appropriate chunk list within an image given chunk tag. -static WEBP_INLINE WebPChunk** MuxImageGetListFromId( - const WebPMuxImage* const wpi, WebPChunkId id) { - assert(wpi != NULL); - switch (id) { - case WEBP_CHUNK_ANMF: - case WEBP_CHUNK_FRGM: return (WebPChunk**)&wpi->header_; - case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_; - case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_; - default: return NULL; - } -} - // Pushes 'wpi' at the end of 'wpi_list'. WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list); @@ -212,38 +203,27 @@ WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth, // Total size of the given image. size_t MuxImageDiskSize(const WebPMuxImage* const wpi); -// Total size of a list of images. -size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list); - // Write out the given image into 'dst'. uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst); -// Write out the given list of images into 'dst'. -uint8_t* MuxImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst); - //------------------------------------------------------------------------------ // Helper methods for mux. -// Checks if the given image list contains at least one lossless image. -int MuxHasLosslessImages(const WebPMuxImage* images); +// Checks if the given image list contains at least one image with alpha. +int MuxHasAlpha(const WebPMuxImage* images); // Write out RIFF header into 'data', given total data size 'size'. uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size); // Returns the list where chunk with given ID is to be inserted in mux. -// Return value is NULL if this chunk should be inserted in mux->images_ list -// or if 'id' is not known. WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id); -// Validates that the given mux has a single image. -WebPMuxError MuxValidateForImage(const WebPMux* const mux); - // Validates the given mux object. WebPMuxError MuxValidate(const WebPMux* const mux); //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/mux/muxinternal.c b/3rdparty/libwebp/mux/muxinternal.c index 9543c0ee5b..387b57e8fe 100644 --- a/3rdparty/libwebp/mux/muxinternal.c +++ b/3rdparty/libwebp/mux/muxinternal.c @@ -16,24 +16,19 @@ #include "./muxi.h" #include "../utils/utils.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#define UNDEFINED_CHUNK_SIZE (-1) +#define UNDEFINED_CHUNK_SIZE ((uint32_t)(-1)) const ChunkInfo kChunks[] = { { MKFOURCC('V', 'P', '8', 'X'), WEBP_CHUNK_VP8X, VP8X_CHUNK_SIZE }, { MKFOURCC('I', 'C', 'C', 'P'), WEBP_CHUNK_ICCP, UNDEFINED_CHUNK_SIZE }, { MKFOURCC('A', 'N', 'I', 'M'), WEBP_CHUNK_ANIM, ANIM_CHUNK_SIZE }, { MKFOURCC('A', 'N', 'M', 'F'), WEBP_CHUNK_ANMF, ANMF_CHUNK_SIZE }, - { MKFOURCC('F', 'R', 'G', 'M'), WEBP_CHUNK_FRGM, FRGM_CHUNK_SIZE }, { MKFOURCC('A', 'L', 'P', 'H'), WEBP_CHUNK_ALPHA, UNDEFINED_CHUNK_SIZE }, { MKFOURCC('V', 'P', '8', ' '), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE }, { MKFOURCC('V', 'P', '8', 'L'), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE }, { MKFOURCC('E', 'X', 'I', 'F'), WEBP_CHUNK_EXIF, UNDEFINED_CHUNK_SIZE }, { MKFOURCC('X', 'M', 'P', ' '), WEBP_CHUNK_XMP, UNDEFINED_CHUNK_SIZE }, - { MKFOURCC('U', 'N', 'K', 'N'), WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE }, + { NIL_TAG, WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE }, { NIL_TAG, WEBP_CHUNK_NIL, UNDEFINED_CHUNK_SIZE } }; @@ -70,9 +65,9 @@ WebPChunk* ChunkRelease(WebPChunk* const chunk) { CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) { int i; for (i = 0; kChunks[i].tag != NIL_TAG; ++i) { - if (tag == kChunks[i].tag) return i; + if (tag == kChunks[i].tag) return (CHUNK_INDEX)i; } - return IDX_NIL; + return IDX_UNKNOWN; } WebPChunkId ChunkGetIdFromTag(uint32_t tag) { @@ -80,7 +75,7 @@ WebPChunkId ChunkGetIdFromTag(uint32_t tag) { for (i = 0; kChunks[i].tag != NIL_TAG; ++i) { if (tag == kChunks[i].tag) return kChunks[i].id; } - return WEBP_CHUNK_NIL; + return WEBP_CHUNK_UNKNOWN; } uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) { @@ -89,8 +84,7 @@ uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) { CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) { const uint32_t tag = ChunkGetTagFromFourCC(fourcc); - const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag); - return (idx == IDX_NIL) ? IDX_UNKNOWN : idx; + return ChunkGetIndexFromTag(tag); } //------------------------------------------------------------------------------ @@ -170,7 +164,7 @@ WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list, return WEBP_MUX_NOT_FOUND; } - new_chunk = (WebPChunk*)malloc(sizeof(*new_chunk)); + new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, sizeof(*new_chunk)); if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR; *new_chunk = *chunk; chunk->owner_ = 0; @@ -184,22 +178,19 @@ WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list, WebPChunk* ChunkDelete(WebPChunk* const chunk) { WebPChunk* const next = ChunkRelease(chunk); - free(chunk); + WebPSafeFree(chunk); return next; } +void ChunkListDelete(WebPChunk** const chunk_list) { + while (*chunk_list != NULL) { + *chunk_list = ChunkDelete(*chunk_list); + } +} + //------------------------------------------------------------------------------ // Chunk serialization methods. -size_t ChunksListDiskSize(const WebPChunk* chunk_list) { - size_t size = 0; - while (chunk_list != NULL) { - size += ChunkDiskSize(chunk_list); - chunk_list = chunk_list->next_; - } - return size; -} - static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) { const size_t chunk_size = chunk->data_.size; assert(chunk); @@ -221,6 +212,15 @@ uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) { return dst; } +size_t ChunkListDiskSize(const WebPChunk* chunk_list) { + size_t size = 0; + while (chunk_list != NULL) { + size += ChunkDiskSize(chunk_list); + chunk_list = chunk_list->next_; + } + return size; +} + //------------------------------------------------------------------------------ // Life of a MuxImage object. @@ -235,6 +235,7 @@ WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) { ChunkDelete(wpi->header_); ChunkDelete(wpi->alpha_); ChunkDelete(wpi->img_); + ChunkListDelete(&wpi->unknown_); next = wpi->next_; MuxImageInit(wpi); @@ -244,6 +245,18 @@ WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) { //------------------------------------------------------------------------------ // MuxImage search methods. +// Get a reference to appropriate chunk list within an image given chunk tag. +static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi, + WebPChunkId id) { + assert(wpi != NULL); + switch (id) { + case WEBP_CHUNK_ANMF: return (WebPChunk**)&wpi->header_; + case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_; + case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_; + default: return NULL; + } +} + int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) { int count = 0; const WebPMuxImage* current; @@ -251,7 +264,7 @@ int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) { if (id == WEBP_CHUNK_NIL) { ++count; // Special case: count all images. } else { - const WebPChunk* const wpi_chunk = *MuxImageGetListFromId(current, id); + const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id); if (wpi_chunk != NULL) { const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_); if (wpi_chunk_id == id) ++count; // Count images with a matching 'id'. @@ -297,7 +310,7 @@ WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) { wpi_list = &cur_wpi->next_; } - new_wpi = (WebPMuxImage*)malloc(sizeof(*new_wpi)); + new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi)); if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR; *new_wpi = *wpi; new_wpi->next_ = NULL; @@ -316,16 +329,10 @@ WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) { WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) { // Delete the components of wpi. If wpi is NULL this is a noop. WebPMuxImage* const next = MuxImageRelease(wpi); - free(wpi); + WebPSafeFree(wpi); return next; } -void MuxImageDeleteAll(WebPMuxImage** const wpi_list) { - while (*wpi_list != NULL) { - *wpi_list = MuxImageDelete(*wpi_list); - } -} - WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) { assert(wpi_list); if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) { @@ -359,25 +366,16 @@ size_t MuxImageDiskSize(const WebPMuxImage* const wpi) { if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_); if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_); if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_); + if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_); return size; } -size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list) { - size_t size = 0; - while (wpi_list != NULL) { - size += MuxImageDiskSize(wpi_list); - wpi_list = wpi_list->next_; - } - return size; -} - -// Special case as ANMF/FRGM chunk encapsulates other image chunks. +// Special case as ANMF chunk encapsulates other image chunks. static uint8_t* ChunkEmitSpecial(const WebPChunk* const header, size_t total_size, uint8_t* dst) { const size_t header_size = header->data_.size; const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE; - assert(header->tag_ == kChunks[IDX_ANMF].tag || - header->tag_ == kChunks[IDX_FRGM].tag); + assert(header->tag_ == kChunks[IDX_ANMF].tag); PutLE32(dst + 0, header->tag_); PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next); assert(header_size == (uint32_t)header_size); @@ -390,7 +388,7 @@ static uint8_t* ChunkEmitSpecial(const WebPChunk* const header, uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) { // Ordering of chunks to be emitted is strictly as follows: - // 1. ANMF/FRGM chunk (if present). + // 1. ANMF chunk (if present). // 2. ALPH chunk (if present). // 3. VP8/VP8L chunk. assert(wpi); @@ -399,26 +397,16 @@ uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) { } if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst); if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst); - return dst; -} - -uint8_t* MuxImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) { - while (wpi_list != NULL) { - dst = MuxImageEmit(wpi_list, dst); - wpi_list = wpi_list->next_; - } + if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst); return dst; } //------------------------------------------------------------------------------ // Helper methods for mux. -int MuxHasLosslessImages(const WebPMuxImage* images) { +int MuxHasAlpha(const WebPMuxImage* images) { while (images != NULL) { - assert(images->img_ != NULL); - if (images->img_->tag_ == kChunks[IDX_VP8L].tag) { - return 1; - } + if (images->has_alpha_) return 1; images = images->next_; } return 0; @@ -440,25 +428,7 @@ WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) { case WEBP_CHUNK_ANIM: return (WebPChunk**)&mux->anim_; case WEBP_CHUNK_EXIF: return (WebPChunk**)&mux->exif_; case WEBP_CHUNK_XMP: return (WebPChunk**)&mux->xmp_; - case WEBP_CHUNK_UNKNOWN: return (WebPChunk**)&mux->unknown_; - default: return NULL; - } -} - -WebPMuxError MuxValidateForImage(const WebPMux* const mux) { - const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE); - const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF); - const int num_fragments = MuxImageCount(mux->images_, WEBP_CHUNK_FRGM); - - if (num_images == 0) { - // No images in mux. - return WEBP_MUX_NOT_FOUND; - } else if (num_images == 1 && num_frames == 0 && num_fragments == 0) { - // Valid case (single image). - return WEBP_MUX_OK; - } else { - // Frame/Fragment case OR an invalid mux. - return WEBP_MUX_INVALID_ARGUMENT; + default: return (WebPChunk**)&mux->unknown_; } } @@ -466,7 +436,7 @@ static int IsNotCompatible(int feature, int num_items) { return (feature != 0) != (num_items > 0); } -#define NO_FLAG 0 +#define NO_FLAG ((WebPFeatureFlags)0) // Test basic constraints: // retrieval, maximum number of chunks by index (use -1 to skip) @@ -474,7 +444,7 @@ static int IsNotCompatible(int feature, int num_items) { // On success returns WEBP_MUX_OK and stores the chunk count in *num. static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx, WebPFeatureFlags feature, - WebPFeatureFlags vp8x_flags, + uint32_t vp8x_flags, int max, int* num) { const WebPMuxError err = WebPMuxNumChunks(mux, kChunks[idx].id, num); @@ -492,7 +462,6 @@ WebPMuxError MuxValidate(const WebPMux* const mux) { int num_xmp; int num_anim; int num_frames; - int num_fragments; int num_vp8x; int num_images; int num_alpha; @@ -537,10 +506,6 @@ WebPMuxError MuxValidate(const WebPMux* const mux) { } } - // Fragmentation: FRAGMENTS_FLAG and FRGM chunk(s) are consistent. - err = ValidateChunk(mux, IDX_FRGM, FRAGMENTS_FLAG, flags, -1, &num_fragments); - if (err != WEBP_MUX_OK) return err; - // Verify either VP8X chunk is present OR there is only one elem in // mux->images_. err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x); @@ -550,19 +515,18 @@ WebPMuxError MuxValidate(const WebPMux* const mux) { if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT; // ALPHA_FLAG & alpha chunk(s) are consistent. - if (MuxHasLosslessImages(mux->images_)) { + if (MuxHasAlpha(mux->images_)) { if (num_vp8x > 0) { - // Special case: we have a VP8X chunk as well as some lossless images. + // VP8X chunk is present, so it should contain ALPHA_FLAG. if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT; - } - } else { - err = ValidateChunk(mux, IDX_ALPHA, ALPHA_FLAG, flags, -1, &num_alpha); + } else { + // VP8X chunk is not present, so ALPH chunks should NOT be present either. + err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha); if (err != WEBP_MUX_OK) return err; - } - - // num_fragments & num_images are consistent. - if (num_fragments > 0 && num_images != num_fragments) { - return WEBP_MUX_INVALID_ARGUMENT; + if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT; + } + } else { // Mux doesn't need alpha. So, ALPHA_FLAG should NOT be present. + if (flags & ALPHA_FLAG) return WEBP_MUX_INVALID_ARGUMENT; } return WEBP_MUX_OK; @@ -572,6 +536,3 @@ WebPMuxError MuxValidate(const WebPMux* const mux) { //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/mux/muxread.c b/3rdparty/libwebp/mux/muxread.c index 2179ccb79e..410acd9119 100644 --- a/3rdparty/libwebp/mux/muxread.c +++ b/3rdparty/libwebp/mux/muxread.c @@ -16,10 +16,6 @@ #include "./muxi.h" #include "../utils/utils.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - //------------------------------------------------------------------------------ // Helper method(s). @@ -61,7 +57,7 @@ static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk, WebPData chunk_data; // Sanity checks. - if (data_size < TAG_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA; + if (data_size < CHUNK_HEADER_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA; chunk_size = GetLE32(data + TAG_SIZE); { @@ -76,6 +72,29 @@ static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk, return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0)); } +int MuxImageFinalize(WebPMuxImage* const wpi) { + const WebPChunk* const img = wpi->img_; + const WebPData* const image = &img->data_; + const int is_lossless = (img->tag_ == kChunks[IDX_VP8L].tag); + int w, h; + int vp8l_has_alpha = 0; + const int ok = is_lossless ? + VP8LGetInfo(image->bytes, image->size, &w, &h, &vp8l_has_alpha) : + VP8GetInfo(image->bytes, image->size, image->size, &w, &h); + assert(img != NULL); + if (ok) { + // Ignore ALPH chunk accompanying VP8L. + if (is_lossless && (wpi->alpha_ != NULL)) { + ChunkDelete(wpi->alpha_); + wpi->alpha_ = NULL; + } + wpi->width_ = w; + wpi->height_ = h; + wpi->has_alpha_ = vp8l_has_alpha || (wpi->alpha_ != NULL); + } + return ok; +} + static int MuxImageParse(const WebPChunk* const chunk, int copy_data, WebPMuxImage* const wpi) { const uint8_t* bytes = chunk->data_.bytes; @@ -85,17 +104,15 @@ static int MuxImageParse(const WebPChunk* const chunk, int copy_data, size_t subchunk_size; ChunkInit(&subchunk); - assert(chunk->tag_ == kChunks[IDX_ANMF].tag || - chunk->tag_ == kChunks[IDX_FRGM].tag); + assert(chunk->tag_ == kChunks[IDX_ANMF].tag); assert(!wpi->is_partial_); - // ANMF/FRGM. + // ANMF. { - const size_t hdr_size = (chunk->tag_ == kChunks[IDX_ANMF].tag) ? - ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE; + const size_t hdr_size = ANMF_CHUNK_SIZE; const WebPData temp = { bytes, hdr_size }; - // Each of ANMF and FRGM chunk contain a header at the beginning. So, its - // size should at least be 'hdr_size'. + // Each of ANMF chunk contain a header at the beginning. So, its size should + // be at least 'hdr_size'. if (size < hdr_size) goto Fail; ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_); } @@ -121,8 +138,14 @@ static int MuxImageParse(const WebPChunk* const chunk, int copy_data, break; case WEBP_CHUNK_IMAGE: if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail; + if (!MuxImageFinalize(wpi)) goto Fail; wpi->is_partial_ = 0; // wpi is completely filled. break; + case WEBP_CHUNK_UNKNOWN: + if (wpi->is_partial_) goto Fail; // Encountered an unknown chunk + // before some image chunks. + if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail; + break; default: goto Fail; break; @@ -195,7 +218,7 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, data += RIFF_HEADER_SIZE; size -= RIFF_HEADER_SIZE; - wpi = (WebPMuxImage*)malloc(sizeof(*wpi)); + wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*wpi)); if (wpi == NULL) goto Err; MuxImageInit(wpi); @@ -218,6 +241,7 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, break; case WEBP_CHUNK_IMAGE: if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err; + if (!MuxImageFinalize(wpi)) goto Err; wpi->is_partial_ = 0; // wpi is completely filled. PushImage: // Add this to mux->images_ list. @@ -225,9 +249,6 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, MuxImageInit(wpi); // Reset for reading next image. break; case WEBP_CHUNK_ANMF: -#ifdef WEBP_EXPERIMENTAL_FEATURES - case WEBP_CHUNK_FRGM: -#endif if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete. if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err; ChunkRelease(&chunk); @@ -237,8 +258,11 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before // getting all chunks of an image. chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk. - if (chunk_list == NULL) chunk_list = &mux->unknown_; if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err; + if (id == WEBP_CHUNK_VP8X) { // grab global specs + mux->canvas_width_ = GetLE24(data + 12) + 1; + mux->canvas_height_ = GetLE24(data + 15) + 1; + } break; } data += data_size; @@ -262,35 +286,73 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, //------------------------------------------------------------------------------ // Get API(s). -WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) { - WebPData data; +// Validates that the given mux has a single image. +static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) { + const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE); + const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF); - if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT; - *flags = 0; + if (num_images == 0) { + // No images in mux. + return WEBP_MUX_NOT_FOUND; + } else if (num_images == 1 && num_frames == 0) { + // Valid case (single image). + return WEBP_MUX_OK; + } else { + // Frame case OR an invalid mux. + return WEBP_MUX_INVALID_ARGUMENT; + } +} + +// Get the canvas width, height and flags after validating that VP8X/VP8/VP8L +// chunk and canvas size are valid. +static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux, + int* width, int* height, uint32_t* flags) { + int w, h; + uint32_t f = 0; + WebPData data; + assert(mux != NULL); // Check if VP8X chunk is present. if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) { - if (data.size < CHUNK_SIZE_BYTES) return WEBP_MUX_BAD_DATA; - *flags = GetLE32(data.bytes); // All OK. Fill up flags. + if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA; + f = GetLE32(data.bytes + 0); + w = GetLE24(data.bytes + 4) + 1; + h = GetLE24(data.bytes + 7) + 1; } else { - WebPMuxError err = MuxValidateForImage(mux); // Check for single image. - if (err != WEBP_MUX_OK) return err; - if (MuxHasLosslessImages(mux->images_)) { - const WebPData* const vp8l_data = &mux->images_->img_->data_; - int has_alpha = 0; - if (!VP8LGetInfo(vp8l_data->bytes, vp8l_data->size, NULL, NULL, - &has_alpha)) { - return WEBP_MUX_BAD_DATA; - } - if (has_alpha) { - *flags = ALPHA_FLAG; - } + const WebPMuxImage* const wpi = mux->images_; + // Grab user-forced canvas size as default. + w = mux->canvas_width_; + h = mux->canvas_height_; + if (w == 0 && h == 0 && ValidateForSingleImage(mux) == WEBP_MUX_OK) { + // single image and not forced canvas size => use dimension of first frame + assert(wpi != NULL); + w = wpi->width_; + h = wpi->height_; + } + if (wpi != NULL) { + if (wpi->has_alpha_) f |= ALPHA_FLAG; } } + if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA; + if (width != NULL) *width = w; + if (height != NULL) *height = h; + if (flags != NULL) *flags = f; return WEBP_MUX_OK; } +WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width, int* height) { + if (mux == NULL || width == NULL || height == NULL) { + return WEBP_MUX_INVALID_ARGUMENT; + } + return MuxGetCanvasInfo(mux, width, height, NULL); +} + +WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) { + if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT; + return MuxGetCanvasInfo(mux, NULL, NULL, flags); +} + static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width, int height, uint32_t flags) { const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; @@ -314,25 +376,17 @@ static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi, const int need_vp8x = (wpi->alpha_ != NULL); const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0; const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0; - // Note: No need to output ANMF/FRGM chunk for a single image. + // Note: No need to output ANMF chunk for a single image. const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size + ChunkDiskSize(wpi->img_); - uint8_t* const data = (uint8_t*)malloc(size); + uint8_t* const data = (uint8_t*)WebPSafeMalloc(1ULL, size); if (data == NULL) return WEBP_MUX_MEMORY_ERROR; // Main RIFF header. dst = MuxEmitRiffHeader(data, size); if (need_vp8x) { - int w, h; - WebPMuxError err; - assert(wpi->img_ != NULL); - err = MuxGetImageWidthHeight(wpi->img_, &w, &h); - if (err != WEBP_MUX_OK) { - free(data); - return err; - } - dst = EmitVP8XChunk(dst, w, h, ALPHA_FLAG); // VP8X. + dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG); // VP8X. dst = ChunkListEmit(wpi->alpha_, dst); // ALPH. } @@ -372,30 +426,32 @@ static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi, info->x_offset = 0; info->y_offset = 0; info->duration = 1; + info->dispose_method = WEBP_MUX_DISPOSE_NONE; + info->blend_method = WEBP_MUX_BLEND; // Extract data for related fields. info->id = ChunkGetIdFromTag(wpi->img_->tag_); return SynthesizeBitstream(wpi, &info->bitstream); } -static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi, - WebPMuxFrameInfo* const frame) { +static WebPMuxError MuxGetFrameInternal(const WebPMuxImage* const wpi, + WebPMuxFrameInfo* const frame) { const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag); - const CHUNK_INDEX idx = is_frame ? IDX_ANMF : IDX_FRGM; - const WebPData* frame_frgm_data; -#ifndef WEBP_EXPERIMENTAL_FEATURES + const WebPData* frame_data; if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT; -#endif assert(wpi->header_ != NULL); // Already checked by WebPMuxGetFrame(). - // Get frame/fragment chunk. - frame_frgm_data = &wpi->header_->data_; - if (frame_frgm_data->size < kChunks[idx].size) return WEBP_MUX_BAD_DATA; + // Get frame chunk. + frame_data = &wpi->header_->data_; + if (frame_data->size < kChunks[IDX_ANMF].size) return WEBP_MUX_BAD_DATA; // Extract info. - frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0); - frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3); - frame->duration = is_frame ? GetLE24(frame_frgm_data->bytes + 12) : 1; - frame->dispose_method = - is_frame ? (WebPMuxAnimDispose)(frame_frgm_data->bytes[15] & 1) - : WEBP_MUX_DISPOSE_NONE; + frame->x_offset = 2 * GetLE24(frame_data->bytes + 0); + frame->y_offset = 2 * GetLE24(frame_data->bytes + 3); + { + const uint8_t bits = frame_data->bytes[15]; + frame->duration = GetLE24(frame_data->bytes + 12); + frame->dispose_method = + (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE; + frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND; + } frame->id = ChunkGetIdFromTag(wpi->header_->tag_); return SynthesizeBitstream(wpi, &frame->bitstream); } @@ -418,7 +474,7 @@ WebPMuxError WebPMuxGetFrame( if (wpi->header_ == NULL) { return MuxGetImageInternal(wpi, frame); } else { - return MuxGetFrameFragmentInternal(wpi, frame); + return MuxGetFrameInternal(wpi, frame); } } @@ -442,7 +498,7 @@ WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux, static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) { int i; for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) { - if (id == kChunks[i].id) return i; + if (id == kChunks[i].id) return (CHUNK_INDEX)i; } return IDX_NIL; } @@ -470,19 +526,11 @@ WebPMuxError WebPMuxNumChunks(const WebPMux* mux, *num_elements = MuxImageCount(mux->images_, id); } else { WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id); - if (chunk_list == NULL) { - *num_elements = 0; - } else { - const CHUNK_INDEX idx = ChunkGetIndexFromId(id); - *num_elements = CountChunks(*chunk_list, kChunks[idx].tag); - } + const CHUNK_INDEX idx = ChunkGetIndexFromId(id); + *num_elements = CountChunks(*chunk_list, kChunks[idx].tag); } return WEBP_MUX_OK; } //------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/utils/Makefile.am b/3rdparty/libwebp/utils/Makefile.am new file mode 100644 index 0000000000..e346c0b430 --- /dev/null +++ b/3rdparty/libwebp/utils/Makefile.am @@ -0,0 +1,50 @@ +noinst_LTLIBRARIES = libwebputils.la + +if BUILD_LIBWEBPDECODER + noinst_LTLIBRARIES += libwebputilsdecode.la +endif + +common_HEADERS = ../webp/types.h +commondir = $(includedir)/webp + +noinst_HEADERS = +noinst_HEADERS += ../dsp/dsp.h +noinst_HEADERS += ../webp/decode.h +noinst_HEADERS += ../webp/encode.h +noinst_HEADERS += ../webp/format_constants.h + +COMMON_SOURCES = +COMMON_SOURCES += bit_reader_utils.c +COMMON_SOURCES += bit_reader_utils.h +COMMON_SOURCES += bit_reader_inl_utils.h +COMMON_SOURCES += color_cache_utils.c +COMMON_SOURCES += color_cache_utils.h +COMMON_SOURCES += endian_inl_utils.h +COMMON_SOURCES += filters_utils.c +COMMON_SOURCES += filters_utils.h +COMMON_SOURCES += huffman_utils.c +COMMON_SOURCES += huffman_utils.h +COMMON_SOURCES += quant_levels_dec_utils.c +COMMON_SOURCES += quant_levels_dec_utils.h +COMMON_SOURCES += rescaler_utils.c +COMMON_SOURCES += rescaler_utils.h +COMMON_SOURCES += random_utils.c +COMMON_SOURCES += random_utils.h +COMMON_SOURCES += thread_utils.c +COMMON_SOURCES += thread_utils.h +COMMON_SOURCES += utils.c +COMMON_SOURCES += utils.h + +ENC_SOURCES = +ENC_SOURCES += bit_writer_utils.c +ENC_SOURCES += bit_writer_utils.h +ENC_SOURCES += huffman_encode_utils.c +ENC_SOURCES += huffman_encode_utils.h +ENC_SOURCES += quant_levels_utils.c +ENC_SOURCES += quant_levels_utils.h + +libwebputils_la_SOURCES = $(COMMON_SOURCES) $(ENC_SOURCES) + +if BUILD_LIBWEBPDECODER + libwebputilsdecode_la_SOURCES = $(COMMON_SOURCES) +endif diff --git a/3rdparty/libwebp/utils/bit_reader.c b/3rdparty/libwebp/utils/bit_reader.c deleted file mode 100644 index ab7a8273ea..0000000000 --- a/3rdparty/libwebp/utils/bit_reader.c +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2010 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Boolean decoder -// -// Author: Skal (pascal.massimino@gmail.com) - -#include "./bit_reader.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#ifndef USE_RIGHT_JUSTIFY -#define MK(X) (((range_t)(X) << (BITS)) | (MASK)) -#else -#define MK(X) ((range_t)(X)) -#endif - -//------------------------------------------------------------------------------ -// VP8BitReader - -void VP8InitBitReader(VP8BitReader* const br, - const uint8_t* const start, const uint8_t* const end) { - assert(br != NULL); - assert(start != NULL); - assert(start <= end); - br->range_ = MK(255 - 1); - br->buf_ = start; - br->buf_end_ = end; - br->value_ = 0; - br->bits_ = -8; // to load the very first 8bits - br->eof_ = 0; -} - -const uint8_t kVP8Log2Range[128] = { - 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0 -}; - -// range = (range << kVP8Log2Range[range]) + trailing 1's -const range_t kVP8NewRange[128] = { - MK(127), MK(127), MK(191), MK(127), MK(159), MK(191), MK(223), MK(127), - MK(143), MK(159), MK(175), MK(191), MK(207), MK(223), MK(239), MK(127), - MK(135), MK(143), MK(151), MK(159), MK(167), MK(175), MK(183), MK(191), - MK(199), MK(207), MK(215), MK(223), MK(231), MK(239), MK(247), MK(127), - MK(131), MK(135), MK(139), MK(143), MK(147), MK(151), MK(155), MK(159), - MK(163), MK(167), MK(171), MK(175), MK(179), MK(183), MK(187), MK(191), - MK(195), MK(199), MK(203), MK(207), MK(211), MK(215), MK(219), MK(223), - MK(227), MK(231), MK(235), MK(239), MK(243), MK(247), MK(251), MK(127), - MK(129), MK(131), MK(133), MK(135), MK(137), MK(139), MK(141), MK(143), - MK(145), MK(147), MK(149), MK(151), MK(153), MK(155), MK(157), MK(159), - MK(161), MK(163), MK(165), MK(167), MK(169), MK(171), MK(173), MK(175), - MK(177), MK(179), MK(181), MK(183), MK(185), MK(187), MK(189), MK(191), - MK(193), MK(195), MK(197), MK(199), MK(201), MK(203), MK(205), MK(207), - MK(209), MK(211), MK(213), MK(215), MK(217), MK(219), MK(221), MK(223), - MK(225), MK(227), MK(229), MK(231), MK(233), MK(235), MK(237), MK(239), - MK(241), MK(243), MK(245), MK(247), MK(249), MK(251), MK(253), MK(127) -}; - -#undef MK - -void VP8LoadFinalBytes(VP8BitReader* const br) { - assert(br != NULL && br->buf_ != NULL); - // Only read 8bits at a time - if (br->buf_ < br->buf_end_) { -#ifndef USE_RIGHT_JUSTIFY - br->value_ |= (bit_t)(*br->buf_++) << ((BITS) - 8 - br->bits_); -#else - br->value_ = (bit_t)(*br->buf_++) | (br->value_ << 8); -#endif - br->bits_ += 8; - } else if (!br->eof_) { -#ifdef USE_RIGHT_JUSTIFY - // These are not strictly needed, but it makes the behaviour - // consistent for both USE_RIGHT_JUSTIFY and !USE_RIGHT_JUSTIFY. - br->value_ <<= 8; - br->bits_ += 8; -#endif - br->eof_ = 1; - } -} - -//------------------------------------------------------------------------------ -// Higher-level calls - -uint32_t VP8GetValue(VP8BitReader* const br, int bits) { - uint32_t v = 0; - while (bits-- > 0) { - v |= VP8GetBit(br, 0x80) << bits; - } - return v; -} - -int32_t VP8GetSignedValue(VP8BitReader* const br, int bits) { - const int value = VP8GetValue(br, bits); - return VP8Get(br) ? -value : value; -} - -//------------------------------------------------------------------------------ -// VP8LBitReader - -#define MAX_NUM_BIT_READ 25 - -#define LBITS 64 // Number of bits prefetched. -#define WBITS 32 // Minimum number of bytes needed after VP8LFillBitWindow. -#define LOG8_WBITS 4 // Number of bytes needed to store WBITS bits. - -static const uint32_t kBitMask[MAX_NUM_BIT_READ] = { - 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, - 65535, 131071, 262143, 524287, 1048575, 2097151, 4194303, 8388607, 16777215 -}; - -void VP8LInitBitReader(VP8LBitReader* const br, - const uint8_t* const start, - size_t length) { - size_t i; - assert(br != NULL); - assert(start != NULL); - assert(length < 0xfffffff8u); // can't happen with a RIFF chunk. - - br->buf_ = start; - br->len_ = length; - br->val_ = 0; - br->pos_ = 0; - br->bit_pos_ = 0; - br->eos_ = 0; - br->error_ = 0; - for (i = 0; i < sizeof(br->val_) && i < br->len_; ++i) { - br->val_ |= ((vp8l_val_t)br->buf_[br->pos_]) << (8 * i); - ++br->pos_; - } -} - -void VP8LBitReaderSetBuffer(VP8LBitReader* const br, - const uint8_t* const buf, size_t len) { - assert(br != NULL); - assert(buf != NULL); - assert(len < 0xfffffff8u); // can't happen with a RIFF chunk. - br->eos_ = (br->pos_ >= len); - br->buf_ = buf; - br->len_ = len; -} - -// If not at EOS, reload up to LBITS byte-by-byte -static void ShiftBytes(VP8LBitReader* const br) { - while (br->bit_pos_ >= 8 && br->pos_ < br->len_) { - br->val_ >>= 8; - br->val_ |= ((vp8l_val_t)br->buf_[br->pos_]) << (LBITS - 8); - ++br->pos_; - br->bit_pos_ -= 8; - } -} - -void VP8LFillBitWindow(VP8LBitReader* const br) { - if (br->bit_pos_ >= WBITS) { -#if (defined(__x86_64__) || defined(_M_X64)) - if (br->pos_ + sizeof(br->val_) < br->len_) { - br->val_ >>= WBITS; - br->bit_pos_ -= WBITS; - // The expression below needs a little-endian arch to work correctly. - // This gives a large speedup for decoding speed. - br->val_ |= *(const vp8l_val_t*)(br->buf_ + br->pos_) << (LBITS - WBITS); - br->pos_ += LOG8_WBITS; - return; - } -#endif - ShiftBytes(br); // Slow path. - if (br->pos_ == br->len_ && br->bit_pos_ == LBITS) { - br->eos_ = 1; - } - } -} - -uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits) { - assert(n_bits >= 0); - // Flag an error if end_of_stream or n_bits is more than allowed limit. - if (!br->eos_ && n_bits < MAX_NUM_BIT_READ) { - const uint32_t val = - (uint32_t)(br->val_ >> br->bit_pos_) & kBitMask[n_bits]; - const int new_bits = br->bit_pos_ + n_bits; - br->bit_pos_ = new_bits; - // If this read is going to cross the read buffer, set the eos flag. - if (br->pos_ == br->len_) { - if (new_bits >= LBITS) { - br->eos_ = 1; - } - } - ShiftBytes(br); - return val; - } else { - br->error_ = 1; - return 0; - } -} - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/utils/bit_reader.h b/3rdparty/libwebp/utils/bit_reader.h deleted file mode 100644 index 588222b3be..0000000000 --- a/3rdparty/libwebp/utils/bit_reader.h +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright 2010 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Boolean decoder -// -// Author: Skal (pascal.massimino@gmail.com) -// Vikas Arora (vikaas.arora@gmail.com) - -#ifndef WEBP_UTILS_BIT_READER_H_ -#define WEBP_UTILS_BIT_READER_H_ - -#include -#ifdef _MSC_VER -#include // _byteswap_ulong -#endif -#include // For memcpy -#include "../webp/types.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -// The Boolean decoder needs to maintain infinite precision on the value_ field. -// However, since range_ is only 8bit, we only need an active window of 8 bits -// for value_. Left bits (MSB) gets zeroed and shifted away when value_ falls -// below 128, range_ is updated, and fresh bits read from the bitstream are -// brought in as LSB. -// To avoid reading the fresh bits one by one (slow), we cache a few of them -// ahead (actually, we cache BITS of them ahead. See below). There's two -// strategies regarding how to shift these looked-ahead fresh bits into the -// 8bit window of value_: either we shift them in, while keeping the position of -// the window fixed. Or we slide the window to the right while keeping the cache -// bits at a fixed, right-justified, position. -// -// Example, for BITS=16: here is the content of value_ for both strategies: -// -// !USE_RIGHT_JUSTIFY || USE_RIGHT_JUSTIFY -// || -// <- 8b -><- 8b -><- BITS bits -> || <- 8b+3b -><- 8b -><- 13 bits -> -// [unused][value_][cached bits][0] || [unused...][value_][cached bits] -// [........00vvvvvvBBBBBBBBBBBBB000]LSB || [...........00vvvvvvBBBBBBBBBBBBB] -// || -// After calling VP8Shift(), where we need to shift away two zeros: -// [........vvvvvvvvBBBBBBBBBBB00000]LSB || [.............vvvvvvvvBBBBBBBBBBB] -// || -// Just before we need to call VP8LoadNewBytes(), the situation is: -// [........vvvvvv000000000000000000]LSB || [..........................vvvvvv] -// || -// And just after calling VP8LoadNewBytes(): -// [........vvvvvvvvBBBBBBBBBBBBBBBB]LSB || [........vvvvvvvvBBBBBBBBBBBBBBBB] -// -// -> we're back to height active 'value_' bits (marked 'v') and BITS cached -// bits (marked 'B') -// -// The right-justify strategy tends to use less shifts and is often faster. - -//------------------------------------------------------------------------------ -// BITS can be any multiple of 8 from 8 to 56 (inclusive). -// Pick values that fit natural register size. - -#if !defined(WEBP_REFERENCE_IMPLEMENTATION) - -#define USE_RIGHT_JUSTIFY - -#if defined(__i386__) || defined(_M_IX86) // x86 32bit -#define BITS 16 -#elif defined(__x86_64__) || defined(_M_X64) // x86 64bit -#define BITS 56 -#elif defined(__arm__) || defined(_M_ARM) // ARM -#define BITS 24 -#else // reasonable default -#define BITS 24 -#endif - -#else // reference choices - -#define USE_RIGHT_JUSTIFY -#define BITS 8 - -#endif - -//------------------------------------------------------------------------------ -// Derived types and constants - -// bit_t = natural register type -// lbit_t = natural type for memory I/O - -#if (BITS > 32) -typedef uint64_t bit_t; -typedef uint64_t lbit_t; -#elif (BITS == 32) -typedef uint64_t bit_t; -typedef uint32_t lbit_t; -#elif (BITS == 24) -typedef uint32_t bit_t; -typedef uint32_t lbit_t; -#elif (BITS == 16) -typedef uint32_t bit_t; -typedef uint16_t lbit_t; -#else -typedef uint32_t bit_t; -typedef uint8_t lbit_t; -#endif - -#ifndef USE_RIGHT_JUSTIFY -typedef bit_t range_t; // type for storing range_ -#define MASK ((((bit_t)1) << (BITS)) - 1) -#else -typedef uint32_t range_t; // range_ only uses 8bits here. No need for bit_t. -#endif - -//------------------------------------------------------------------------------ -// Bitreader - -typedef struct VP8BitReader VP8BitReader; -struct VP8BitReader { - const uint8_t* buf_; // next byte to be read - const uint8_t* buf_end_; // end of read buffer - int eof_; // true if input is exhausted - - // boolean decoder - range_t range_; // current range minus 1. In [127, 254] interval. - bit_t value_; // current value - int bits_; // number of valid bits left -}; - -// Initialize the bit reader and the boolean decoder. -void VP8InitBitReader(VP8BitReader* const br, - const uint8_t* const start, const uint8_t* const end); - -// return the next value made of 'num_bits' bits -uint32_t VP8GetValue(VP8BitReader* const br, int num_bits); -static WEBP_INLINE uint32_t VP8Get(VP8BitReader* const br) { - return VP8GetValue(br, 1); -} - -// return the next value with sign-extension. -int32_t VP8GetSignedValue(VP8BitReader* const br, int num_bits); - -// Read a bit with proba 'prob'. Speed-critical function! -extern const uint8_t kVP8Log2Range[128]; -extern const range_t kVP8NewRange[128]; - -void VP8LoadFinalBytes(VP8BitReader* const br); // special case for the tail - -static WEBP_INLINE void VP8LoadNewBytes(VP8BitReader* const br) { - assert(br != NULL && br->buf_ != NULL); - // Read 'BITS' bits at a time if possible. - if (br->buf_ + sizeof(lbit_t) <= br->buf_end_) { - // convert memory type to register type (with some zero'ing!) - bit_t bits; - lbit_t in_bits = *(lbit_t*)br->buf_; - br->buf_ += (BITS) >> 3; -#if !defined(__BIG_ENDIAN__) -#if (BITS > 32) -// gcc 4.3 has builtin functions for swap32/swap64 -#if defined(__GNUC__) && \ - (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) - bits = (bit_t)__builtin_bswap64(in_bits); -#elif defined(_MSC_VER) - bits = (bit_t)_byteswap_uint64(in_bits); -#elif defined(__x86_64__) - __asm__ volatile("bswapq %0" : "=r"(bits) : "0"(in_bits)); -#else // generic code for swapping 64-bit values (suggested by bdb@) - bits = (bit_t)in_bits; - bits = ((bits & 0xffffffff00000000ull) >> 32) | - ((bits & 0x00000000ffffffffull) << 32); - bits = ((bits & 0xffff0000ffff0000ull) >> 16) | - ((bits & 0x0000ffff0000ffffull) << 16); - bits = ((bits & 0xff00ff00ff00ff00ull) >> 8) | - ((bits & 0x00ff00ff00ff00ffull) << 8); -#endif - bits >>= 64 - BITS; -#elif (BITS >= 24) -#if defined(__i386__) || defined(__x86_64__) - __asm__ volatile("bswap %k0" : "=r"(in_bits) : "0"(in_bits)); - bits = (bit_t)in_bits; // 24b/32b -> 32b/64b zero-extension -#elif defined(_MSC_VER) - bits = (bit_t)_byteswap_ulong(in_bits); -#else - bits = (bit_t)(in_bits >> 24) | ((in_bits >> 8) & 0xff00) - | ((in_bits << 8) & 0xff0000) | (in_bits << 24); -#endif // x86 - bits >>= (32 - BITS); -#elif (BITS == 16) - // gcc will recognize a 'rorw $8, ...' here: - bits = (bit_t)(in_bits >> 8) | ((in_bits & 0xff) << 8); -#else // BITS == 8 - bits = (bit_t)in_bits; -#endif -#else // BIG_ENDIAN - bits = (bit_t)in_bits; - if (BITS != 8 * sizeof(bit_t)) bits >>= (8 * sizeof(bit_t) - BITS); -#endif -#ifndef USE_RIGHT_JUSTIFY - br->value_ |= bits << (-br->bits_); -#else - br->value_ = bits | (br->value_ << (BITS)); -#endif - br->bits_ += (BITS); - } else { - VP8LoadFinalBytes(br); // no need to be inlined - } -} - -static WEBP_INLINE int VP8BitUpdate(VP8BitReader* const br, range_t split) { - if (br->bits_ < 0) { // Make sure we have a least BITS bits in 'value_' - VP8LoadNewBytes(br); - } -#ifndef USE_RIGHT_JUSTIFY - split |= (MASK); - if (br->value_ > split) { - br->range_ -= split + 1; - br->value_ -= split + 1; - return 1; - } else { - br->range_ = split; - return 0; - } -#else - { - const int pos = br->bits_; - const range_t value = (range_t)(br->value_ >> pos); - if (value > split) { - br->range_ -= split + 1; - br->value_ -= (bit_t)(split + 1) << pos; - return 1; - } else { - br->range_ = split; - return 0; - } - } -#endif -} - -static WEBP_INLINE void VP8Shift(VP8BitReader* const br) { -#ifndef USE_RIGHT_JUSTIFY - // range_ is in [0..127] interval here. - const bit_t idx = br->range_ >> (BITS); - const int shift = kVP8Log2Range[idx]; - br->range_ = kVP8NewRange[idx]; - br->value_ <<= shift; - br->bits_ -= shift; -#else - const int shift = kVP8Log2Range[br->range_]; - assert(br->range_ < (range_t)128); - br->range_ = kVP8NewRange[br->range_]; - br->bits_ -= shift; -#endif -} -static WEBP_INLINE int VP8GetBit(VP8BitReader* const br, int prob) { -#ifndef USE_RIGHT_JUSTIFY - // It's important to avoid generating a 64bit x 64bit multiply here. - // We just need an 8b x 8b after all. - const range_t split = - (range_t)((uint32_t)(br->range_ >> (BITS)) * prob) << ((BITS) - 8); - const int bit = VP8BitUpdate(br, split); - if (br->range_ <= (((range_t)0x7e << (BITS)) | (MASK))) { - VP8Shift(br); - } - return bit; -#else - const range_t split = (br->range_ * prob) >> 8; - const int bit = VP8BitUpdate(br, split); - if (br->range_ <= (range_t)0x7e) { - VP8Shift(br); - } - return bit; -#endif -} - -static WEBP_INLINE int VP8GetSigned(VP8BitReader* const br, int v) { - const range_t split = (br->range_ >> 1); - const int bit = VP8BitUpdate(br, split); - VP8Shift(br); - return bit ? -v : v; -} - - -// ----------------------------------------------------------------------------- -// Bitreader for lossless format - -typedef uint64_t vp8l_val_t; // right now, this bit-reader can only use 64bit. - -typedef struct { - vp8l_val_t val_; // pre-fetched bits - const uint8_t* buf_; // input byte buffer - size_t len_; // buffer length - size_t pos_; // byte position in buf_ - int bit_pos_; // current bit-reading position in val_ - int eos_; // bitstream is finished - int error_; // an error occurred (buffer overflow attempt...) -} VP8LBitReader; - -void VP8LInitBitReader(VP8LBitReader* const br, - const uint8_t* const start, - size_t length); - -// Sets a new data buffer. -void VP8LBitReaderSetBuffer(VP8LBitReader* const br, - const uint8_t* const buffer, size_t length); - -// Reads the specified number of bits from Read Buffer. -// Flags an error in case end_of_stream or n_bits is more than allowed limit. -// Flags eos if this read attempt is going to cross the read buffer. -uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits); - -// Return the prefetched bits, so they can be looked up. -static WEBP_INLINE uint32_t VP8LPrefetchBits(VP8LBitReader* const br) { - return (uint32_t)(br->val_ >> br->bit_pos_); -} - -// Discard 'num_bits' bits from the cache. -static WEBP_INLINE void VP8LDiscardBits(VP8LBitReader* const br, int num_bits) { - br->bit_pos_ += num_bits; -} - -// Advances the Read buffer by 4 bytes to make room for reading next 32 bits. -void VP8LFillBitWindow(VP8LBitReader* const br); - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif - -#endif /* WEBP_UTILS_BIT_READER_H_ */ diff --git a/3rdparty/libwebp/utils/bit_reader_inl_utils.h b/3rdparty/libwebp/utils/bit_reader_inl_utils.h new file mode 100644 index 0000000000..fd7fb0446c --- /dev/null +++ b/3rdparty/libwebp/utils/bit_reader_inl_utils.h @@ -0,0 +1,190 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Specific inlined methods for boolean decoder [VP8GetBit() ...] +// This file should be included by the .c sources that actually need to call +// these methods. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_BIT_READER_INL_H_ +#define WEBP_UTILS_BIT_READER_INL_H_ + +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" +#endif + +#include // for memcpy + +#include "../dsp/dsp.h" +#include "./bit_reader_utils.h" +#include "./endian_inl_utils.h" +#include "./utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Derived type lbit_t = natural type for memory I/O + +#if (BITS > 32) +typedef uint64_t lbit_t; +#elif (BITS > 16) +typedef uint32_t lbit_t; +#elif (BITS > 8) +typedef uint16_t lbit_t; +#else +typedef uint8_t lbit_t; +#endif + +extern const uint8_t kVP8Log2Range[128]; +extern const uint8_t kVP8NewRange[128]; + +// special case for the tail byte-reading +void VP8LoadFinalBytes(VP8BitReader* const br); + +//------------------------------------------------------------------------------ +// Inlined critical functions + +// makes sure br->value_ has at least BITS bits worth of data +static WEBP_UBSAN_IGNORE_UNDEF WEBP_INLINE +void VP8LoadNewBytes(VP8BitReader* const br) { + assert(br != NULL && br->buf_ != NULL); + // Read 'BITS' bits at a time if possible. + if (br->buf_ < br->buf_max_) { + // convert memory type to register type (with some zero'ing!) + bit_t bits; +#if defined(WEBP_USE_MIPS32) + // This is needed because of un-aligned read. + lbit_t in_bits; + lbit_t* p_buf_ = (lbit_t*)br->buf_; + __asm__ volatile( + ".set push \n\t" + ".set at \n\t" + ".set macro \n\t" + "ulw %[in_bits], 0(%[p_buf_]) \n\t" + ".set pop \n\t" + : [in_bits]"=r"(in_bits) + : [p_buf_]"r"(p_buf_) + : "memory", "at" + ); +#else + lbit_t in_bits; + memcpy(&in_bits, br->buf_, sizeof(in_bits)); +#endif + br->buf_ += BITS >> 3; +#if !defined(WORDS_BIGENDIAN) +#if (BITS > 32) + bits = BSwap64(in_bits); + bits >>= 64 - BITS; +#elif (BITS >= 24) + bits = BSwap32(in_bits); + bits >>= (32 - BITS); +#elif (BITS == 16) + bits = BSwap16(in_bits); +#else // BITS == 8 + bits = (bit_t)in_bits; +#endif // BITS > 32 +#else // WORDS_BIGENDIAN + bits = (bit_t)in_bits; + if (BITS != 8 * sizeof(bit_t)) bits >>= (8 * sizeof(bit_t) - BITS); +#endif + br->value_ = bits | (br->value_ << BITS); + br->bits_ += BITS; + } else { + VP8LoadFinalBytes(br); // no need to be inlined + } +} + +// Read a bit with proba 'prob'. Speed-critical function! +static WEBP_INLINE int VP8GetBit(VP8BitReader* const br, int prob) { + // Don't move this declaration! It makes a big speed difference to store + // 'range' *before* calling VP8LoadNewBytes(), even if this function doesn't + // alter br->range_ value. + range_t range = br->range_; + if (br->bits_ < 0) { + VP8LoadNewBytes(br); + } + { + const int pos = br->bits_; + const range_t split = (range * prob) >> 8; + const range_t value = (range_t)(br->value_ >> pos); + const int bit = (value > split); + if (bit) { + range -= split; + br->value_ -= (bit_t)(split + 1) << pos; + } else { + range = split + 1; + } + { + const int shift = 7 ^ BitsLog2Floor(range); + range <<= shift; + br->bits_ -= shift; + } + br->range_ = range - 1; + return bit; + } +} + +// simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here) +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +int VP8GetSigned(VP8BitReader* const br, int v) { + if (br->bits_ < 0) { + VP8LoadNewBytes(br); + } + { + const int pos = br->bits_; + const range_t split = br->range_ >> 1; + const range_t value = (range_t)(br->value_ >> pos); + const int32_t mask = (int32_t)(split - value) >> 31; // -1 or 0 + br->bits_ -= 1; + br->range_ += mask; + br->range_ |= 1; + br->value_ -= (bit_t)((split + 1) & mask) << pos; + return (v ^ mask) - mask; + } +} + +static WEBP_INLINE int VP8GetBitAlt(VP8BitReader* const br, int prob) { + // Don't move this declaration! It makes a big speed difference to store + // 'range' *before* calling VP8LoadNewBytes(), even if this function doesn't + // alter br->range_ value. + range_t range = br->range_; + if (br->bits_ < 0) { + VP8LoadNewBytes(br); + } + { + const int pos = br->bits_; + const range_t split = (range * prob) >> 8; + const range_t value = (range_t)(br->value_ >> pos); + int bit; // Don't use 'const int bit = (value > split);", it's slower. + if (value > split) { + range -= split + 1; + br->value_ -= (bit_t)(split + 1) << pos; + bit = 1; + } else { + range = split; + bit = 0; + } + if (range <= (range_t)0x7e) { + const int shift = kVP8Log2Range[range]; + range = kVP8NewRange[range]; + br->bits_ -= shift; + } + br->range_ = range; + return bit; + } +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_BIT_READER_INL_H_ diff --git a/3rdparty/libwebp/utils/bit_reader_utils.c b/3rdparty/libwebp/utils/bit_reader_utils.c new file mode 100644 index 0000000000..c3157e8fe8 --- /dev/null +++ b/3rdparty/libwebp/utils/bit_reader_utils.c @@ -0,0 +1,222 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Boolean decoder non-inlined methods +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" +#endif + +#include "./bit_reader_inl_utils.h" +#include "../utils/utils.h" + +//------------------------------------------------------------------------------ +// VP8BitReader + +void VP8BitReaderSetBuffer(VP8BitReader* const br, + const uint8_t* const start, + size_t size) { + br->buf_ = start; + br->buf_end_ = start + size; + br->buf_max_ = + (size >= sizeof(lbit_t)) ? start + size - sizeof(lbit_t) + 1 + : start; +} + +void VP8InitBitReader(VP8BitReader* const br, + const uint8_t* const start, size_t size) { + assert(br != NULL); + assert(start != NULL); + assert(size < (1u << 31)); // limit ensured by format and upstream checks + br->range_ = 255 - 1; + br->value_ = 0; + br->bits_ = -8; // to load the very first 8bits + br->eof_ = 0; + VP8BitReaderSetBuffer(br, start, size); + VP8LoadNewBytes(br); +} + +void VP8RemapBitReader(VP8BitReader* const br, ptrdiff_t offset) { + if (br->buf_ != NULL) { + br->buf_ += offset; + br->buf_end_ += offset; + br->buf_max_ += offset; + } +} + +const uint8_t kVP8Log2Range[128] = { + 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0 +}; + +// range = ((range - 1) << kVP8Log2Range[range]) + 1 +const uint8_t kVP8NewRange[128] = { + 127, 127, 191, 127, 159, 191, 223, 127, + 143, 159, 175, 191, 207, 223, 239, 127, + 135, 143, 151, 159, 167, 175, 183, 191, + 199, 207, 215, 223, 231, 239, 247, 127, + 131, 135, 139, 143, 147, 151, 155, 159, + 163, 167, 171, 175, 179, 183, 187, 191, + 195, 199, 203, 207, 211, 215, 219, 223, + 227, 231, 235, 239, 243, 247, 251, 127, + 129, 131, 133, 135, 137, 139, 141, 143, + 145, 147, 149, 151, 153, 155, 157, 159, + 161, 163, 165, 167, 169, 171, 173, 175, + 177, 179, 181, 183, 185, 187, 189, 191, + 193, 195, 197, 199, 201, 203, 205, 207, + 209, 211, 213, 215, 217, 219, 221, 223, + 225, 227, 229, 231, 233, 235, 237, 239, + 241, 243, 245, 247, 249, 251, 253, 127 +}; + +void VP8LoadFinalBytes(VP8BitReader* const br) { + assert(br != NULL && br->buf_ != NULL); + // Only read 8bits at a time + if (br->buf_ < br->buf_end_) { + br->bits_ += 8; + br->value_ = (bit_t)(*br->buf_++) | (br->value_ << 8); + } else if (!br->eof_) { + br->value_ <<= 8; + br->bits_ += 8; + br->eof_ = 1; + } else { + br->bits_ = 0; // This is to avoid undefined behaviour with shifts. + } +} + +//------------------------------------------------------------------------------ +// Higher-level calls + +uint32_t VP8GetValue(VP8BitReader* const br, int bits) { + uint32_t v = 0; + while (bits-- > 0) { + v |= VP8GetBit(br, 0x80) << bits; + } + return v; +} + +int32_t VP8GetSignedValue(VP8BitReader* const br, int bits) { + const int value = VP8GetValue(br, bits); + return VP8Get(br) ? -value : value; +} + +//------------------------------------------------------------------------------ +// VP8LBitReader + +#define VP8L_LOG8_WBITS 4 // Number of bytes needed to store VP8L_WBITS bits. + +#if defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || \ + defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64__) || defined(_M_X64) +#define VP8L_USE_FAST_LOAD +#endif + +static const uint32_t kBitMask[VP8L_MAX_NUM_BIT_READ + 1] = { + 0, + 0x000001, 0x000003, 0x000007, 0x00000f, + 0x00001f, 0x00003f, 0x00007f, 0x0000ff, + 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, + 0x001fff, 0x003fff, 0x007fff, 0x00ffff, + 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff, + 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff +}; + +void VP8LInitBitReader(VP8LBitReader* const br, const uint8_t* const start, + size_t length) { + size_t i; + vp8l_val_t value = 0; + assert(br != NULL); + assert(start != NULL); + assert(length < 0xfffffff8u); // can't happen with a RIFF chunk. + + br->len_ = length; + br->val_ = 0; + br->bit_pos_ = 0; + br->eos_ = 0; + + if (length > sizeof(br->val_)) { + length = sizeof(br->val_); + } + for (i = 0; i < length; ++i) { + value |= (vp8l_val_t)start[i] << (8 * i); + } + br->val_ = value; + br->pos_ = length; + br->buf_ = start; +} + +void VP8LBitReaderSetBuffer(VP8LBitReader* const br, + const uint8_t* const buf, size_t len) { + assert(br != NULL); + assert(buf != NULL); + assert(len < 0xfffffff8u); // can't happen with a RIFF chunk. + br->buf_ = buf; + br->len_ = len; + // pos_ > len_ should be considered a param error. + br->eos_ = (br->pos_ > br->len_) || VP8LIsEndOfStream(br); +} + +static void VP8LSetEndOfStream(VP8LBitReader* const br) { + br->eos_ = 1; + br->bit_pos_ = 0; // To avoid undefined behaviour with shifts. +} + +// If not at EOS, reload up to VP8L_LBITS byte-by-byte +static void ShiftBytes(VP8LBitReader* const br) { + while (br->bit_pos_ >= 8 && br->pos_ < br->len_) { + br->val_ >>= 8; + br->val_ |= ((vp8l_val_t)br->buf_[br->pos_]) << (VP8L_LBITS - 8); + ++br->pos_; + br->bit_pos_ -= 8; + } + if (VP8LIsEndOfStream(br)) { + VP8LSetEndOfStream(br); + } +} + +void VP8LDoFillBitWindow(VP8LBitReader* const br) { + assert(br->bit_pos_ >= VP8L_WBITS); +#if defined(VP8L_USE_FAST_LOAD) + if (br->pos_ + sizeof(br->val_) < br->len_) { + br->val_ >>= VP8L_WBITS; + br->bit_pos_ -= VP8L_WBITS; + br->val_ |= (vp8l_val_t)HToLE32(WebPMemToUint32(br->buf_ + br->pos_)) << + (VP8L_LBITS - VP8L_WBITS); + br->pos_ += VP8L_LOG8_WBITS; + return; + } +#endif + ShiftBytes(br); // Slow path. +} + +uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits) { + assert(n_bits >= 0); + // Flag an error if end_of_stream or n_bits is more than allowed limit. + if (!br->eos_ && n_bits <= VP8L_MAX_NUM_BIT_READ) { + const uint32_t val = VP8LPrefetchBits(br) & kBitMask[n_bits]; + const int new_bits = br->bit_pos_ + n_bits; + br->bit_pos_ = new_bits; + ShiftBytes(br); + return val; + } else { + VP8LSetEndOfStream(br); + return 0; + } +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/utils/bit_reader_utils.h b/3rdparty/libwebp/utils/bit_reader_utils.h new file mode 100644 index 0000000000..ec3426cd11 --- /dev/null +++ b/3rdparty/libwebp/utils/bit_reader_utils.h @@ -0,0 +1,174 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Boolean decoder +// +// Author: Skal (pascal.massimino@gmail.com) +// Vikas Arora (vikaas.arora@gmail.com) + +#ifndef WEBP_UTILS_BIT_READER_H_ +#define WEBP_UTILS_BIT_READER_H_ + +#include +#ifdef _MSC_VER +#include // _byteswap_ulong +#endif +#include "../webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// The Boolean decoder needs to maintain infinite precision on the value_ field. +// However, since range_ is only 8bit, we only need an active window of 8 bits +// for value_. Left bits (MSB) gets zeroed and shifted away when value_ falls +// below 128, range_ is updated, and fresh bits read from the bitstream are +// brought in as LSB. To avoid reading the fresh bits one by one (slow), we +// cache BITS of them ahead. The total of (BITS + 8) bits must fit into a +// natural register (with type bit_t). To fetch BITS bits from bitstream we +// use a type lbit_t. +// +// BITS can be any multiple of 8 from 8 to 56 (inclusive). +// Pick values that fit natural register size. + +#if defined(__i386__) || defined(_M_IX86) // x86 32bit +#define BITS 24 +#elif defined(__x86_64__) || defined(_M_X64) // x86 64bit +#define BITS 56 +#elif defined(__arm__) || defined(_M_ARM) // ARM +#define BITS 24 +#elif defined(__aarch64__) // ARM 64bit +#define BITS 56 +#elif defined(__mips__) // MIPS +#define BITS 24 +#else // reasonable default +#define BITS 24 +#endif + +//------------------------------------------------------------------------------ +// Derived types and constants: +// bit_t = natural register type for storing 'value_' (which is BITS+8 bits) +// range_t = register for 'range_' (which is 8bits only) + +#if (BITS > 24) +typedef uint64_t bit_t; +#else +typedef uint32_t bit_t; +#endif + +typedef uint32_t range_t; + +//------------------------------------------------------------------------------ +// Bitreader + +typedef struct VP8BitReader VP8BitReader; +struct VP8BitReader { + // boolean decoder (keep the field ordering as is!) + bit_t value_; // current value + range_t range_; // current range minus 1. In [127, 254] interval. + int bits_; // number of valid bits left + // read buffer + const uint8_t* buf_; // next byte to be read + const uint8_t* buf_end_; // end of read buffer + const uint8_t* buf_max_; // max packed-read position on buffer + int eof_; // true if input is exhausted +}; + +// Initialize the bit reader and the boolean decoder. +void VP8InitBitReader(VP8BitReader* const br, + const uint8_t* const start, size_t size); +// Sets the working read buffer. +void VP8BitReaderSetBuffer(VP8BitReader* const br, + const uint8_t* const start, size_t size); + +// Update internal pointers to displace the byte buffer by the +// relative offset 'offset'. +void VP8RemapBitReader(VP8BitReader* const br, ptrdiff_t offset); + +// return the next value made of 'num_bits' bits +uint32_t VP8GetValue(VP8BitReader* const br, int num_bits); +static WEBP_INLINE uint32_t VP8Get(VP8BitReader* const br) { + return VP8GetValue(br, 1); +} + +// return the next value with sign-extension. +int32_t VP8GetSignedValue(VP8BitReader* const br, int num_bits); + +// bit_reader_inl.h will implement the following methods: +// static WEBP_INLINE int VP8GetBit(VP8BitReader* const br, int prob) +// static WEBP_INLINE int VP8GetSigned(VP8BitReader* const br, int v) +// and should be included by the .c files that actually need them. +// This is to avoid recompiling the whole library whenever this file is touched, +// and also allowing platform-specific ad-hoc hacks. + +// ----------------------------------------------------------------------------- +// Bitreader for lossless format + +// maximum number of bits (inclusive) the bit-reader can handle: +#define VP8L_MAX_NUM_BIT_READ 24 + +#define VP8L_LBITS 64 // Number of bits prefetched (= bit-size of vp8l_val_t). +#define VP8L_WBITS 32 // Minimum number of bytes ready after VP8LFillBitWindow. + +typedef uint64_t vp8l_val_t; // right now, this bit-reader can only use 64bit. + +typedef struct { + vp8l_val_t val_; // pre-fetched bits + const uint8_t* buf_; // input byte buffer + size_t len_; // buffer length + size_t pos_; // byte position in buf_ + int bit_pos_; // current bit-reading position in val_ + int eos_; // true if a bit was read past the end of buffer +} VP8LBitReader; + +void VP8LInitBitReader(VP8LBitReader* const br, + const uint8_t* const start, + size_t length); + +// Sets a new data buffer. +void VP8LBitReaderSetBuffer(VP8LBitReader* const br, + const uint8_t* const buffer, size_t length); + +// Reads the specified number of bits from read buffer. +// Flags an error in case end_of_stream or n_bits is more than the allowed limit +// of VP8L_MAX_NUM_BIT_READ (inclusive). +// Flags eos_ if this read attempt is going to cross the read buffer. +uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits); + +// Return the prefetched bits, so they can be looked up. +static WEBP_INLINE uint32_t VP8LPrefetchBits(VP8LBitReader* const br) { + return (uint32_t)(br->val_ >> (br->bit_pos_ & (VP8L_LBITS - 1))); +} + +// Returns true if there was an attempt at reading bit past the end of +// the buffer. Doesn't set br->eos_ flag. +static WEBP_INLINE int VP8LIsEndOfStream(const VP8LBitReader* const br) { + assert(br->pos_ <= br->len_); + return br->eos_ || ((br->pos_ == br->len_) && (br->bit_pos_ > VP8L_LBITS)); +} + +// For jumping over a number of bits in the bit stream when accessed with +// VP8LPrefetchBits and VP8LFillBitWindow. +static WEBP_INLINE void VP8LSetBitPos(VP8LBitReader* const br, int val) { + br->bit_pos_ = val; + br->eos_ = VP8LIsEndOfStream(br); +} + +// Advances the read buffer by 4 bytes to make room for reading next 32 bits. +// Speed critical, but infrequent part of the code can be non-inlined. +extern void VP8LDoFillBitWindow(VP8LBitReader* const br); +static WEBP_INLINE void VP8LFillBitWindow(VP8LBitReader* const br) { + if (br->bit_pos_ >= VP8L_WBITS) VP8LDoFillBitWindow(br); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_UTILS_BIT_READER_H_ */ diff --git a/3rdparty/libwebp/utils/bit_writer.c b/3rdparty/libwebp/utils/bit_writer_utils.c similarity index 62% rename from 3rdparty/libwebp/utils/bit_writer.c rename to 3rdparty/libwebp/utils/bit_writer_utils.c index 3827a13ad1..ab0c49dce8 100644 --- a/3rdparty/libwebp/utils/bit_writer.c +++ b/3rdparty/libwebp/utils/bit_writer_utils.c @@ -15,11 +15,10 @@ #include #include // for memcpy() #include -#include "./bit_writer.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./bit_writer_utils.h" +#include "./endian_inl_utils.h" +#include "./utils.h" //------------------------------------------------------------------------------ // VP8BitWriter @@ -38,19 +37,22 @@ static int BitWriterResize(VP8BitWriter* const bw, size_t extra_size) { new_size = 2 * bw->max_pos_; if (new_size < needed_size) new_size = needed_size; if (new_size < 1024) new_size = 1024; - new_buf = (uint8_t*)malloc(new_size); + new_buf = (uint8_t*)WebPSafeMalloc(1ULL, new_size); if (new_buf == NULL) { bw->error_ = 1; return 0; } - memcpy(new_buf, bw->buf_, bw->pos_); - free(bw->buf_); + if (bw->pos_ > 0) { + assert(bw->buf_ != NULL); + memcpy(new_buf, bw->buf_, bw->pos_); + } + WebPSafeFree(bw->buf_); bw->buf_ = new_buf; bw->max_pos_ = new_size; return 1; } -static void kFlush(VP8BitWriter* const bw) { +static void Flush(VP8BitWriter* const bw) { const int s = 8 + bw->nb_bits_; const int32_t bits = bw->value_ >> s; assert(bw->nb_bits_ >= 0); @@ -116,7 +118,7 @@ int VP8PutBit(VP8BitWriter* const bw, int bit, int prob) { bw->range_ = kNewRange[bw->range_]; bw->value_ <<= shift; bw->nb_bits_ += shift; - if (bw->nb_bits_ > 0) kFlush(bw); + if (bw->nb_bits_ > 0) Flush(bw); } return bit; } @@ -133,24 +135,25 @@ int VP8PutBitUniform(VP8BitWriter* const bw, int bit) { bw->range_ = kNewRange[bw->range_]; bw->value_ <<= 1; bw->nb_bits_ += 1; - if (bw->nb_bits_ > 0) kFlush(bw); + if (bw->nb_bits_ > 0) Flush(bw); } return bit; } -void VP8PutValue(VP8BitWriter* const bw, int value, int nb_bits) { - int mask; - for (mask = 1 << (nb_bits - 1); mask; mask >>= 1) +void VP8PutBits(VP8BitWriter* const bw, uint32_t value, int nb_bits) { + uint32_t mask; + assert(nb_bits > 0 && nb_bits < 32); + for (mask = 1u << (nb_bits - 1); mask; mask >>= 1) { VP8PutBitUniform(bw, value & mask); + } } -void VP8PutSignedValue(VP8BitWriter* const bw, int value, int nb_bits) { - if (!VP8PutBitUniform(bw, value != 0)) - return; +void VP8PutSignedBits(VP8BitWriter* const bw, int value, int nb_bits) { + if (!VP8PutBitUniform(bw, value != 0)) return; if (value < 0) { - VP8PutValue(bw, ((-value) << 1) | 1, nb_bits + 1); + VP8PutBits(bw, ((-value) << 1) | 1, nb_bits + 1); } else { - VP8PutValue(bw, value << 1, nb_bits + 1); + VP8PutBits(bw, value << 1, nb_bits + 1); } } @@ -169,16 +172,16 @@ int VP8BitWriterInit(VP8BitWriter* const bw, size_t expected_size) { } uint8_t* VP8BitWriterFinish(VP8BitWriter* const bw) { - VP8PutValue(bw, 0, 9 - bw->nb_bits_); + VP8PutBits(bw, 0, 9 - bw->nb_bits_); bw->nb_bits_ = 0; // pad with zeroes - kFlush(bw); + Flush(bw); return bw->buf_; } int VP8BitWriterAppend(VP8BitWriter* const bw, const uint8_t* data, size_t size) { - assert(data); - if (bw->nb_bits_ != -8) return 0; // kFlush() must have been called + assert(data != NULL); + if (bw->nb_bits_ != -8) return 0; // Flush() must have been called if (!BitWriterResize(bw, size)) return 0; memcpy(bw->buf_ + bw->pos_, data, size); bw->pos_ += size; @@ -186,8 +189,8 @@ int VP8BitWriterAppend(VP8BitWriter* const bw, } void VP8BitWriterWipeOut(VP8BitWriter* const bw) { - if (bw) { - free(bw->buf_); + if (bw != NULL) { + WebPSafeFree(bw->buf_); memset(bw, 0, sizeof(*bw)); } } @@ -195,32 +198,39 @@ void VP8BitWriterWipeOut(VP8BitWriter* const bw) { //------------------------------------------------------------------------------ // VP8LBitWriter +// This is the minimum amount of size the memory buffer is guaranteed to grow +// when extra space is needed. +#define MIN_EXTRA_SIZE (32768ULL) + // Returns 1 on success. static int VP8LBitWriterResize(VP8LBitWriter* const bw, size_t extra_size) { uint8_t* allocated_buf; size_t allocated_size; - const size_t current_size = VP8LBitWriterNumBytes(bw); + const size_t max_bytes = bw->end_ - bw->buf_; + const size_t current_size = bw->cur_ - bw->buf_; const uint64_t size_required_64b = (uint64_t)current_size + extra_size; const size_t size_required = (size_t)size_required_64b; if (size_required != size_required_64b) { bw->error_ = 1; return 0; } - if (bw->max_bytes_ > 0 && size_required <= bw->max_bytes_) return 1; - allocated_size = (3 * bw->max_bytes_) >> 1; + if (max_bytes > 0 && size_required <= max_bytes) return 1; + allocated_size = (3 * max_bytes) >> 1; if (allocated_size < size_required) allocated_size = size_required; // make allocated size multiple of 1k allocated_size = (((allocated_size >> 10) + 1) << 10); - allocated_buf = (uint8_t*)malloc(allocated_size); + allocated_buf = (uint8_t*)WebPSafeMalloc(1ULL, allocated_size); if (allocated_buf == NULL) { bw->error_ = 1; return 0; } - memcpy(allocated_buf, bw->buf_, current_size); - free(bw->buf_); + if (current_size > 0) { + memcpy(allocated_buf, bw->buf_, current_size); + } + WebPSafeFree(bw->buf_); bw->buf_ = allocated_buf; - bw->max_bytes_ = allocated_size; - memset(allocated_buf + current_size, 0, allocated_size - current_size); + bw->cur_ = bw->buf_ + current_size; + bw->end_ = bw->buf_ + allocated_size; return 1; } @@ -229,58 +239,81 @@ int VP8LBitWriterInit(VP8LBitWriter* const bw, size_t expected_size) { return VP8LBitWriterResize(bw, expected_size); } -void VP8LBitWriterDestroy(VP8LBitWriter* const bw) { +void VP8LBitWriterWipeOut(VP8LBitWriter* const bw) { if (bw != NULL) { - free(bw->buf_); + WebPSafeFree(bw->buf_); memset(bw, 0, sizeof(*bw)); } } -void VP8LWriteBits(VP8LBitWriter* const bw, int n_bits, uint32_t bits) { - if (n_bits < 1) return; -#if !defined(__BIG_ENDIAN__) - // Technically, this branch of the code can write up to 25 bits at a time, - // but in prefix encoding, the maximum number of bits written is 18 at a time. - { - uint8_t* const p = &bw->buf_[bw->bit_pos_ >> 3]; - uint32_t v = *(const uint32_t*)p; - v |= bits << (bw->bit_pos_ & 7); - *(uint32_t*)p = v; - bw->bit_pos_ += n_bits; - } -#else // BIG_ENDIAN - { - uint8_t* p = &bw->buf_[bw->bit_pos_ >> 3]; - const int bits_reserved_in_first_byte = bw->bit_pos_ & 7; - const int bits_left_to_write = n_bits - 8 + bits_reserved_in_first_byte; - // implicit & 0xff is assumed for uint8_t arithmetics - *p++ |= bits << bits_reserved_in_first_byte; - bits >>= 8 - bits_reserved_in_first_byte; - if (bits_left_to_write >= 1) { - *p++ = bits; - bits >>= 8; - if (bits_left_to_write >= 9) { - *p++ = bits; - bits >>= 8; - } - } - assert(n_bits <= 25); - *p = bits; - bw->bit_pos_ += n_bits; - } -#endif - if ((bw->bit_pos_ >> 3) > (bw->max_bytes_ - 8)) { - const uint64_t extra_size = 32768ULL + bw->max_bytes_; +void VP8LPutBitsFlushBits(VP8LBitWriter* const bw) { + // If needed, make some room by flushing some bits out. + if (bw->cur_ + VP8L_WRITER_BYTES > bw->end_) { + const uint64_t extra_size = (bw->end_ - bw->buf_) + MIN_EXTRA_SIZE; if (extra_size != (size_t)extra_size || !VP8LBitWriterResize(bw, (size_t)extra_size)) { - bw->bit_pos_ = 0; + bw->cur_ = bw->buf_; bw->error_ = 1; + return; } } + *(vp8l_wtype_t*)bw->cur_ = (vp8l_wtype_t)WSWAP((vp8l_wtype_t)bw->bits_); + bw->cur_ += VP8L_WRITER_BYTES; + bw->bits_ >>= VP8L_WRITER_BITS; + bw->used_ -= VP8L_WRITER_BITS; +} + +void VP8LPutBitsInternal(VP8LBitWriter* const bw, uint32_t bits, int n_bits) { + assert(n_bits <= 32); + // That's the max we can handle: + assert(sizeof(vp8l_wtype_t) == 2); + if (n_bits > 0) { + vp8l_atype_t lbits = bw->bits_; + int used = bw->used_; + // Special case of overflow handling for 32bit accumulator (2-steps flush). +#if VP8L_WRITER_BITS == 16 + if (used + n_bits >= VP8L_WRITER_MAX_BITS) { + // Fill up all the VP8L_WRITER_MAX_BITS so it can be flushed out below. + const int shift = VP8L_WRITER_MAX_BITS - used; + lbits |= (vp8l_atype_t)bits << used; + used = VP8L_WRITER_MAX_BITS; + n_bits -= shift; + bits >>= shift; + assert(n_bits <= VP8L_WRITER_MAX_BITS); + } +#endif + // If needed, make some room by flushing some bits out. + while (used >= VP8L_WRITER_BITS) { + if (bw->cur_ + VP8L_WRITER_BYTES > bw->end_) { + const uint64_t extra_size = (bw->end_ - bw->buf_) + MIN_EXTRA_SIZE; + if (extra_size != (size_t)extra_size || + !VP8LBitWriterResize(bw, (size_t)extra_size)) { + bw->cur_ = bw->buf_; + bw->error_ = 1; + return; + } + } + *(vp8l_wtype_t*)bw->cur_ = (vp8l_wtype_t)WSWAP((vp8l_wtype_t)lbits); + bw->cur_ += VP8L_WRITER_BYTES; + lbits >>= VP8L_WRITER_BITS; + used -= VP8L_WRITER_BITS; + } + bw->bits_ = lbits | ((vp8l_atype_t)bits << used); + bw->used_ = used + n_bits; + } } -//------------------------------------------------------------------------------ +uint8_t* VP8LBitWriterFinish(VP8LBitWriter* const bw) { + // flush leftover bits + if (VP8LBitWriterResize(bw, (bw->used_ + 7) >> 3)) { + while (bw->used_ > 0) { + *bw->cur_++ = (uint8_t)bw->bits_; + bw->bits_ >>= 8; + bw->used_ -= 8; + } + bw->used_ = 0; + } + return bw->buf_; +} -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/utils/bit_writer.h b/3rdparty/libwebp/utils/bit_writer_utils.h similarity index 52% rename from 3rdparty/libwebp/utils/bit_writer.h rename to 3rdparty/libwebp/utils/bit_writer_utils.h index cbb095c1be..9c02bbc06d 100644 --- a/3rdparty/libwebp/utils/bit_writer.h +++ b/3rdparty/libwebp/utils/bit_writer_utils.h @@ -16,7 +16,7 @@ #include "../webp/types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -45,8 +45,8 @@ void VP8BitWriterWipeOut(VP8BitWriter* const bw); int VP8PutBit(VP8BitWriter* const bw, int bit, int prob); int VP8PutBitUniform(VP8BitWriter* const bw, int bit); -void VP8PutValue(VP8BitWriter* const bw, int value, int nb_bits); -void VP8PutSignedValue(VP8BitWriter* const bw, int value, int nb_bits); +void VP8PutBits(VP8BitWriter* const bw, uint32_t value, int nb_bits); +void VP8PutSignedBits(VP8BitWriter* const bw, int value, int nb_bits); // Appends some bytes to the internal buffer. Data is copied. int VP8BitWriterAppend(VP8BitWriter* const bw, @@ -54,7 +54,8 @@ int VP8BitWriterAppend(VP8BitWriter* const bw, // return approximate write position (in bits) static WEBP_INLINE uint64_t VP8BitWriterPos(const VP8BitWriter* const bw) { - return (uint64_t)(bw->pos_ + bw->run_) * 8 + 8 + bw->nb_bits_; + const uint64_t nb_bits = 8 + bw->nb_bits_; // bw->nb_bits_ is <= 0, note + return (bw->pos_ + bw->run_) * 8 + nb_bits; } // Returns a pointer to the internal buffer. @@ -68,57 +69,77 @@ static WEBP_INLINE size_t VP8BitWriterSize(const VP8BitWriter* const bw) { //------------------------------------------------------------------------------ // VP8LBitWriter -// TODO(vikasa): VP8LBitWriter is copied as-is from lossless code. There's scope -// of re-using VP8BitWriter. Will evaluate once basic lossless encoder is -// implemented. + +#if defined(__x86_64__) || defined(_M_X64) // 64bit +typedef uint64_t vp8l_atype_t; // accumulator type +typedef uint32_t vp8l_wtype_t; // writing type +#define WSWAP HToLE32 +#define VP8L_WRITER_BYTES 4 // sizeof(vp8l_wtype_t) +#define VP8L_WRITER_BITS 32 // 8 * sizeof(vp8l_wtype_t) +#define VP8L_WRITER_MAX_BITS 64 // 8 * sizeof(vp8l_atype_t) +#else +typedef uint32_t vp8l_atype_t; +typedef uint16_t vp8l_wtype_t; +#define WSWAP HToLE16 +#define VP8L_WRITER_BYTES 2 +#define VP8L_WRITER_BITS 16 +#define VP8L_WRITER_MAX_BITS 32 +#endif typedef struct { - uint8_t* buf_; - size_t bit_pos_; - size_t max_bytes_; + vp8l_atype_t bits_; // bit accumulator + int used_; // number of bits used in accumulator + uint8_t* buf_; // start of buffer + uint8_t* cur_; // current write position + uint8_t* end_; // end of buffer - // After all bits are written, the caller must observe the state of - // error_. A value of 1 indicates that a memory allocation failure - // has happened during bit writing. A value of 0 indicates successful + // After all bits are written (VP8LBitWriterFinish()), the caller must observe + // the state of error_. A value of 1 indicates that a memory allocation + // failure has happened during bit writing. A value of 0 indicates successful // writing of bits. int error_; } VP8LBitWriter; static WEBP_INLINE size_t VP8LBitWriterNumBytes(VP8LBitWriter* const bw) { - return (bw->bit_pos_ + 7) >> 3; + return (bw->cur_ - bw->buf_) + ((bw->used_ + 7) >> 3); } -static WEBP_INLINE uint8_t* VP8LBitWriterFinish(VP8LBitWriter* const bw) { - return bw->buf_; -} - -// Returns 0 in case of memory allocation error. +// Returns false in case of memory allocation error. int VP8LBitWriterInit(VP8LBitWriter* const bw, size_t expected_size); +// Finalize the bitstream coding. Returns a pointer to the internal buffer. +uint8_t* VP8LBitWriterFinish(VP8LBitWriter* const bw); +// Release any pending memory and zeroes the object. +void VP8LBitWriterWipeOut(VP8LBitWriter* const bw); -void VP8LBitWriterDestroy(VP8LBitWriter* const bw); +// Internal function for VP8LPutBits flushing 32 bits from the written state. +void VP8LPutBitsFlushBits(VP8LBitWriter* const bw); -// This function writes bits into bytes in increasing addresses, and within -// a byte least-significant-bit first. -// -// The function can write up to 16 bits in one go with WriteBits -// Example: let's assume that 3 bits (Rs below) have been written already: -// -// BYTE-0 BYTE+1 BYTE+2 -// -// 0000 0RRR 0000 0000 0000 0000 -// -// Now, we could write 5 or less bits in MSB by just sifting by 3 -// and OR'ing to BYTE-0. -// -// For n bits, we take the last 5 bytes, OR that with high bits in BYTE-0, -// and locate the rest in BYTE+1 and BYTE+2. -// +// PutBits internal function used in the 16 bit vp8l_wtype_t case. +void VP8LPutBitsInternal(VP8LBitWriter* const bw, uint32_t bits, int n_bits); + +// This function writes bits into bytes in increasing addresses (little endian), +// and within a byte least-significant-bit first. +// This function can write up to 32 bits in one go, but VP8LBitReader can only +// read 24 bits max (VP8L_MAX_NUM_BIT_READ). // VP8LBitWriter's error_ flag is set in case of memory allocation error. -void VP8LWriteBits(VP8LBitWriter* const bw, int n_bits, uint32_t bits); +static WEBP_INLINE void VP8LPutBits(VP8LBitWriter* const bw, + uint32_t bits, int n_bits) { + if (sizeof(vp8l_wtype_t) == 4) { + if (n_bits > 0) { + if (bw->used_ >= 32) { + VP8LPutBitsFlushBits(bw); + } + bw->bits_ |= (vp8l_atype_t)bits << bw->used_; + bw->used_ += n_bits; + } + } else { + VP8LPutBitsInternal(bw, bits, n_bits); + } +} //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/utils/color_cache.c b/3rdparty/libwebp/utils/color_cache_utils.c similarity index 72% rename from 3rdparty/libwebp/utils/color_cache.c rename to 3rdparty/libwebp/utils/color_cache_utils.c index 749db6128f..0172590c48 100644 --- a/3rdparty/libwebp/utils/color_cache.c +++ b/3rdparty/libwebp/utils/color_cache_utils.c @@ -13,12 +13,9 @@ #include #include -#include "./color_cache.h" -#include "../utils/utils.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include +#include "./color_cache_utils.h" +#include "./utils.h" //------------------------------------------------------------------------------ // VP8LColorCache. @@ -31,16 +28,22 @@ int VP8LColorCacheInit(VP8LColorCache* const cc, int hash_bits) { sizeof(*cc->colors_)); if (cc->colors_ == NULL) return 0; cc->hash_shift_ = 32 - hash_bits; + cc->hash_bits_ = hash_bits; return 1; } void VP8LColorCacheClear(VP8LColorCache* const cc) { if (cc != NULL) { - free(cc->colors_); + WebPSafeFree(cc->colors_); cc->colors_ = NULL; } } -#if defined(__cplusplus) || defined(c_plusplus) +void VP8LColorCacheCopy(const VP8LColorCache* const src, + VP8LColorCache* const dst) { + assert(src != NULL); + assert(dst != NULL); + assert(src->hash_bits_ == dst->hash_bits_); + memcpy(dst->colors_, src->colors_, + ((size_t)1u << dst->hash_bits_) * sizeof(*dst->colors_)); } -#endif diff --git a/3rdparty/libwebp/utils/color_cache.h b/3rdparty/libwebp/utils/color_cache_utils.h similarity index 68% rename from 3rdparty/libwebp/utils/color_cache.h rename to 3rdparty/libwebp/utils/color_cache_utils.h index e5a0bd6fd4..c373e6b361 100644 --- a/3rdparty/libwebp/utils/color_cache.h +++ b/3rdparty/libwebp/utils/color_cache_utils.h @@ -17,39 +17,51 @@ #include "../webp/types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif // Main color cache struct. typedef struct { uint32_t *colors_; // color entries - int hash_shift_; // Hash shift: 32 - hash_bits. + int hash_shift_; // Hash shift: 32 - hash_bits_. + int hash_bits_; } VP8LColorCache; -static const uint32_t kHashMul = 0x1e35a7bd; +static const uint64_t kHashMul = 0x1e35a7bdull; + +static WEBP_INLINE int HashPix(uint32_t argb, int shift) { + return (int)(((argb * kHashMul) & 0xffffffffu) >> shift); +} static WEBP_INLINE uint32_t VP8LColorCacheLookup( const VP8LColorCache* const cc, uint32_t key) { - assert(key <= (~0U >> cc->hash_shift_)); + assert((key >> cc->hash_bits_) == 0u); return cc->colors_[key]; } +static WEBP_INLINE void VP8LColorCacheSet(const VP8LColorCache* const cc, + uint32_t key, uint32_t argb) { + assert((key >> cc->hash_bits_) == 0u); + cc->colors_[key] = argb; +} + static WEBP_INLINE void VP8LColorCacheInsert(const VP8LColorCache* const cc, uint32_t argb) { - const uint32_t key = (kHashMul * argb) >> cc->hash_shift_; + const int key = HashPix(argb, cc->hash_shift_); cc->colors_[key] = argb; } static WEBP_INLINE int VP8LColorCacheGetIndex(const VP8LColorCache* const cc, uint32_t argb) { - return (kHashMul * argb) >> cc->hash_shift_; + return HashPix(argb, cc->hash_shift_); } +// Return the key if cc contains argb, and -1 otherwise. static WEBP_INLINE int VP8LColorCacheContains(const VP8LColorCache* const cc, uint32_t argb) { - const uint32_t key = (kHashMul * argb) >> cc->hash_shift_; - return cc->colors_[key] == argb; + const int key = HashPix(argb, cc->hash_shift_); + return (cc->colors_[key] == argb) ? key : -1; } //------------------------------------------------------------------------------ @@ -58,12 +70,15 @@ static WEBP_INLINE int VP8LColorCacheContains(const VP8LColorCache* const cc, // Returns false in case of memory error. int VP8LColorCacheInit(VP8LColorCache* const color_cache, int hash_bits); +void VP8LColorCacheCopy(const VP8LColorCache* const src, + VP8LColorCache* const dst); + // Delete the memory associated to color cache. void VP8LColorCacheClear(VP8LColorCache* const color_cache); //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/3rdparty/libwebp/utils/endian_inl_utils.h b/3rdparty/libwebp/utils/endian_inl_utils.h new file mode 100644 index 0000000000..e11260ff7d --- /dev/null +++ b/3rdparty/libwebp/utils/endian_inl_utils.h @@ -0,0 +1,100 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Endian related functions. + +#ifndef WEBP_UTILS_ENDIAN_INL_H_ +#define WEBP_UTILS_ENDIAN_INL_H_ + +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" +#endif + +#include "../dsp/dsp.h" +#include "../webp/types.h" + +// some endian fix (e.g.: mips-gcc doesn't define __BIG_ENDIAN__) +#if !defined(WORDS_BIGENDIAN) && \ + (defined(__BIG_ENDIAN__) || defined(_M_PPC) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))) +#define WORDS_BIGENDIAN +#endif + +#if defined(WORDS_BIGENDIAN) +#define HToLE32 BSwap32 +#define HToLE16 BSwap16 +#else +#define HToLE32(x) (x) +#define HToLE16(x) (x) +#endif + +#if !defined(HAVE_CONFIG_H) +#if LOCAL_GCC_PREREQ(4,8) || __has_builtin(__builtin_bswap16) +#define HAVE_BUILTIN_BSWAP16 +#endif +#if LOCAL_GCC_PREREQ(4,3) || __has_builtin(__builtin_bswap32) +#define HAVE_BUILTIN_BSWAP32 +#endif +#if LOCAL_GCC_PREREQ(4,3) || __has_builtin(__builtin_bswap64) +#define HAVE_BUILTIN_BSWAP64 +#endif +#endif // !HAVE_CONFIG_H + +static WEBP_INLINE uint16_t BSwap16(uint16_t x) { +#if defined(HAVE_BUILTIN_BSWAP16) + return __builtin_bswap16(x); +#elif defined(_MSC_VER) + return _byteswap_ushort(x); +#else + // gcc will recognize a 'rorw $8, ...' here: + return (x >> 8) | ((x & 0xff) << 8); +#endif // HAVE_BUILTIN_BSWAP16 +} + +static WEBP_INLINE uint32_t BSwap32(uint32_t x) { +#if defined(WEBP_USE_MIPS32_R2) + uint32_t ret; + __asm__ volatile ( + "wsbh %[ret], %[x] \n\t" + "rotr %[ret], %[ret], 16 \n\t" + : [ret]"=r"(ret) + : [x]"r"(x) + ); + return ret; +#elif defined(HAVE_BUILTIN_BSWAP32) + return __builtin_bswap32(x); +#elif defined(__i386__) || defined(__x86_64__) + uint32_t swapped_bytes; + __asm__ volatile("bswap %0" : "=r"(swapped_bytes) : "0"(x)); + return swapped_bytes; +#elif defined(_MSC_VER) + return (uint32_t)_byteswap_ulong(x); +#else + return (x >> 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x << 24); +#endif // HAVE_BUILTIN_BSWAP32 +} + +static WEBP_INLINE uint64_t BSwap64(uint64_t x) { +#if defined(HAVE_BUILTIN_BSWAP64) + return __builtin_bswap64(x); +#elif defined(__x86_64__) + uint64_t swapped_bytes; + __asm__ volatile("bswapq %0" : "=r"(swapped_bytes) : "0"(x)); + return swapped_bytes; +#elif defined(_MSC_VER) + return (uint64_t)_byteswap_uint64(x); +#else // generic code for swapping 64-bit values (suggested by bdb@) + x = ((x & 0xffffffff00000000ull) >> 32) | ((x & 0x00000000ffffffffull) << 32); + x = ((x & 0xffff0000ffff0000ull) >> 16) | ((x & 0x0000ffff0000ffffull) << 16); + x = ((x & 0xff00ff00ff00ff00ull) >> 8) | ((x & 0x00ff00ff00ff00ffull) << 8); + return x; +#endif // HAVE_BUILTIN_BSWAP64 +} + +#endif // WEBP_UTILS_ENDIAN_INL_H_ diff --git a/3rdparty/libwebp/utils/filters.c b/3rdparty/libwebp/utils/filters.c deleted file mode 100644 index eb5bb34f41..0000000000 --- a/3rdparty/libwebp/utils/filters.c +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Spatial prediction using various filters -// -// Author: Urvang (urvang@google.com) - -#include "./filters.h" -#include -#include -#include - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -//------------------------------------------------------------------------------ -// Helpful macro. - -# define SANITY_CHECK(in, out) \ - assert(in != NULL); \ - assert(out != NULL); \ - assert(width > 0); \ - assert(height > 0); \ - assert(stride >= width); - -static WEBP_INLINE void PredictLine(const uint8_t* src, const uint8_t* pred, - uint8_t* dst, int length, int inverse) { - int i; - if (inverse) { - for (i = 0; i < length; ++i) dst[i] = src[i] + pred[i]; - } else { - for (i = 0; i < length; ++i) dst[i] = src[i] - pred[i]; - } -} - -//------------------------------------------------------------------------------ -// Horizontal filter. - -static WEBP_INLINE void DoHorizontalFilter(const uint8_t* in, - int width, int height, int stride, - int inverse, uint8_t* out) { - int h; - const uint8_t* preds = (inverse ? out : in); - SANITY_CHECK(in, out); - - // Filter line-by-line. - for (h = 0; h < height; ++h) { - // Leftmost pixel is predicted from above (except for topmost scanline). - if (h == 0) { - out[0] = in[0]; - } else { - PredictLine(in, preds - stride, out, 1, inverse); - } - PredictLine(in + 1, preds, out + 1, width - 1, inverse); - preds += stride; - in += stride; - out += stride; - } -} - -static void HorizontalFilter(const uint8_t* data, int width, int height, - int stride, uint8_t* filtered_data) { - DoHorizontalFilter(data, width, height, stride, 0, filtered_data); -} - -static void HorizontalUnfilter(int width, int height, int stride, - uint8_t* data) { - DoHorizontalFilter(data, width, height, stride, 1, data); -} - -//------------------------------------------------------------------------------ -// Vertical filter. - -static WEBP_INLINE void DoVerticalFilter(const uint8_t* in, - int width, int height, int stride, - int inverse, uint8_t* out) { - int h; - const uint8_t* preds = (inverse ? out : in); - SANITY_CHECK(in, out); - - // Very first top-left pixel is copied. - out[0] = in[0]; - // Rest of top scan-line is left-predicted. - PredictLine(in + 1, preds, out + 1, width - 1, inverse); - - // Filter line-by-line. - for (h = 1; h < height; ++h) { - in += stride; - out += stride; - PredictLine(in, preds, out, width, inverse); - preds += stride; - } -} - -static void VerticalFilter(const uint8_t* data, int width, int height, - int stride, uint8_t* filtered_data) { - DoVerticalFilter(data, width, height, stride, 0, filtered_data); -} - -static void VerticalUnfilter(int width, int height, int stride, uint8_t* data) { - DoVerticalFilter(data, width, height, stride, 1, data); -} - -//------------------------------------------------------------------------------ -// Gradient filter. - -static WEBP_INLINE int GradientPredictor(uint8_t a, uint8_t b, uint8_t c) { - const int g = a + b - c; - return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit -} - -static WEBP_INLINE -void DoGradientFilter(const uint8_t* in, int width, int height, - int stride, int inverse, uint8_t* out) { - const uint8_t* preds = (inverse ? out : in); - int h; - SANITY_CHECK(in, out); - - // left prediction for top scan-line - out[0] = in[0]; - PredictLine(in + 1, preds, out + 1, width - 1, inverse); - - // Filter line-by-line. - for (h = 1; h < height; ++h) { - int w; - preds += stride; - in += stride; - out += stride; - // leftmost pixel: predict from above. - PredictLine(in, preds - stride, out, 1, inverse); - for (w = 1; w < width; ++w) { - const int pred = GradientPredictor(preds[w - 1], - preds[w - stride], - preds[w - stride - 1]); - out[w] = in[w] + (inverse ? pred : -pred); - } - } -} - -static void GradientFilter(const uint8_t* data, int width, int height, - int stride, uint8_t* filtered_data) { - DoGradientFilter(data, width, height, stride, 0, filtered_data); -} - -static void GradientUnfilter(int width, int height, int stride, uint8_t* data) { - DoGradientFilter(data, width, height, stride, 1, data); -} - -#undef SANITY_CHECK - -// ----------------------------------------------------------------------------- -// Quick estimate of a potentially interesting filter mode to try. - -#define SMAX 16 -#define SDIFF(a, b) (abs((a) - (b)) >> 4) // Scoring diff, in [0..SMAX) - -WEBP_FILTER_TYPE EstimateBestFilter(const uint8_t* data, - int width, int height, int stride) { - int i, j; - int bins[WEBP_FILTER_LAST][SMAX]; - memset(bins, 0, sizeof(bins)); - - // We only sample every other pixels. That's enough. - for (j = 2; j < height - 1; j += 2) { - const uint8_t* const p = data + j * stride; - int mean = p[0]; - for (i = 2; i < width - 1; i += 2) { - const int diff0 = SDIFF(p[i], mean); - const int diff1 = SDIFF(p[i], p[i - 1]); - const int diff2 = SDIFF(p[i], p[i - width]); - const int grad_pred = - GradientPredictor(p[i - 1], p[i - width], p[i - width - 1]); - const int diff3 = SDIFF(p[i], grad_pred); - bins[WEBP_FILTER_NONE][diff0] = 1; - bins[WEBP_FILTER_HORIZONTAL][diff1] = 1; - bins[WEBP_FILTER_VERTICAL][diff2] = 1; - bins[WEBP_FILTER_GRADIENT][diff3] = 1; - mean = (3 * mean + p[i] + 2) >> 2; - } - } - { - WEBP_FILTER_TYPE filter, best_filter = WEBP_FILTER_NONE; - int best_score = 0x7fffffff; - for (filter = WEBP_FILTER_NONE; filter < WEBP_FILTER_LAST; ++filter) { - int score = 0; - for (i = 0; i < SMAX; ++i) { - if (bins[filter][i] > 0) { - score += i; - } - } - if (score < best_score) { - best_score = score; - best_filter = filter; - } - } - return best_filter; - } -} - -#undef SMAX -#undef SDIFF - -//------------------------------------------------------------------------------ - -const WebPFilterFunc WebPFilters[WEBP_FILTER_LAST] = { - NULL, // WEBP_FILTER_NONE - HorizontalFilter, // WEBP_FILTER_HORIZONTAL - VerticalFilter, // WEBP_FILTER_VERTICAL - GradientFilter // WEBP_FILTER_GRADIENT -}; - -const WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST] = { - NULL, // WEBP_FILTER_NONE - HorizontalUnfilter, // WEBP_FILTER_HORIZONTAL - VerticalUnfilter, // WEBP_FILTER_VERTICAL - GradientUnfilter // WEBP_FILTER_GRADIENT -}; - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/utils/filters.h b/3rdparty/libwebp/utils/filters.h deleted file mode 100644 index 1f5fa164f3..0000000000 --- a/3rdparty/libwebp/utils/filters.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Spatial prediction using various filters -// -// Author: Urvang (urvang@google.com) - -#ifndef WEBP_UTILS_FILTERS_H_ -#define WEBP_UTILS_FILTERS_H_ - -#include "../webp/types.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -// Filters. -typedef enum { - WEBP_FILTER_NONE = 0, - WEBP_FILTER_HORIZONTAL, - WEBP_FILTER_VERTICAL, - WEBP_FILTER_GRADIENT, - WEBP_FILTER_LAST = WEBP_FILTER_GRADIENT + 1, // end marker - WEBP_FILTER_BEST, - WEBP_FILTER_FAST -} WEBP_FILTER_TYPE; - -typedef void (*WebPFilterFunc)(const uint8_t* in, int width, int height, - int stride, uint8_t* out); -typedef void (*WebPUnfilterFunc)(int width, int height, int stride, - uint8_t* data); - -// Filter the given data using the given predictor. -// 'in' corresponds to a 2-dimensional pixel array of size (stride * height) -// in raster order. -// 'stride' is number of bytes per scan line (with possible padding). -// 'out' should be pre-allocated. -extern const WebPFilterFunc WebPFilters[WEBP_FILTER_LAST]; - -// In-place reconstruct the original data from the given filtered data. -extern const WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST]; - -// Fast estimate of a potentially good filter. -extern WEBP_FILTER_TYPE EstimateBestFilter(const uint8_t* data, - int width, int height, int stride); - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif - -#endif /* WEBP_UTILS_FILTERS_H_ */ diff --git a/3rdparty/libwebp/utils/filters_utils.c b/3rdparty/libwebp/utils/filters_utils.c new file mode 100644 index 0000000000..49c1d18a22 --- /dev/null +++ b/3rdparty/libwebp/utils/filters_utils.c @@ -0,0 +1,76 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// filter estimation +// +// Author: Urvang (urvang@google.com) + +#include "./filters_utils.h" +#include +#include + +// ----------------------------------------------------------------------------- +// Quick estimate of a potentially interesting filter mode to try. + +#define SMAX 16 +#define SDIFF(a, b) (abs((a) - (b)) >> 4) // Scoring diff, in [0..SMAX) + +static WEBP_INLINE int GradientPredictor(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +WEBP_FILTER_TYPE WebPEstimateBestFilter(const uint8_t* data, + int width, int height, int stride) { + int i, j; + int bins[WEBP_FILTER_LAST][SMAX]; + memset(bins, 0, sizeof(bins)); + + // We only sample every other pixels. That's enough. + for (j = 2; j < height - 1; j += 2) { + const uint8_t* const p = data + j * stride; + int mean = p[0]; + for (i = 2; i < width - 1; i += 2) { + const int diff0 = SDIFF(p[i], mean); + const int diff1 = SDIFF(p[i], p[i - 1]); + const int diff2 = SDIFF(p[i], p[i - width]); + const int grad_pred = + GradientPredictor(p[i - 1], p[i - width], p[i - width - 1]); + const int diff3 = SDIFF(p[i], grad_pred); + bins[WEBP_FILTER_NONE][diff0] = 1; + bins[WEBP_FILTER_HORIZONTAL][diff1] = 1; + bins[WEBP_FILTER_VERTICAL][diff2] = 1; + bins[WEBP_FILTER_GRADIENT][diff3] = 1; + mean = (3 * mean + p[i] + 2) >> 2; + } + } + { + int filter; + WEBP_FILTER_TYPE best_filter = WEBP_FILTER_NONE; + int best_score = 0x7fffffff; + for (filter = WEBP_FILTER_NONE; filter < WEBP_FILTER_LAST; ++filter) { + int score = 0; + for (i = 0; i < SMAX; ++i) { + if (bins[filter][i] > 0) { + score += i; + } + } + if (score < best_score) { + best_score = score; + best_filter = (WEBP_FILTER_TYPE)filter; + } + } + return best_filter; + } +} + +#undef SMAX +#undef SDIFF + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/dec/layer.c b/3rdparty/libwebp/utils/filters_utils.h similarity index 51% rename from 3rdparty/libwebp/dec/layer.c rename to 3rdparty/libwebp/utils/filters_utils.h index 9a4b2d9032..088b132fc5 100644 --- a/3rdparty/libwebp/dec/layer.c +++ b/3rdparty/libwebp/utils/filters_utils.h @@ -7,31 +7,26 @@ // be found in the AUTHORS file in the root of the source tree. // ----------------------------------------------------------------------------- // -// Enhancement layer (for YUV444/422) +// Spatial prediction using various filters // -// Author: Skal (pascal.massimino@gmail.com) +// Author: Urvang (urvang@google.com) -#include -#include +#ifndef WEBP_UTILS_FILTERS_H_ +#define WEBP_UTILS_FILTERS_H_ -#include "./vp8i.h" +#include "../webp/types.h" +#include "../dsp/dsp.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -//------------------------------------------------------------------------------ +// Fast estimate of a potentially good filter. +WEBP_FILTER_TYPE WebPEstimateBestFilter(const uint8_t* data, + int width, int height, int stride); -int VP8DecodeLayer(VP8Decoder* const dec) { - assert(dec); - assert(dec->layer_data_size_ > 0); - (void)dec; - - // TODO: handle enhancement layer here. - - return 1; -} - -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif + +#endif /* WEBP_UTILS_FILTERS_H_ */ diff --git a/3rdparty/libwebp/utils/huffman.c b/3rdparty/libwebp/utils/huffman.c deleted file mode 100644 index 0ba9d05cfc..0000000000 --- a/3rdparty/libwebp/utils/huffman.c +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Utilities for building and looking up Huffman trees. -// -// Author: Urvang Joshi (urvang@google.com) - -#include -#include -#include "./huffman.h" -#include "../utils/utils.h" -#include "../webp/format_constants.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#define NON_EXISTENT_SYMBOL (-1) - -static void TreeNodeInit(HuffmanTreeNode* const node) { - node->children_ = -1; // means: 'unassigned so far' -} - -static int NodeIsEmpty(const HuffmanTreeNode* const node) { - return (node->children_ < 0); -} - -static int IsFull(const HuffmanTree* const tree) { - return (tree->num_nodes_ == tree->max_nodes_); -} - -static void AssignChildren(HuffmanTree* const tree, - HuffmanTreeNode* const node) { - HuffmanTreeNode* const children = tree->root_ + tree->num_nodes_; - node->children_ = (int)(children - node); - assert(children - node == (int)(children - node)); - tree->num_nodes_ += 2; - TreeNodeInit(children + 0); - TreeNodeInit(children + 1); -} - -static int TreeInit(HuffmanTree* const tree, int num_leaves) { - assert(tree != NULL); - if (num_leaves == 0) return 0; - // We allocate maximum possible nodes in the tree at once. - // Note that a Huffman tree is a full binary tree; and in a full binary tree - // with L leaves, the total number of nodes N = 2 * L - 1. - tree->max_nodes_ = 2 * num_leaves - 1; - tree->root_ = (HuffmanTreeNode*)WebPSafeMalloc((uint64_t)tree->max_nodes_, - sizeof(*tree->root_)); - if (tree->root_ == NULL) return 0; - TreeNodeInit(tree->root_); // Initialize root. - tree->num_nodes_ = 1; - return 1; -} - -void HuffmanTreeRelease(HuffmanTree* const tree) { - if (tree != NULL) { - free(tree->root_); - tree->root_ = NULL; - tree->max_nodes_ = 0; - tree->num_nodes_ = 0; - } -} - -int HuffmanCodeLengthsToCodes(const int* const code_lengths, - int code_lengths_size, int* const huff_codes) { - int symbol; - int code_len; - int code_length_hist[MAX_ALLOWED_CODE_LENGTH + 1] = { 0 }; - int curr_code; - int next_codes[MAX_ALLOWED_CODE_LENGTH + 1] = { 0 }; - int max_code_length = 0; - - assert(code_lengths != NULL); - assert(code_lengths_size > 0); - assert(huff_codes != NULL); - - // Calculate max code length. - for (symbol = 0; symbol < code_lengths_size; ++symbol) { - if (code_lengths[symbol] > max_code_length) { - max_code_length = code_lengths[symbol]; - } - } - if (max_code_length > MAX_ALLOWED_CODE_LENGTH) return 0; - - // Calculate code length histogram. - for (symbol = 0; symbol < code_lengths_size; ++symbol) { - ++code_length_hist[code_lengths[symbol]]; - } - code_length_hist[0] = 0; - - // Calculate the initial values of 'next_codes' for each code length. - // next_codes[code_len] denotes the code to be assigned to the next symbol - // of code length 'code_len'. - curr_code = 0; - next_codes[0] = -1; // Unused, as code length = 0 implies code doesn't exist. - for (code_len = 1; code_len <= max_code_length; ++code_len) { - curr_code = (curr_code + code_length_hist[code_len - 1]) << 1; - next_codes[code_len] = curr_code; - } - - // Get symbols. - for (symbol = 0; symbol < code_lengths_size; ++symbol) { - if (code_lengths[symbol] > 0) { - huff_codes[symbol] = next_codes[code_lengths[symbol]]++; - } else { - huff_codes[symbol] = NON_EXISTENT_SYMBOL; - } - } - return 1; -} - -static int TreeAddSymbol(HuffmanTree* const tree, - int symbol, int code, int code_length) { - HuffmanTreeNode* node = tree->root_; - const HuffmanTreeNode* const max_node = tree->root_ + tree->max_nodes_; - while (code_length-- > 0) { - if (node >= max_node) { - return 0; - } - if (NodeIsEmpty(node)) { - if (IsFull(tree)) return 0; // error: too many symbols. - AssignChildren(tree, node); - } else if (HuffmanTreeNodeIsLeaf(node)) { - return 0; // leaf is already occupied. - } - node += node->children_ + ((code >> code_length) & 1); - } - if (NodeIsEmpty(node)) { - node->children_ = 0; // turn newly created node into a leaf. - } else if (!HuffmanTreeNodeIsLeaf(node)) { - return 0; // trying to assign a symbol to already used code. - } - node->symbol_ = symbol; // Add symbol in this node. - return 1; -} - -int HuffmanTreeBuildImplicit(HuffmanTree* const tree, - const int* const code_lengths, - int code_lengths_size) { - int symbol; - int num_symbols = 0; - int root_symbol = 0; - - assert(tree != NULL); - assert(code_lengths != NULL); - - // Find out number of symbols and the root symbol. - for (symbol = 0; symbol < code_lengths_size; ++symbol) { - if (code_lengths[symbol] > 0) { - // Note: code length = 0 indicates non-existent symbol. - ++num_symbols; - root_symbol = symbol; - } - } - - // Initialize the tree. Will fail for num_symbols = 0 - if (!TreeInit(tree, num_symbols)) return 0; - - // Build tree. - if (num_symbols == 1) { // Trivial case. - const int max_symbol = code_lengths_size; - if (root_symbol < 0 || root_symbol >= max_symbol) { - HuffmanTreeRelease(tree); - return 0; - } - return TreeAddSymbol(tree, root_symbol, 0, 0); - } else { // Normal case. - int ok = 0; - - // Get Huffman codes from the code lengths. - int* const codes = - (int*)WebPSafeMalloc((uint64_t)code_lengths_size, sizeof(*codes)); - if (codes == NULL) goto End; - - if (!HuffmanCodeLengthsToCodes(code_lengths, code_lengths_size, codes)) { - goto End; - } - - // Add symbols one-by-one. - for (symbol = 0; symbol < code_lengths_size; ++symbol) { - if (code_lengths[symbol] > 0) { - if (!TreeAddSymbol(tree, symbol, codes[symbol], code_lengths[symbol])) { - goto End; - } - } - } - ok = 1; - End: - free(codes); - ok = ok && IsFull(tree); - if (!ok) HuffmanTreeRelease(tree); - return ok; - } -} - -int HuffmanTreeBuildExplicit(HuffmanTree* const tree, - const int* const code_lengths, - const int* const codes, - const int* const symbols, int max_symbol, - int num_symbols) { - int ok = 0; - int i; - - assert(tree != NULL); - assert(code_lengths != NULL); - assert(codes != NULL); - assert(symbols != NULL); - - // Initialize the tree. Will fail if num_symbols = 0. - if (!TreeInit(tree, num_symbols)) return 0; - - // Add symbols one-by-one. - for (i = 0; i < num_symbols; ++i) { - if (codes[i] != NON_EXISTENT_SYMBOL) { - if (symbols[i] < 0 || symbols[i] >= max_symbol) { - goto End; - } - if (!TreeAddSymbol(tree, symbols[i], codes[i], code_lengths[i])) { - goto End; - } - } - } - ok = 1; - End: - ok = ok && IsFull(tree); - if (!ok) HuffmanTreeRelease(tree); - return ok; -} - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/utils/huffman.h b/3rdparty/libwebp/utils/huffman.h deleted file mode 100644 index 83a517ee60..0000000000 --- a/3rdparty/libwebp/utils/huffman.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Utilities for building and looking up Huffman trees. -// -// Author: Urvang Joshi (urvang@google.com) - -#ifndef WEBP_UTILS_HUFFMAN_H_ -#define WEBP_UTILS_HUFFMAN_H_ - -#include -#include "../webp/types.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -// A node of a Huffman tree. -typedef struct { - int symbol_; - int children_; // delta offset to both children (contiguous) or 0 if leaf. -} HuffmanTreeNode; - -// Huffman Tree. -typedef struct HuffmanTree HuffmanTree; -struct HuffmanTree { - HuffmanTreeNode* root_; // all the nodes, starting at root. - int max_nodes_; // max number of nodes - int num_nodes_; // number of currently occupied nodes -}; - -// Returns true if the given node is a leaf of the Huffman tree. -static WEBP_INLINE int HuffmanTreeNodeIsLeaf( - const HuffmanTreeNode* const node) { - return (node->children_ == 0); -} - -// Go down one level. Most critical function. 'right_child' must be 0 or 1. -static WEBP_INLINE const HuffmanTreeNode* HuffmanTreeNextNode( - const HuffmanTreeNode* node, int right_child) { - return node + node->children_ + right_child; -} - -// Releases the nodes of the Huffman tree. -// Note: It does NOT free 'tree' itself. -void HuffmanTreeRelease(HuffmanTree* const tree); - -// Builds Huffman tree assuming code lengths are implicitly in symbol order. -// Returns false in case of error (invalid tree or memory error). -int HuffmanTreeBuildImplicit(HuffmanTree* const tree, - const int* const code_lengths, - int code_lengths_size); - -// Build a Huffman tree with explicitly given lists of code lengths, codes -// and symbols. Verifies that all symbols added are smaller than max_symbol. -// Returns false in case of an invalid symbol, invalid tree or memory error. -int HuffmanTreeBuildExplicit(HuffmanTree* const tree, - const int* const code_lengths, - const int* const codes, - const int* const symbols, int max_symbol, - int num_symbols); - -// Utility: converts Huffman code lengths to corresponding Huffman codes. -// 'huff_codes' should be pre-allocated. -// Returns false in case of error (memory allocation, invalid codes). -int HuffmanCodeLengthsToCodes(const int* const code_lengths, - int code_lengths_size, int* const huff_codes); - - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif - -#endif // WEBP_UTILS_HUFFMAN_H_ diff --git a/3rdparty/libwebp/utils/huffman_encode.c b/3rdparty/libwebp/utils/huffman_encode_utils.c similarity index 87% rename from 3rdparty/libwebp/utils/huffman_encode.c rename to 3rdparty/libwebp/utils/huffman_encode_utils.c index 96086663d9..f9504658ea 100644 --- a/3rdparty/libwebp/utils/huffman_encode.c +++ b/3rdparty/libwebp/utils/huffman_encode_utils.c @@ -14,8 +14,8 @@ #include #include #include -#include "./huffman_encode.h" -#include "../utils/utils.h" +#include "./huffman_encode_utils.h" +#include "./utils.h" #include "../webp/format_constants.h" // ----------------------------------------------------------------------------- @@ -27,14 +27,14 @@ static int ValuesShouldBeCollapsedToStrideAverage(int a, int b) { } // Change the population counts in a way that the consequent -// Hufmann tree compression, especially its RLE-part, give smaller output. -static int OptimizeHuffmanForRle(int length, int* const counts) { - uint8_t* good_for_rle; +// Huffman tree compression, especially its RLE-part, give smaller output. +static void OptimizeHuffmanForRle(int length, uint8_t* const good_for_rle, + uint32_t* const counts) { // 1) Let's make the Huffman code more compatible with rle encoding. int i; for (; length >= 0; --length) { if (length == 0) { - return 1; // All zeros. + return; // All zeros. } if (counts[length - 1] != 0) { // Now counts[0..length - 1] does not have trailing zeros. @@ -43,15 +43,11 @@ static int OptimizeHuffmanForRle(int length, int* const counts) { } // 2) Let's mark all population counts that already can be encoded // with an rle code. - good_for_rle = (uint8_t*)calloc(length, 1); - if (good_for_rle == NULL) { - return 0; - } { // Let's not spoil any of the existing good rle codes. // Mark any seq of 0's that is longer as 5 as a good_for_rle. // Mark any seq of non-0's that is longer as 7 as a good_for_rle. - int symbol = counts[0]; + uint32_t symbol = counts[0]; int stride = 0; for (i = 0; i < length + 1; ++i) { if (i == length || counts[i] != symbol) { @@ -73,17 +69,17 @@ static int OptimizeHuffmanForRle(int length, int* const counts) { } // 3) Let's replace those population counts that lead to more rle codes. { - int stride = 0; - int limit = counts[0]; - int sum = 0; + uint32_t stride = 0; + uint32_t limit = counts[0]; + uint32_t sum = 0; for (i = 0; i < length + 1; ++i) { if (i == length || good_for_rle[i] || (i != 0 && good_for_rle[i - 1]) || !ValuesShouldBeCollapsedToStrideAverage(counts[i], limit)) { if (stride >= 4 || (stride >= 3 && sum == 0)) { - int k; + uint32_t k; // The stride must end, collapse what we have, if we have enough (4). - int count = (sum + stride / 2) / stride; + uint32_t count = (sum + stride / 2) / stride; if (count < 1) { count = 1; } @@ -119,17 +115,8 @@ static int OptimizeHuffmanForRle(int length, int* const counts) { } } } - free(good_for_rle); - return 1; } -typedef struct { - int total_count_; - int value_; - int pool_index_left_; - int pool_index_right_; -} HuffmanTree; - // A comparer function for two Huffman trees: sorts first by 'total count' // (more comes first), and then by 'value' (more comes first). static int CompareHuffmanTrees(const void* ptr1, const void* ptr2) { @@ -175,12 +162,12 @@ static void SetBitDepths(const HuffmanTree* const tree, // we are not planning to use this with extremely long blocks. // // See http://en.wikipedia.org/wiki/Huffman_coding -static int GenerateOptimalTree(const int* const histogram, int histogram_size, - int tree_depth_limit, - uint8_t* const bit_depths) { - int count_min; +static void GenerateOptimalTree(const uint32_t* const histogram, + int histogram_size, + HuffmanTree* tree, int tree_depth_limit, + uint8_t* const bit_depths) { + uint32_t count_min; HuffmanTree* tree_pool; - HuffmanTree* tree; int tree_size_orig = 0; int i; @@ -191,15 +178,9 @@ static int GenerateOptimalTree(const int* const histogram, int histogram_size, } if (tree_size_orig == 0) { // pretty optimal already! - return 1; + return; } - // 3 * tree_size is enough to cover all the nodes representing a - // population and all the inserted nodes combining two existing nodes. - // The tree pool needs 2 * (tree_size_orig - 1) entities, and the - // tree needs exactly tree_size_orig entities. - tree = (HuffmanTree*)WebPSafeMalloc(3ULL * tree_size_orig, sizeof(*tree)); - if (tree == NULL) return 0; tree_pool = tree + tree_size_orig; // For block sizes with less than 64k symbols we never need to do a @@ -215,7 +196,7 @@ static int GenerateOptimalTree(const int* const histogram, int histogram_size, int j; for (j = 0; j < histogram_size; ++j) { if (histogram[j] != 0) { - const int count = + const uint32_t count = (histogram[j] < count_min) ? count_min : histogram[j]; tree[idx].total_count_ = count; tree[idx].value_ = j; @@ -231,7 +212,7 @@ static int GenerateOptimalTree(const int* const histogram, int histogram_size, if (tree_size > 1) { // Normal case. int tree_pool_size = 0; while (tree_size > 1) { // Finish when we have only one root. - int count; + uint32_t count; tree_pool[tree_pool_size++] = tree[tree_size - 1]; tree_pool[tree_pool_size++] = tree[tree_size - 2]; count = tree_pool[tree_pool_size - 1].total_count_ + @@ -272,8 +253,6 @@ static int GenerateOptimalTree(const int* const histogram, int histogram_size, } } } - free(tree); - return 1; } // ----------------------------------------------------------------------------- @@ -424,17 +403,15 @@ static void ConvertBitDepthsToSymbols(HuffmanTreeCode* const tree) { // ----------------------------------------------------------------------------- // Main entry point -int VP8LCreateHuffmanTree(int* const histogram, int tree_depth_limit, - HuffmanTreeCode* const tree) { - const int num_symbols = tree->num_symbols; - if (!OptimizeHuffmanForRle(num_symbols, histogram)) { - return 0; - } - if (!GenerateOptimalTree(histogram, num_symbols, - tree_depth_limit, tree->code_lengths)) { - return 0; - } +void VP8LCreateHuffmanTree(uint32_t* const histogram, int tree_depth_limit, + uint8_t* const buf_rle, + HuffmanTree* const huff_tree, + HuffmanTreeCode* const huff_code) { + const int num_symbols = huff_code->num_symbols; + memset(buf_rle, 0, num_symbols * sizeof(*buf_rle)); + OptimizeHuffmanForRle(num_symbols, buf_rle, histogram); + GenerateOptimalTree(histogram, num_symbols, huff_tree, tree_depth_limit, + huff_code->code_lengths); // Create the actual bit codes for the bit lengths. - ConvertBitDepthsToSymbols(tree); - return 1; + ConvertBitDepthsToSymbols(huff_code); } diff --git a/3rdparty/libwebp/utils/huffman_encode.h b/3rdparty/libwebp/utils/huffman_encode_utils.h similarity index 69% rename from 3rdparty/libwebp/utils/huffman_encode.h rename to 3rdparty/libwebp/utils/huffman_encode_utils.h index 0b81f470b3..a157165148 100644 --- a/3rdparty/libwebp/utils/huffman_encode.h +++ b/3rdparty/libwebp/utils/huffman_encode_utils.h @@ -16,7 +16,7 @@ #include "../webp/types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -33,16 +33,27 @@ typedef struct { uint16_t* codes; // Symbol Codes. } HuffmanTreeCode; +// Struct to represent the Huffman tree. +typedef struct { + uint32_t total_count_; // Symbol frequency. + int value_; // Symbol value. + int pool_index_left_; // Index for the left sub-tree. + int pool_index_right_; // Index for the right sub-tree. +} HuffmanTree; + // Turn the Huffman tree into a token sequence. // Returns the number of tokens used. int VP8LCreateCompressedHuffmanTree(const HuffmanTreeCode* const tree, HuffmanTreeToken* tokens, int max_tokens); // Create an optimized tree, and tokenize it. -int VP8LCreateHuffmanTree(int* const histogram, int tree_depth_limit, - HuffmanTreeCode* const tree); +// 'buf_rle' and 'huff_tree' are pre-allocated and the 'tree' is the constructed +// huffman code tree. +void VP8LCreateHuffmanTree(uint32_t* const histogram, int tree_depth_limit, + uint8_t* const buf_rle, HuffmanTree* const huff_tree, + HuffmanTreeCode* const tree); -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/3rdparty/libwebp/utils/huffman_utils.c b/3rdparty/libwebp/utils/huffman_utils.c new file mode 100644 index 0000000000..008b5d746f --- /dev/null +++ b/3rdparty/libwebp/utils/huffman_utils.c @@ -0,0 +1,223 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for building and looking up Huffman trees. +// +// Author: Urvang Joshi (urvang@google.com) + +#include +#include +#include +#include "./huffman_utils.h" +#include "./utils.h" +#include "../webp/format_constants.h" + +// Huffman data read via DecodeImageStream is represented in two (red and green) +// bytes. +#define MAX_HTREE_GROUPS 0x10000 + +HTreeGroup* VP8LHtreeGroupsNew(int num_htree_groups) { + HTreeGroup* const htree_groups = + (HTreeGroup*)WebPSafeMalloc(num_htree_groups, sizeof(*htree_groups)); + if (htree_groups == NULL) { + return NULL; + } + assert(num_htree_groups <= MAX_HTREE_GROUPS); + return htree_groups; +} + +void VP8LHtreeGroupsFree(HTreeGroup* const htree_groups) { + if (htree_groups != NULL) { + WebPSafeFree(htree_groups); + } +} + +// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the +// bit-wise reversal of the len least significant bits of key. +static WEBP_INLINE uint32_t GetNextKey(uint32_t key, int len) { + uint32_t step = 1 << (len - 1); + while (key & step) { + step >>= 1; + } + return step ? (key & (step - 1)) + step : key; +} + +// Stores code in table[0], table[step], table[2*step], ..., table[end]. +// Assumes that end is an integer multiple of step. +static WEBP_INLINE void ReplicateValue(HuffmanCode* table, + int step, int end, + HuffmanCode code) { + assert(end % step == 0); + do { + end -= step; + table[end] = code; + } while (end > 0); +} + +// Returns the table width of the next 2nd level table. count is the histogram +// of bit lengths for the remaining symbols, len is the code length of the next +// processed symbol +static WEBP_INLINE int NextTableBitSize(const int* const count, + int len, int root_bits) { + int left = 1 << (len - root_bits); + while (len < MAX_ALLOWED_CODE_LENGTH) { + left -= count[len]; + if (left <= 0) break; + ++len; + left <<= 1; + } + return len - root_bits; +} + +// sorted[code_lengths_size] is a pre-allocated array for sorting symbols +// by code length. +static int BuildHuffmanTable(HuffmanCode* const root_table, int root_bits, + const int code_lengths[], int code_lengths_size, + uint16_t sorted[]) { + HuffmanCode* table = root_table; // next available space in table + int total_size = 1 << root_bits; // total size root table + 2nd level table + int len; // current code length + int symbol; // symbol index in original or sorted table + // number of codes of each length: + int count[MAX_ALLOWED_CODE_LENGTH + 1] = { 0 }; + // offsets in sorted table for each length: + int offset[MAX_ALLOWED_CODE_LENGTH + 1]; + + assert(code_lengths_size != 0); + assert(code_lengths != NULL); + assert(root_table != NULL); + assert(root_bits > 0); + + // Build histogram of code lengths. + for (symbol = 0; symbol < code_lengths_size; ++symbol) { + if (code_lengths[symbol] > MAX_ALLOWED_CODE_LENGTH) { + return 0; + } + ++count[code_lengths[symbol]]; + } + + // Error, all code lengths are zeros. + if (count[0] == code_lengths_size) { + return 0; + } + + // Generate offsets into sorted symbol table by code length. + offset[1] = 0; + for (len = 1; len < MAX_ALLOWED_CODE_LENGTH; ++len) { + if (count[len] > (1 << len)) { + return 0; + } + offset[len + 1] = offset[len] + count[len]; + } + + // Sort symbols by length, by symbol order within each length. + for (symbol = 0; symbol < code_lengths_size; ++symbol) { + const int symbol_code_length = code_lengths[symbol]; + if (code_lengths[symbol] > 0) { + sorted[offset[symbol_code_length]++] = symbol; + } + } + + // Special case code with only one value. + if (offset[MAX_ALLOWED_CODE_LENGTH] == 1) { + HuffmanCode code; + code.bits = 0; + code.value = (uint16_t)sorted[0]; + ReplicateValue(table, 1, total_size, code); + return total_size; + } + + { + int step; // step size to replicate values in current table + uint32_t low = -1; // low bits for current root entry + uint32_t mask = total_size - 1; // mask for low bits + uint32_t key = 0; // reversed prefix code + int num_nodes = 1; // number of Huffman tree nodes + int num_open = 1; // number of open branches in current tree level + int table_bits = root_bits; // key length of current table + int table_size = 1 << table_bits; // size of current table + symbol = 0; + // Fill in root table. + for (len = 1, step = 2; len <= root_bits; ++len, step <<= 1) { + num_open <<= 1; + num_nodes += num_open; + num_open -= count[len]; + if (num_open < 0) { + return 0; + } + for (; count[len] > 0; --count[len]) { + HuffmanCode code; + code.bits = (uint8_t)len; + code.value = (uint16_t)sorted[symbol++]; + ReplicateValue(&table[key], step, table_size, code); + key = GetNextKey(key, len); + } + } + + // Fill in 2nd level tables and add pointers to root table. + for (len = root_bits + 1, step = 2; len <= MAX_ALLOWED_CODE_LENGTH; + ++len, step <<= 1) { + num_open <<= 1; + num_nodes += num_open; + num_open -= count[len]; + if (num_open < 0) { + return 0; + } + for (; count[len] > 0; --count[len]) { + HuffmanCode code; + if ((key & mask) != low) { + table += table_size; + table_bits = NextTableBitSize(count, len, root_bits); + table_size = 1 << table_bits; + total_size += table_size; + low = key & mask; + root_table[low].bits = (uint8_t)(table_bits + root_bits); + root_table[low].value = (uint16_t)((table - root_table) - low); + } + code.bits = (uint8_t)(len - root_bits); + code.value = (uint16_t)sorted[symbol++]; + ReplicateValue(&table[key >> root_bits], step, table_size, code); + key = GetNextKey(key, len); + } + } + + // Check if tree is full. + if (num_nodes != 2 * offset[MAX_ALLOWED_CODE_LENGTH] - 1) { + return 0; + } + } + + return total_size; +} + +// Maximum code_lengths_size is 2328 (reached for 11-bit color_cache_bits). +// More commonly, the value is around ~280. +#define MAX_CODE_LENGTHS_SIZE \ + ((1 << MAX_CACHE_BITS) + NUM_LITERAL_CODES + NUM_LENGTH_CODES) +// Cut-off value for switching between heap and stack allocation. +#define SORTED_SIZE_CUTOFF 512 +int VP8LBuildHuffmanTable(HuffmanCode* const root_table, int root_bits, + const int code_lengths[], int code_lengths_size) { + int total_size; + assert(code_lengths_size <= MAX_CODE_LENGTHS_SIZE); + if (code_lengths_size <= SORTED_SIZE_CUTOFF) { + // use local stack-allocated array. + uint16_t sorted[SORTED_SIZE_CUTOFF]; + total_size = BuildHuffmanTable(root_table, root_bits, + code_lengths, code_lengths_size, sorted); + } else { // rare case. Use heap allocation. + uint16_t* const sorted = + (uint16_t*)WebPSafeMalloc(code_lengths_size, sizeof(*sorted)); + if (sorted == NULL) return 0; + total_size = BuildHuffmanTable(root_table, root_bits, + code_lengths, code_lengths_size, sorted); + WebPSafeFree(sorted); + } + return total_size; +} diff --git a/3rdparty/libwebp/utils/huffman_utils.h b/3rdparty/libwebp/utils/huffman_utils.h new file mode 100644 index 0000000000..c6dd6aaa45 --- /dev/null +++ b/3rdparty/libwebp/utils/huffman_utils.h @@ -0,0 +1,88 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for building and looking up Huffman trees. +// +// Author: Urvang Joshi (urvang@google.com) + +#ifndef WEBP_UTILS_HUFFMAN_H_ +#define WEBP_UTILS_HUFFMAN_H_ + +#include +#include "../webp/format_constants.h" +#include "../webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HUFFMAN_TABLE_BITS 8 +#define HUFFMAN_TABLE_MASK ((1 << HUFFMAN_TABLE_BITS) - 1) + +#define LENGTHS_TABLE_BITS 7 +#define LENGTHS_TABLE_MASK ((1 << LENGTHS_TABLE_BITS) - 1) + + +// Huffman lookup table entry +typedef struct { + uint8_t bits; // number of bits used for this symbol + uint16_t value; // symbol value or table offset +} HuffmanCode; + +// long version for holding 32b values +typedef struct { + int bits; // number of bits used for this symbol, + // or an impossible value if not a literal code. + uint32_t value; // 32b packed ARGB value if literal, + // or non-literal symbol otherwise +} HuffmanCode32; + +#define HUFFMAN_PACKED_BITS 6 +#define HUFFMAN_PACKED_TABLE_SIZE (1u << HUFFMAN_PACKED_BITS) + +// Huffman table group. +// Includes special handling for the following cases: +// - is_trivial_literal: one common literal base for RED/BLUE/ALPHA (not GREEN) +// - is_trivial_code: only 1 code (no bit is read from bitstream) +// - use_packed_table: few enough literal symbols, so all the bit codes +// can fit into a small look-up table packed_table[] +// The common literal base, if applicable, is stored in 'literal_arb'. +typedef struct HTreeGroup HTreeGroup; +struct HTreeGroup { + HuffmanCode* htrees[HUFFMAN_CODES_PER_META_CODE]; + int is_trivial_literal; // True, if huffman trees for Red, Blue & Alpha + // Symbols are trivial (have a single code). + uint32_t literal_arb; // If is_trivial_literal is true, this is the + // ARGB value of the pixel, with Green channel + // being set to zero. + int is_trivial_code; // true if is_trivial_literal with only one code + int use_packed_table; // use packed table below for short literal code + // table mapping input bits to a packed values, or escape case to literal code + HuffmanCode32 packed_table[HUFFMAN_PACKED_TABLE_SIZE]; +}; + +// Creates the instance of HTreeGroup with specified number of tree-groups. +HTreeGroup* VP8LHtreeGroupsNew(int num_htree_groups); + +// Releases the memory allocated for HTreeGroup. +void VP8LHtreeGroupsFree(HTreeGroup* const htree_groups); + +// Builds Huffman lookup table assuming code lengths are in symbol order. +// The 'code_lengths' is pre-allocated temporary memory buffer used for creating +// the huffman table. +// Returns built table size or 0 in case of error (invalid tree or +// memory error). +int VP8LBuildHuffmanTable(HuffmanCode* const root_table, int root_bits, + const int code_lengths[], int code_lengths_size); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_HUFFMAN_H_ diff --git a/3rdparty/libwebp/utils/quant_levels_dec.c b/3rdparty/libwebp/utils/quant_levels_dec.c deleted file mode 100644 index d93594b3ba..0000000000 --- a/3rdparty/libwebp/utils/quant_levels_dec.c +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2013 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// TODO(skal): implement gradient smoothing. -// -// Author: Skal (pascal.massimino@gmail.com) - -#include "./quant_levels_dec.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -int DequantizeLevels(uint8_t* const data, int width, int height) { - if (data == NULL || width <= 0 || height <= 0) return 0; - (void)data; - (void)width; - (void)height; - return 1; -} - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/utils/quant_levels_dec_utils.c b/3rdparty/libwebp/utils/quant_levels_dec_utils.c new file mode 100644 index 0000000000..d4d23d3147 --- /dev/null +++ b/3rdparty/libwebp/utils/quant_levels_dec_utils.c @@ -0,0 +1,284 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Implement gradient smoothing: we replace a current alpha value by its +// surrounding average if it's close enough (that is: the change will be less +// than the minimum distance between two quantized level). +// We use sliding window for computing the 2d moving average. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./quant_levels_dec_utils.h" + +#include // for memset + +#include "./utils.h" + +// #define USE_DITHERING // uncomment to enable ordered dithering (not vital) + +#define FIX 16 // fix-point precision for averaging +#define LFIX 2 // extra precision for look-up table +#define LUT_SIZE ((1 << (8 + LFIX)) - 1) // look-up table size + +#if defined(USE_DITHERING) + +#define DFIX 4 // extra precision for ordered dithering +#define DSIZE 4 // dithering size (must be a power of two) +// cf. http://en.wikipedia.org/wiki/Ordered_dithering +static const uint8_t kOrderedDither[DSIZE][DSIZE] = { + { 0, 8, 2, 10 }, // coefficients are in DFIX fixed-point precision + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 } +}; + +#else +#define DFIX 0 +#endif + +typedef struct { + int width_, height_; // dimension + int stride_; // stride in bytes + int row_; // current input row being processed + uint8_t* src_; // input pointer + uint8_t* dst_; // output pointer + + int radius_; // filter radius (=delay) + int scale_; // normalization factor, in FIX bits precision + + void* mem_; // all memory + + // various scratch buffers + uint16_t* start_; + uint16_t* cur_; + uint16_t* end_; + uint16_t* top_; + uint16_t* average_; + + // input levels distribution + int num_levels_; // number of quantized levels + int min_, max_; // min and max level values + int min_level_dist_; // smallest distance between two consecutive levels + + int16_t* correction_; // size = 1 + 2*LUT_SIZE -> ~4k memory +} SmoothParams; + +//------------------------------------------------------------------------------ + +#define CLIP_MASK (int)(~0U << (8 + DFIX)) +static WEBP_INLINE uint8_t clip_8b(int v) { + return (!(v & CLIP_MASK)) ? (uint8_t)(v >> DFIX) : (v < 0) ? 0u : 255u; +} + +// vertical accumulation +static void VFilter(SmoothParams* const p) { + const uint8_t* src = p->src_; + const int w = p->width_; + uint16_t* const cur = p->cur_; + const uint16_t* const top = p->top_; + uint16_t* const out = p->end_; + uint16_t sum = 0; // all arithmetic is modulo 16bit + int x; + + for (x = 0; x < w; ++x) { + uint16_t new_value; + sum += src[x]; + new_value = top[x] + sum; + out[x] = new_value - cur[x]; // vertical sum of 'r' pixels. + cur[x] = new_value; + } + // move input pointers one row down + p->top_ = p->cur_; + p->cur_ += w; + if (p->cur_ == p->end_) p->cur_ = p->start_; // roll-over + // We replicate edges, as it's somewhat easier as a boundary condition. + // That's why we don't update the 'src' pointer on top/bottom area: + if (p->row_ >= 0 && p->row_ < p->height_ - 1) { + p->src_ += p->stride_; + } +} + +// horizontal accumulation. We use mirror replication of missing pixels, as it's +// a little easier to implement (surprisingly). +static void HFilter(SmoothParams* const p) { + const uint16_t* const in = p->end_; + uint16_t* const out = p->average_; + const uint32_t scale = p->scale_; + const int w = p->width_; + const int r = p->radius_; + + int x; + for (x = 0; x <= r; ++x) { // left mirroring + const uint16_t delta = in[x + r - 1] + in[r - x]; + out[x] = (delta * scale) >> FIX; + } + for (; x < w - r; ++x) { // bulk middle run + const uint16_t delta = in[x + r] - in[x - r - 1]; + out[x] = (delta * scale) >> FIX; + } + for (; x < w; ++x) { // right mirroring + const uint16_t delta = + 2 * in[w - 1] - in[2 * w - 2 - r - x] - in[x - r - 1]; + out[x] = (delta * scale) >> FIX; + } +} + +// emit one filtered output row +static void ApplyFilter(SmoothParams* const p) { + const uint16_t* const average = p->average_; + const int w = p->width_; + const int16_t* const correction = p->correction_; +#if defined(USE_DITHERING) + const uint8_t* const dither = kOrderedDither[p->row_ % DSIZE]; +#endif + uint8_t* const dst = p->dst_; + int x; + for (x = 0; x < w; ++x) { + const int v = dst[x]; + if (v < p->max_ && v > p->min_) { + const int c = (v << DFIX) + correction[average[x] - (v << LFIX)]; +#if defined(USE_DITHERING) + dst[x] = clip_8b(c + dither[x % DSIZE]); +#else + dst[x] = clip_8b(c); +#endif + } + } + p->dst_ += p->stride_; // advance output pointer +} + +//------------------------------------------------------------------------------ +// Initialize correction table + +static void InitCorrectionLUT(int16_t* const lut, int min_dist) { + // The correction curve is: + // f(x) = x for x <= threshold2 + // f(x) = 0 for x >= threshold1 + // and a linear interpolation for range x=[threshold2, threshold1] + // (along with f(-x) = -f(x) symmetry). + // Note that: threshold2 = 3/4 * threshold1 + const int threshold1 = min_dist << LFIX; + const int threshold2 = (3 * threshold1) >> 2; + const int max_threshold = threshold2 << DFIX; + const int delta = threshold1 - threshold2; + int i; + for (i = 1; i <= LUT_SIZE; ++i) { + int c = (i <= threshold2) ? (i << DFIX) + : (i < threshold1) ? max_threshold * (threshold1 - i) / delta + : 0; + c >>= LFIX; + lut[+i] = +c; + lut[-i] = -c; + } + lut[0] = 0; +} + +static void CountLevels(SmoothParams* const p) { + int i, j, last_level; + uint8_t used_levels[256] = { 0 }; + const uint8_t* data = p->src_; + p->min_ = 255; + p->max_ = 0; + for (j = 0; j < p->height_; ++j) { + for (i = 0; i < p->width_; ++i) { + const int v = data[i]; + if (v < p->min_) p->min_ = v; + if (v > p->max_) p->max_ = v; + used_levels[v] = 1; + } + data += p->stride_; + } + // Compute the mininum distance between two non-zero levels. + p->min_level_dist_ = p->max_ - p->min_; + last_level = -1; + for (i = 0; i < 256; ++i) { + if (used_levels[i]) { + ++p->num_levels_; + if (last_level >= 0) { + const int level_dist = i - last_level; + if (level_dist < p->min_level_dist_) { + p->min_level_dist_ = level_dist; + } + } + last_level = i; + } + } +} + +// Initialize all params. +static int InitParams(uint8_t* const data, int width, int height, int stride, + int radius, SmoothParams* const p) { + const int R = 2 * radius + 1; // total size of the kernel + + const size_t size_scratch_m = (R + 1) * width * sizeof(*p->start_); + const size_t size_m = width * sizeof(*p->average_); + const size_t size_lut = (1 + 2 * LUT_SIZE) * sizeof(*p->correction_); + const size_t total_size = size_scratch_m + size_m + size_lut; + uint8_t* mem = (uint8_t*)WebPSafeMalloc(1U, total_size); + + if (mem == NULL) return 0; + p->mem_ = (void*)mem; + + p->start_ = (uint16_t*)mem; + p->cur_ = p->start_; + p->end_ = p->start_ + R * width; + p->top_ = p->end_ - width; + memset(p->top_, 0, width * sizeof(*p->top_)); + mem += size_scratch_m; + + p->average_ = (uint16_t*)mem; + mem += size_m; + + p->width_ = width; + p->height_ = height; + p->stride_ = stride; + p->src_ = data; + p->dst_ = data; + p->radius_ = radius; + p->scale_ = (1 << (FIX + LFIX)) / (R * R); // normalization constant + p->row_ = -radius; + + // analyze the input distribution so we can best-fit the threshold + CountLevels(p); + + // correction table + p->correction_ = ((int16_t*)mem) + LUT_SIZE; + InitCorrectionLUT(p->correction_, p->min_level_dist_); + + return 1; +} + +static void CleanupParams(SmoothParams* const p) { + WebPSafeFree(p->mem_); +} + +int WebPDequantizeLevels(uint8_t* const data, int width, int height, int stride, + int strength) { + const int radius = 4 * strength / 100; + if (strength < 0 || strength > 100) return 0; + if (data == NULL || width <= 0 || height <= 0) return 0; // bad params + if (radius > 0) { + SmoothParams p; + memset(&p, 0, sizeof(p)); + if (!InitParams(data, width, height, stride, radius, &p)) return 0; + if (p.num_levels_ > 2) { + for (; p.row_ < p.height_; ++p.row_) { + VFilter(&p); // accumulate average of input + // Need to wait few rows in order to prime the filter, + // before emitting some output. + if (p.row_ >= p.radius_) { + HFilter(&p); + ApplyFilter(&p); + } + } + } + CleanupParams(&p); + } + return 1; +} diff --git a/3rdparty/libwebp/utils/quant_levels_dec.h b/3rdparty/libwebp/utils/quant_levels_dec_utils.h similarity index 71% rename from 3rdparty/libwebp/utils/quant_levels_dec.h rename to 3rdparty/libwebp/utils/quant_levels_dec_utils.h index 5891067650..59a13495d3 100644 --- a/3rdparty/libwebp/utils/quant_levels_dec.h +++ b/3rdparty/libwebp/utils/quant_levels_dec_utils.h @@ -16,16 +16,19 @@ #include "../webp/types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -// Apply post-processing to input 'data' of size 'width'x'height' assuming -// that the source was quantized to a reduced number of levels. -// Returns false in case of error (data is NULL, invalid parameters, ...). -int DequantizeLevels(uint8_t* const data, int width, int height); +// Apply post-processing to input 'data' of size 'width'x'height' assuming that +// the source was quantized to a reduced number of levels. 'stride' is in bytes. +// Strength is in [0..100] and controls the amount of dithering applied. +// Returns false in case of error (data is NULL, invalid parameters, +// malloc failure, ...). +int WebPDequantizeLevels(uint8_t* const data, int width, int height, int stride, + int strength); -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/utils/quant_levels.c b/3rdparty/libwebp/utils/quant_levels_utils.c similarity index 95% rename from 3rdparty/libwebp/utils/quant_levels.c rename to 3rdparty/libwebp/utils/quant_levels_utils.c index 42c7245d36..73174e8ab9 100644 --- a/3rdparty/libwebp/utils/quant_levels.c +++ b/3rdparty/libwebp/utils/quant_levels_utils.c @@ -14,11 +14,7 @@ #include -#include "./quant_levels.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./quant_levels_utils.h" #define NUM_SYMBOLS 256 @@ -142,6 +138,3 @@ int QuantizeLevels(uint8_t* const data, int width, int height, return 1; } -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/utils/quant_levels.h b/3rdparty/libwebp/utils/quant_levels_utils.h similarity index 92% rename from 3rdparty/libwebp/utils/quant_levels.h rename to 3rdparty/libwebp/utils/quant_levels_utils.h index 2d90828df9..1cb5a32cae 100644 --- a/3rdparty/libwebp/utils/quant_levels.h +++ b/3rdparty/libwebp/utils/quant_levels_utils.h @@ -18,7 +18,7 @@ #include "../webp/types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -29,7 +29,7 @@ extern "C" { int QuantizeLevels(uint8_t* const data, int width, int height, int num_levels, uint64_t* const sse); -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/utils/random_utils.c b/3rdparty/libwebp/utils/random_utils.c new file mode 100644 index 0000000000..9f1e4154a6 --- /dev/null +++ b/3rdparty/libwebp/utils/random_utils.c @@ -0,0 +1,43 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Pseudo-random utilities +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include "./random_utils.h" + +//------------------------------------------------------------------------------ + +// 31b-range values +static const uint32_t kRandomTable[VP8_RANDOM_TABLE_SIZE] = { + 0x0de15230, 0x03b31886, 0x775faccb, 0x1c88626a, 0x68385c55, 0x14b3b828, + 0x4a85fef8, 0x49ddb84b, 0x64fcf397, 0x5c550289, 0x4a290000, 0x0d7ec1da, + 0x5940b7ab, 0x5492577d, 0x4e19ca72, 0x38d38c69, 0x0c01ee65, 0x32a1755f, + 0x5437f652, 0x5abb2c32, 0x0faa57b1, 0x73f533e7, 0x685feeda, 0x7563cce2, + 0x6e990e83, 0x4730a7ed, 0x4fc0d9c6, 0x496b153c, 0x4f1403fa, 0x541afb0c, + 0x73990b32, 0x26d7cb1c, 0x6fcc3706, 0x2cbb77d8, 0x75762f2a, 0x6425ccdd, + 0x24b35461, 0x0a7d8715, 0x220414a8, 0x141ebf67, 0x56b41583, 0x73e502e3, + 0x44cab16f, 0x28264d42, 0x73baaefb, 0x0a50ebed, 0x1d6ab6fb, 0x0d3ad40b, + 0x35db3b68, 0x2b081e83, 0x77ce6b95, 0x5181e5f0, 0x78853bbc, 0x009f9494, + 0x27e5ed3c +}; + +void VP8InitRandom(VP8Random* const rg, float dithering) { + memcpy(rg->tab_, kRandomTable, sizeof(rg->tab_)); + rg->index1_ = 0; + rg->index2_ = 31; + rg->amp_ = (dithering < 0.0) ? 0 + : (dithering > 1.0) ? (1 << VP8_RANDOM_DITHER_FIX) + : (uint32_t)((1 << VP8_RANDOM_DITHER_FIX) * dithering); +} + +//------------------------------------------------------------------------------ + diff --git a/3rdparty/libwebp/utils/random_utils.h b/3rdparty/libwebp/utils/random_utils.h new file mode 100644 index 0000000000..c392a615ca --- /dev/null +++ b/3rdparty/libwebp/utils/random_utils.h @@ -0,0 +1,63 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Pseudo-random utilities +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_RANDOM_H_ +#define WEBP_UTILS_RANDOM_H_ + +#include +#include "../webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VP8_RANDOM_DITHER_FIX 8 // fixed-point precision for dithering +#define VP8_RANDOM_TABLE_SIZE 55 + +typedef struct { + int index1_, index2_; + uint32_t tab_[VP8_RANDOM_TABLE_SIZE]; + int amp_; +} VP8Random; + +// Initializes random generator with an amplitude 'dithering' in range [0..1]. +void VP8InitRandom(VP8Random* const rg, float dithering); + +// Returns a centered pseudo-random number with 'num_bits' amplitude. +// (uses D.Knuth's Difference-based random generator). +// 'amp' is in VP8_RANDOM_DITHER_FIX fixed-point precision. +static WEBP_INLINE int VP8RandomBits2(VP8Random* const rg, int num_bits, + int amp) { + int diff; + assert(num_bits + VP8_RANDOM_DITHER_FIX <= 31); + diff = rg->tab_[rg->index1_] - rg->tab_[rg->index2_]; + if (diff < 0) diff += (1u << 31); + rg->tab_[rg->index1_] = diff; + if (++rg->index1_ == VP8_RANDOM_TABLE_SIZE) rg->index1_ = 0; + if (++rg->index2_ == VP8_RANDOM_TABLE_SIZE) rg->index2_ = 0; + // sign-extend, 0-center + diff = (int)((uint32_t)diff << 1) >> (32 - num_bits); + diff = (diff * amp) >> VP8_RANDOM_DITHER_FIX; // restrict range + diff += 1 << (num_bits - 1); // shift back to 0.5-center + return diff; +} + +static WEBP_INLINE int VP8RandomBits(VP8Random* const rg, int num_bits) { + return VP8RandomBits2(rg, num_bits, rg->amp_); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_UTILS_RANDOM_H_ */ diff --git a/3rdparty/libwebp/utils/rescaler.c b/3rdparty/libwebp/utils/rescaler.c deleted file mode 100644 index e5ddc296ab..0000000000 --- a/3rdparty/libwebp/utils/rescaler.c +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Rescaling functions -// -// Author: Skal (pascal.massimino@gmail.com) - -#include -#include -#include "./rescaler.h" - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#define RFIX 30 -#define MULT_FIX(x, y) (((int64_t)(x) * (y) + (1 << (RFIX - 1))) >> RFIX) - -void WebPRescalerInit(WebPRescaler* const wrk, int src_width, int src_height, - uint8_t* const dst, int dst_width, int dst_height, - int dst_stride, int num_channels, int x_add, int x_sub, - int y_add, int y_sub, int32_t* const work) { - wrk->x_expand = (src_width < dst_width); - wrk->src_width = src_width; - wrk->src_height = src_height; - wrk->dst_width = dst_width; - wrk->dst_height = dst_height; - wrk->dst = dst; - wrk->dst_stride = dst_stride; - wrk->num_channels = num_channels; - // for 'x_expand', we use bilinear interpolation - wrk->x_add = wrk->x_expand ? (x_sub - 1) : x_add - x_sub; - wrk->x_sub = wrk->x_expand ? (x_add - 1) : x_sub; - wrk->y_accum = y_add; - wrk->y_add = y_add; - wrk->y_sub = y_sub; - wrk->fx_scale = (1 << RFIX) / x_sub; - wrk->fy_scale = (1 << RFIX) / y_sub; - wrk->fxy_scale = wrk->x_expand ? - ((int64_t)dst_height << RFIX) / (x_sub * src_height) : - ((int64_t)dst_height << RFIX) / (x_add * src_height); - wrk->irow = work; - wrk->frow = work + num_channels * dst_width; -} - -void WebPRescalerImportRow(WebPRescaler* const wrk, - const uint8_t* const src, int channel) { - const int x_stride = wrk->num_channels; - const int x_out_max = wrk->dst_width * wrk->num_channels; - int x_in = channel; - int x_out; - int accum = 0; - if (!wrk->x_expand) { - int sum = 0; - for (x_out = channel; x_out < x_out_max; x_out += x_stride) { - accum += wrk->x_add; - for (; accum > 0; accum -= wrk->x_sub) { - sum += src[x_in]; - x_in += x_stride; - } - { // Emit next horizontal pixel. - const int32_t base = src[x_in]; - const int32_t frac = base * (-accum); - x_in += x_stride; - wrk->frow[x_out] = (sum + base) * wrk->x_sub - frac; - // fresh fractional start for next pixel - sum = (int)MULT_FIX(frac, wrk->fx_scale); - } - } - } else { // simple bilinear interpolation - int left = src[channel], right = src[channel]; - for (x_out = channel; x_out < x_out_max; x_out += x_stride) { - if (accum < 0) { - left = right; - x_in += x_stride; - right = src[x_in]; - accum += wrk->x_add; - } - wrk->frow[x_out] = right * wrk->x_add + (left - right) * accum; - accum -= wrk->x_sub; - } - } - // Accumulate the new row's contribution - for (x_out = channel; x_out < x_out_max; x_out += x_stride) { - wrk->irow[x_out] += wrk->frow[x_out]; - } -} - -uint8_t* WebPRescalerExportRow(WebPRescaler* const wrk) { - if (wrk->y_accum <= 0) { - int x_out; - uint8_t* const dst = wrk->dst; - int32_t* const irow = wrk->irow; - const int32_t* const frow = wrk->frow; - const int yscale = wrk->fy_scale * (-wrk->y_accum); - const int x_out_max = wrk->dst_width * wrk->num_channels; - - for (x_out = 0; x_out < x_out_max; ++x_out) { - const int frac = (int)MULT_FIX(frow[x_out], yscale); - const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale); - dst[x_out] = (!(v & ~0xff)) ? v : (v < 0) ? 0 : 255; - irow[x_out] = frac; // new fractional start - } - wrk->y_accum += wrk->y_add; - wrk->dst += wrk->dst_stride; - return dst; - } else { - return NULL; - } -} - -#undef MULT_FIX -#undef RFIX - -//------------------------------------------------------------------------------ -// all-in-one calls - -int WebPRescalerImport(WebPRescaler* const wrk, int num_lines, - const uint8_t* src, int src_stride) { - int total_imported = 0; - while (total_imported < num_lines && wrk->y_accum > 0) { - int channel; - for (channel = 0; channel < wrk->num_channels; ++channel) { - WebPRescalerImportRow(wrk, src, channel); - } - src += src_stride; - ++total_imported; - wrk->y_accum -= wrk->y_sub; - } - return total_imported; -} - -int WebPRescalerExport(WebPRescaler* const rescaler) { - int total_exported = 0; - while (WebPRescalerHasPendingOutput(rescaler)) { - WebPRescalerExportRow(rescaler); - ++total_exported; - } - return total_exported; -} - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif diff --git a/3rdparty/libwebp/utils/rescaler.h b/3rdparty/libwebp/utils/rescaler.h deleted file mode 100644 index aedce46254..0000000000 --- a/3rdparty/libwebp/utils/rescaler.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Rescaling functions -// -// Author: Skal (pascal.massimino@gmail.com) - -#ifndef WEBP_UTILS_RESCALER_H_ -#define WEBP_UTILS_RESCALER_H_ - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#include "../webp/types.h" - -// Structure used for on-the-fly rescaling -typedef struct { - int x_expand; // true if we're expanding in the x direction - int num_channels; // bytes to jump between pixels - int fy_scale, fx_scale; // fixed-point scaling factor - int64_t fxy_scale; // '' - // we need hpel-precise add/sub increments, for the downsampled U/V planes. - int y_accum; // vertical accumulator - int y_add, y_sub; // vertical increments (add ~= src, sub ~= dst) - int x_add, x_sub; // horizontal increments (add ~= src, sub ~= dst) - int src_width, src_height; // source dimensions - int dst_width, dst_height; // destination dimensions - uint8_t* dst; - int dst_stride; - int32_t* irow, *frow; // work buffer -} WebPRescaler; - -// Initialize a rescaler given scratch area 'work' and dimensions of src & dst. -void WebPRescalerInit(WebPRescaler* const wrk, int src_width, int src_height, - uint8_t* const dst, - int dst_width, int dst_height, int dst_stride, - int num_channels, - int x_add, int x_sub, - int y_add, int y_sub, - int32_t* const work); - -// Import a row of data and save its contribution in the rescaler. -// 'channel' denotes the channel number to be imported. -void WebPRescalerImportRow(WebPRescaler* const rescaler, - const uint8_t* const src, int channel); - -// Import multiple rows over all channels, until at least one row is ready to -// be exported. Returns the actual number of lines that were imported. -int WebPRescalerImport(WebPRescaler* const rescaler, int num_rows, - const uint8_t* src, int src_stride); - -// Return true if there is pending output rows ready. -static WEBP_INLINE -int WebPRescalerHasPendingOutput(const WebPRescaler* const rescaler) { - return (rescaler->y_accum <= 0); -} - -// Export one row from rescaler. Returns the pointer where output was written, -// or NULL if no row was pending. -uint8_t* WebPRescalerExportRow(WebPRescaler* const wrk); - -// Export as many rows as possible. Return the numbers of rows written. -int WebPRescalerExport(WebPRescaler* const wrk); - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif - -#endif /* WEBP_UTILS_RESCALER_H_ */ diff --git a/3rdparty/libwebp/utils/rescaler_utils.c b/3rdparty/libwebp/utils/rescaler_utils.c new file mode 100644 index 0000000000..0d1f80da24 --- /dev/null +++ b/3rdparty/libwebp/utils/rescaler_utils.c @@ -0,0 +1,146 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include +#include "../dsp/dsp.h" +#include "./rescaler_utils.h" + +//------------------------------------------------------------------------------ + +void WebPRescalerInit(WebPRescaler* const wrk, int src_width, int src_height, + uint8_t* const dst, + int dst_width, int dst_height, int dst_stride, + int num_channels, rescaler_t* const work) { + const int x_add = src_width, x_sub = dst_width; + const int y_add = src_height, y_sub = dst_height; + wrk->x_expand = (src_width < dst_width); + wrk->y_expand = (src_height < dst_height); + wrk->src_width = src_width; + wrk->src_height = src_height; + wrk->dst_width = dst_width; + wrk->dst_height = dst_height; + wrk->src_y = 0; + wrk->dst_y = 0; + wrk->dst = dst; + wrk->dst_stride = dst_stride; + wrk->num_channels = num_channels; + + // for 'x_expand', we use bilinear interpolation + wrk->x_add = wrk->x_expand ? (x_sub - 1) : x_add; + wrk->x_sub = wrk->x_expand ? (x_add - 1) : x_sub; + if (!wrk->x_expand) { // fx_scale is not used otherwise + wrk->fx_scale = WEBP_RESCALER_FRAC(1, wrk->x_sub); + } + // vertical scaling parameters + wrk->y_add = wrk->y_expand ? y_add - 1 : y_add; + wrk->y_sub = wrk->y_expand ? y_sub - 1 : y_sub; + wrk->y_accum = wrk->y_expand ? wrk->y_sub : wrk->y_add; + if (!wrk->y_expand) { + // This is WEBP_RESCALER_FRAC(dst_height, x_add * y_add) without the cast. + // Its value is <= WEBP_RESCALER_ONE, because dst_height <= wrk->y_add, and + // wrk->x_add >= 1; + const uint64_t ratio = + (uint64_t)dst_height * WEBP_RESCALER_ONE / (wrk->x_add * wrk->y_add); + if (ratio != (uint32_t)ratio) { + // When ratio == WEBP_RESCALER_ONE, we can't represent the ratio with the + // current fixed-point precision. This happens when src_height == + // wrk->y_add (which == src_height), and wrk->x_add == 1. + // => We special-case fxy_scale = 0, in WebPRescalerExportRow(). + wrk->fxy_scale = 0; + } else { + wrk->fxy_scale = (uint32_t)ratio; + } + wrk->fy_scale = WEBP_RESCALER_FRAC(1, wrk->y_sub); + } else { + wrk->fy_scale = WEBP_RESCALER_FRAC(1, wrk->x_add); + // wrk->fxy_scale is unused here. + } + wrk->irow = work; + wrk->frow = work + num_channels * dst_width; + memset(work, 0, 2 * dst_width * num_channels * sizeof(*work)); + + WebPRescalerDspInit(); +} + +int WebPRescalerGetScaledDimensions(int src_width, int src_height, + int* const scaled_width, + int* const scaled_height) { + assert(scaled_width != NULL); + assert(scaled_height != NULL); + { + int width = *scaled_width; + int height = *scaled_height; + + // if width is unspecified, scale original proportionally to height ratio. + if (width == 0) { + width = (src_width * height + src_height / 2) / src_height; + } + // if height is unspecified, scale original proportionally to width ratio. + if (height == 0) { + height = (src_height * width + src_width / 2) / src_width; + } + // Check if the overall dimensions still make sense. + if (width <= 0 || height <= 0) { + return 0; + } + + *scaled_width = width; + *scaled_height = height; + return 1; + } +} + +//------------------------------------------------------------------------------ +// all-in-one calls + +int WebPRescaleNeededLines(const WebPRescaler* const wrk, int max_num_lines) { + const int num_lines = (wrk->y_accum + wrk->y_sub - 1) / wrk->y_sub; + return (num_lines > max_num_lines) ? max_num_lines : num_lines; +} + +int WebPRescalerImport(WebPRescaler* const wrk, int num_lines, + const uint8_t* src, int src_stride) { + int total_imported = 0; + while (total_imported < num_lines && !WebPRescalerHasPendingOutput(wrk)) { + if (wrk->y_expand) { + rescaler_t* const tmp = wrk->irow; + wrk->irow = wrk->frow; + wrk->frow = tmp; + } + WebPRescalerImportRow(wrk, src); + if (!wrk->y_expand) { // Accumulate the contribution of the new row. + int x; + for (x = 0; x < wrk->num_channels * wrk->dst_width; ++x) { + wrk->irow[x] += wrk->frow[x]; + } + } + ++wrk->src_y; + src += src_stride; + ++total_imported; + wrk->y_accum -= wrk->y_sub; + } + return total_imported; +} + +int WebPRescalerExport(WebPRescaler* const rescaler) { + int total_exported = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + WebPRescalerExportRow(rescaler); + ++total_exported; + } + return total_exported; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/utils/rescaler_utils.h b/3rdparty/libwebp/utils/rescaler_utils.h new file mode 100644 index 0000000000..98b01a76d0 --- /dev/null +++ b/3rdparty/libwebp/utils/rescaler_utils.h @@ -0,0 +1,101 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_RESCALER_H_ +#define WEBP_UTILS_RESCALER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../webp/types.h" + +#define WEBP_RESCALER_RFIX 32 // fixed-point precision for multiplies +#define WEBP_RESCALER_ONE (1ull << WEBP_RESCALER_RFIX) +#define WEBP_RESCALER_FRAC(x, y) \ + ((uint32_t)(((uint64_t)(x) << WEBP_RESCALER_RFIX) / (y))) + +// Structure used for on-the-fly rescaling +typedef uint32_t rescaler_t; // type for side-buffer +typedef struct WebPRescaler WebPRescaler; +struct WebPRescaler { + int x_expand; // true if we're expanding in the x direction + int y_expand; // true if we're expanding in the y direction + int num_channels; // bytes to jump between pixels + uint32_t fx_scale; // fixed-point scaling factors + uint32_t fy_scale; // '' + uint32_t fxy_scale; // '' + int y_accum; // vertical accumulator + int y_add, y_sub; // vertical increments + int x_add, x_sub; // horizontal increments + int src_width, src_height; // source dimensions + int dst_width, dst_height; // destination dimensions + int src_y, dst_y; // row counters for input and output + uint8_t* dst; + int dst_stride; + rescaler_t* irow, *frow; // work buffer +}; + +// Initialize a rescaler given scratch area 'work' and dimensions of src & dst. +void WebPRescalerInit(WebPRescaler* const rescaler, + int src_width, int src_height, + uint8_t* const dst, + int dst_width, int dst_height, int dst_stride, + int num_channels, + rescaler_t* const work); + +// If either 'scaled_width' or 'scaled_height' (but not both) is 0 the value +// will be calculated preserving the aspect ratio, otherwise the values are +// left unmodified. Returns true on success, false if either value is 0 after +// performing the scaling calculation. +int WebPRescalerGetScaledDimensions(int src_width, int src_height, + int* const scaled_width, + int* const scaled_height); + +// Returns the number of input lines needed next to produce one output line, +// considering that the maximum available input lines are 'max_num_lines'. +int WebPRescaleNeededLines(const WebPRescaler* const rescaler, + int max_num_lines); + +// Import multiple rows over all channels, until at least one row is ready to +// be exported. Returns the actual number of lines that were imported. +int WebPRescalerImport(WebPRescaler* const rescaler, int num_rows, + const uint8_t* src, int src_stride); + +// Export as many rows as possible. Return the numbers of rows written. +int WebPRescalerExport(WebPRescaler* const rescaler); + +// Return true if input is finished +static WEBP_INLINE +int WebPRescalerInputDone(const WebPRescaler* const rescaler) { + return (rescaler->src_y >= rescaler->src_height); +} +// Return true if output is finished +static WEBP_INLINE +int WebPRescalerOutputDone(const WebPRescaler* const rescaler) { + return (rescaler->dst_y >= rescaler->dst_height); +} + +// Return true if there are pending output rows ready. +static WEBP_INLINE +int WebPRescalerHasPendingOutput(const WebPRescaler* const rescaler) { + return !WebPRescalerOutputDone(rescaler) && (rescaler->y_accum <= 0); +} + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_UTILS_RESCALER_H_ */ diff --git a/3rdparty/libwebp/utils/thread.h b/3rdparty/libwebp/utils/thread.h deleted file mode 100644 index 13a61a4c84..0000000000 --- a/3rdparty/libwebp/utils/thread.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. -// ----------------------------------------------------------------------------- -// -// Multi-threaded worker -// -// Author: Skal (pascal.massimino@gmail.com) - -#ifndef WEBP_UTILS_THREAD_H_ -#define WEBP_UTILS_THREAD_H_ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#if WEBP_USE_THREAD - -#if defined(_WIN32) - -#include -typedef HANDLE pthread_t; -typedef CRITICAL_SECTION pthread_mutex_t; -typedef struct { - HANDLE waiting_sem_; - HANDLE received_sem_; - HANDLE signal_event_; -} pthread_cond_t; - -#else - -#include - -#endif /* _WIN32 */ -#endif /* WEBP_USE_THREAD */ - -// State of the worker thread object -typedef enum { - NOT_OK = 0, // object is unusable - OK, // ready to work - WORK // busy finishing the current task -} WebPWorkerStatus; - -// Function to be called by the worker thread. Takes two opaque pointers as -// arguments (data1 and data2), and should return false in case of error. -typedef int (*WebPWorkerHook)(void*, void*); - -// Synchronize object used to launch job in the worker thread -typedef struct { -#if WEBP_USE_THREAD - pthread_mutex_t mutex_; - pthread_cond_t condition_; - pthread_t thread_; -#endif - WebPWorkerStatus status_; - WebPWorkerHook hook; // hook to call - void* data1; // first argument passed to 'hook' - void* data2; // second argument passed to 'hook' - int had_error; // return value of the last call to 'hook' -} WebPWorker; - -// Must be called first, before any other method. -void WebPWorkerInit(WebPWorker* const worker); -// Must be called to initialize the object and spawn the thread. Re-entrant. -// Will potentially launch the thread. Returns false in case of error. -int WebPWorkerReset(WebPWorker* const worker); -// Makes sure the previous work is finished. Returns true if worker->had_error -// was not set and no error condition was triggered by the working thread. -int WebPWorkerSync(WebPWorker* const worker); -// Triggers the thread to call hook() with data1 and data2 argument. These -// hook/data1/data2 can be changed at any time before calling this function, -// but not be changed afterward until the next call to WebPWorkerSync(). -void WebPWorkerLaunch(WebPWorker* const worker); -// Kill the thread and terminate the object. To use the object again, one -// must call WebPWorkerReset() again. -void WebPWorkerEnd(WebPWorker* const worker); - -//------------------------------------------------------------------------------ - -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif - -#endif /* WEBP_UTILS_THREAD_H_ */ diff --git a/3rdparty/libwebp/utils/thread.c b/3rdparty/libwebp/utils/thread_utils.c similarity index 51% rename from 3rdparty/libwebp/utils/thread.c rename to 3rdparty/libwebp/utils/thread_utils.c index b1615d0fb8..1729060c70 100644 --- a/3rdparty/libwebp/utils/thread.c +++ b/3rdparty/libwebp/utils/thread_utils.c @@ -13,16 +13,51 @@ #include #include // for memset() -#include "./thread.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "./thread_utils.h" +#include "./utils.h" #ifdef WEBP_USE_THREAD #if defined(_WIN32) +#include +typedef HANDLE pthread_t; +typedef CRITICAL_SECTION pthread_mutex_t; + +#if _WIN32_WINNT >= 0x0600 // Windows Vista / Server 2008 or greater +#define USE_WINDOWS_CONDITION_VARIABLE +typedef CONDITION_VARIABLE pthread_cond_t; +#else +typedef struct { + HANDLE waiting_sem_; + HANDLE received_sem_; + HANDLE signal_event_; +} pthread_cond_t; +#endif // _WIN32_WINNT >= 0x600 + +#ifndef WINAPI_FAMILY_PARTITION +#define WINAPI_PARTITION_DESKTOP 1 +#define WINAPI_FAMILY_PARTITION(x) x +#endif + +#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#define USE_CREATE_THREAD +#endif + +#else // !_WIN32 + +#include + +#endif // _WIN32 + +struct WebPWorkerImpl { + pthread_mutex_t mutex_; + pthread_cond_t condition_; + pthread_t thread_; +}; + +#if defined(_WIN32) + //------------------------------------------------------------------------------ // simplistic pthread emulation layer @@ -32,15 +67,29 @@ extern "C" { #define THREADFN unsigned int __stdcall #define THREAD_RETURN(val) (unsigned int)((DWORD_PTR)val) +#if _WIN32_WINNT >= 0x0501 // Windows XP or greater +#define WaitForSingleObject(obj, timeout) \ + WaitForSingleObjectEx(obj, timeout, FALSE /*bAlertable*/) +#endif + static int pthread_create(pthread_t* const thread, const void* attr, unsigned int (__stdcall *start)(void*), void* arg) { (void)attr; +#ifdef USE_CREATE_THREAD + *thread = CreateThread(NULL, /* lpThreadAttributes */ + 0, /* dwStackSize */ + start, + arg, + 0, /* dwStackSize */ + NULL); /* lpThreadId */ +#else *thread = (pthread_t)_beginthreadex(NULL, /* void *security */ 0, /* unsigned stack_size */ start, arg, 0, /* unsigned initflag */ NULL); /* unsigned *thrdaddr */ +#endif if (*thread == NULL) return 1; SetThreadPriority(*thread, THREAD_PRIORITY_ABOVE_NORMAL); return 0; @@ -55,7 +104,11 @@ static int pthread_join(pthread_t thread, void** value_ptr) { // Mutex static int pthread_mutex_init(pthread_mutex_t* const mutex, void* mutexattr) { (void)mutexattr; +#if _WIN32_WINNT >= 0x0600 // Windows Vista / Server 2008 or greater + InitializeCriticalSectionEx(mutex, 0 /*dwSpinCount*/, 0 /*Flags*/); +#else InitializeCriticalSection(mutex); +#endif return 0; } @@ -77,14 +130,21 @@ static int pthread_mutex_destroy(pthread_mutex_t* const mutex) { // Condition static int pthread_cond_destroy(pthread_cond_t* const condition) { int ok = 1; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + (void)condition; +#else ok &= (CloseHandle(condition->waiting_sem_) != 0); ok &= (CloseHandle(condition->received_sem_) != 0); ok &= (CloseHandle(condition->signal_event_) != 0); +#endif return !ok; } static int pthread_cond_init(pthread_cond_t* const condition, void* cond_attr) { (void)cond_attr; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + InitializeConditionVariable(condition); +#else condition->waiting_sem_ = CreateSemaphore(NULL, 0, 1, NULL); condition->received_sem_ = CreateSemaphore(NULL, 0, 1, NULL); condition->signal_event_ = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -94,11 +154,15 @@ static int pthread_cond_init(pthread_cond_t* const condition, void* cond_attr) { pthread_cond_destroy(condition); return 1; } +#endif return 0; } static int pthread_cond_signal(pthread_cond_t* const condition) { int ok = 1; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + WakeConditionVariable(condition); +#else if (WaitForSingleObject(condition->waiting_sem_, 0) == WAIT_OBJECT_0) { // a thread is waiting in pthread_cond_wait: allow it to be notified ok = SetEvent(condition->signal_event_); @@ -107,139 +171,186 @@ static int pthread_cond_signal(pthread_cond_t* const condition) { ok &= (WaitForSingleObject(condition->received_sem_, INFINITE) != WAIT_OBJECT_0); } +#endif return !ok; } static int pthread_cond_wait(pthread_cond_t* const condition, pthread_mutex_t* const mutex) { int ok; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + ok = SleepConditionVariableCS(condition, mutex, INFINITE); +#else // note that there is a consumer available so the signal isn't dropped in // pthread_cond_signal - if (!ReleaseSemaphore(condition->waiting_sem_, 1, NULL)) - return 1; + if (!ReleaseSemaphore(condition->waiting_sem_, 1, NULL)) return 1; // now unlock the mutex so pthread_cond_signal may be issued pthread_mutex_unlock(mutex); ok = (WaitForSingleObject(condition->signal_event_, INFINITE) == WAIT_OBJECT_0); ok &= ReleaseSemaphore(condition->received_sem_, 1, NULL); pthread_mutex_lock(mutex); +#endif return !ok; } -#else // _WIN32 +#else // !_WIN32 # define THREADFN void* # define THREAD_RETURN(val) val -#endif +#endif // _WIN32 //------------------------------------------------------------------------------ -static THREADFN WebPWorkerThreadLoop(void *ptr) { // thread loop +static void Execute(WebPWorker* const worker); // Forward declaration. + +static THREADFN ThreadLoop(void* ptr) { WebPWorker* const worker = (WebPWorker*)ptr; int done = 0; while (!done) { - pthread_mutex_lock(&worker->mutex_); + pthread_mutex_lock(&worker->impl_->mutex_); while (worker->status_ == OK) { // wait in idling mode - pthread_cond_wait(&worker->condition_, &worker->mutex_); + pthread_cond_wait(&worker->impl_->condition_, &worker->impl_->mutex_); } if (worker->status_ == WORK) { - if (worker->hook) { - worker->had_error |= !worker->hook(worker->data1, worker->data2); - } + Execute(worker); worker->status_ = OK; } else if (worker->status_ == NOT_OK) { // finish the worker done = 1; } // signal to the main thread that we're done (for Sync()) - pthread_cond_signal(&worker->condition_); - pthread_mutex_unlock(&worker->mutex_); + pthread_cond_signal(&worker->impl_->condition_); + pthread_mutex_unlock(&worker->impl_->mutex_); } return THREAD_RETURN(NULL); // Thread is finished } // main thread state control -static void WebPWorkerChangeState(WebPWorker* const worker, - WebPWorkerStatus new_status) { - // no-op when attempting to change state on a thread that didn't come up - if (worker->status_ < OK) return; +static void ChangeState(WebPWorker* const worker, WebPWorkerStatus new_status) { + // No-op when attempting to change state on a thread that didn't come up. + // Checking status_ without acquiring the lock first would result in a data + // race. + if (worker->impl_ == NULL) return; - pthread_mutex_lock(&worker->mutex_); - // wait for the worker to finish - while (worker->status_ != OK) { - pthread_cond_wait(&worker->condition_, &worker->mutex_); + pthread_mutex_lock(&worker->impl_->mutex_); + if (worker->status_ >= OK) { + // wait for the worker to finish + while (worker->status_ != OK) { + pthread_cond_wait(&worker->impl_->condition_, &worker->impl_->mutex_); + } + // assign new status and release the working thread if needed + if (new_status != OK) { + worker->status_ = new_status; + pthread_cond_signal(&worker->impl_->condition_); + } } - // assign new status and release the working thread if needed - if (new_status != OK) { - worker->status_ = new_status; - pthread_cond_signal(&worker->condition_); - } - pthread_mutex_unlock(&worker->mutex_); + pthread_mutex_unlock(&worker->impl_->mutex_); } -#endif +#endif // WEBP_USE_THREAD //------------------------------------------------------------------------------ -void WebPWorkerInit(WebPWorker* const worker) { +static void Init(WebPWorker* const worker) { memset(worker, 0, sizeof(*worker)); worker->status_ = NOT_OK; } -int WebPWorkerSync(WebPWorker* const worker) { +static int Sync(WebPWorker* const worker) { #ifdef WEBP_USE_THREAD - WebPWorkerChangeState(worker, OK); + ChangeState(worker, OK); #endif assert(worker->status_ <= OK); return !worker->had_error; } -int WebPWorkerReset(WebPWorker* const worker) { +static int Reset(WebPWorker* const worker) { int ok = 1; worker->had_error = 0; if (worker->status_ < OK) { #ifdef WEBP_USE_THREAD - if (pthread_mutex_init(&worker->mutex_, NULL) || - pthread_cond_init(&worker->condition_, NULL)) { + worker->impl_ = (WebPWorkerImpl*)WebPSafeCalloc(1, sizeof(*worker->impl_)); + if (worker->impl_ == NULL) { return 0; } - pthread_mutex_lock(&worker->mutex_); - ok = !pthread_create(&worker->thread_, NULL, WebPWorkerThreadLoop, worker); + if (pthread_mutex_init(&worker->impl_->mutex_, NULL)) { + goto Error; + } + if (pthread_cond_init(&worker->impl_->condition_, NULL)) { + pthread_mutex_destroy(&worker->impl_->mutex_); + goto Error; + } + pthread_mutex_lock(&worker->impl_->mutex_); + ok = !pthread_create(&worker->impl_->thread_, NULL, ThreadLoop, worker); if (ok) worker->status_ = OK; - pthread_mutex_unlock(&worker->mutex_); + pthread_mutex_unlock(&worker->impl_->mutex_); + if (!ok) { + pthread_mutex_destroy(&worker->impl_->mutex_); + pthread_cond_destroy(&worker->impl_->condition_); + Error: + WebPSafeFree(worker->impl_); + worker->impl_ = NULL; + return 0; + } #else worker->status_ = OK; #endif } else if (worker->status_ > OK) { - ok = WebPWorkerSync(worker); + ok = Sync(worker); } assert(!ok || (worker->status_ == OK)); return ok; } -void WebPWorkerLaunch(WebPWorker* const worker) { -#ifdef WEBP_USE_THREAD - WebPWorkerChangeState(worker, WORK); -#else - if (worker->hook) +static void Execute(WebPWorker* const worker) { + if (worker->hook != NULL) { worker->had_error |= !worker->hook(worker->data1, worker->data2); + } +} + +static void Launch(WebPWorker* const worker) { +#ifdef WEBP_USE_THREAD + ChangeState(worker, WORK); +#else + Execute(worker); #endif } -void WebPWorkerEnd(WebPWorker* const worker) { - if (worker->status_ >= OK) { +static void End(WebPWorker* const worker) { #ifdef WEBP_USE_THREAD - WebPWorkerChangeState(worker, NOT_OK); - pthread_join(worker->thread_, NULL); - pthread_mutex_destroy(&worker->mutex_); - pthread_cond_destroy(&worker->condition_); -#else - worker->status_ = NOT_OK; -#endif + if (worker->impl_ != NULL) { + ChangeState(worker, NOT_OK); + pthread_join(worker->impl_->thread_, NULL); + pthread_mutex_destroy(&worker->impl_->mutex_); + pthread_cond_destroy(&worker->impl_->condition_); + WebPSafeFree(worker->impl_); + worker->impl_ = NULL; } +#else + worker->status_ = NOT_OK; + assert(worker->impl_ == NULL); +#endif assert(worker->status_ == NOT_OK); } //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" -#endif +static WebPWorkerInterface g_worker_interface = { + Init, Reset, Sync, Launch, Execute, End +}; + +int WebPSetWorkerInterface(const WebPWorkerInterface* const winterface) { + if (winterface == NULL || + winterface->Init == NULL || winterface->Reset == NULL || + winterface->Sync == NULL || winterface->Launch == NULL || + winterface->Execute == NULL || winterface->End == NULL) { + return 0; + } + g_worker_interface = *winterface; + return 1; +} + +const WebPWorkerInterface* WebPGetWorkerInterface(void) { + return &g_worker_interface; +} + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/utils/thread_utils.h b/3rdparty/libwebp/utils/thread_utils.h new file mode 100644 index 0000000000..8408311855 --- /dev/null +++ b/3rdparty/libwebp/utils/thread_utils.h @@ -0,0 +1,93 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Multi-threaded worker +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_THREAD_H_ +#define WEBP_UTILS_THREAD_H_ + +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" +#endif + +#include "../webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// State of the worker thread object +typedef enum { + NOT_OK = 0, // object is unusable + OK, // ready to work + WORK // busy finishing the current task +} WebPWorkerStatus; + +// Function to be called by the worker thread. Takes two opaque pointers as +// arguments (data1 and data2), and should return false in case of error. +typedef int (*WebPWorkerHook)(void*, void*); + +// Platform-dependent implementation details for the worker. +typedef struct WebPWorkerImpl WebPWorkerImpl; + +// Synchronization object used to launch job in the worker thread +typedef struct { + WebPWorkerImpl* impl_; + WebPWorkerStatus status_; + WebPWorkerHook hook; // hook to call + void* data1; // first argument passed to 'hook' + void* data2; // second argument passed to 'hook' + int had_error; // return value of the last call to 'hook' +} WebPWorker; + +// The interface for all thread-worker related functions. All these functions +// must be implemented. +typedef struct { + // Must be called first, before any other method. + void (*Init)(WebPWorker* const worker); + // Must be called to initialize the object and spawn the thread. Re-entrant. + // Will potentially launch the thread. Returns false in case of error. + int (*Reset)(WebPWorker* const worker); + // Makes sure the previous work is finished. Returns true if worker->had_error + // was not set and no error condition was triggered by the working thread. + int (*Sync)(WebPWorker* const worker); + // Triggers the thread to call hook() with data1 and data2 arguments. These + // hook/data1/data2 values can be changed at any time before calling this + // function, but not be changed afterward until the next call to Sync(). + void (*Launch)(WebPWorker* const worker); + // This function is similar to Launch() except that it calls the + // hook directly instead of using a thread. Convenient to bypass the thread + // mechanism while still using the WebPWorker structs. Sync() must + // still be called afterward (for error reporting). + void (*Execute)(WebPWorker* const worker); + // Kill the thread and terminate the object. To use the object again, one + // must call Reset() again. + void (*End)(WebPWorker* const worker); +} WebPWorkerInterface; + +// Install a new set of threading functions, overriding the defaults. This +// should be done before any workers are started, i.e., before any encoding or +// decoding takes place. The contents of the interface struct are copied, it +// is safe to free the corresponding memory after this call. This function is +// not thread-safe. Return false in case of invalid pointer or methods. +WEBP_EXTERN(int) WebPSetWorkerInterface( + const WebPWorkerInterface* const winterface); + +// Retrieve the currently set thread worker interface. +WEBP_EXTERN(const WebPWorkerInterface*) WebPGetWorkerInterface(void); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_UTILS_THREAD_H_ */ diff --git a/3rdparty/libwebp/utils/utils.c b/3rdparty/libwebp/utils/utils.c index 7eb06105b8..504d924b60 100644 --- a/3rdparty/libwebp/utils/utils.c +++ b/3rdparty/libwebp/utils/utils.c @@ -12,38 +12,319 @@ // Author: Skal (pascal.massimino@gmail.com) #include +#include // for memcpy() +#include "../webp/decode.h" +#include "../webp/encode.h" +#include "../webp/format_constants.h" // for MAX_PALETTE_SIZE #include "./utils.h" -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +// If PRINT_MEM_INFO is defined, extra info (like total memory used, number of +// alloc/free etc) is printed. For debugging/tuning purpose only (it's slow, +// and not multi-thread safe!). +// An interesting alternative is valgrind's 'massif' tool: +// http://valgrind.org/docs/manual/ms-manual.html +// Here is an example command line: +/* valgrind --tool=massif --massif-out-file=massif.out \ + --stacks=yes --alloc-fn=WebPSafeMalloc --alloc-fn=WebPSafeCalloc + ms_print massif.out +*/ +// In addition: +// * if PRINT_MEM_TRAFFIC is defined, all the details of the malloc/free cycles +// are printed. +// * if MALLOC_FAIL_AT is defined, the global environment variable +// $MALLOC_FAIL_AT is used to simulate a memory error when calloc or malloc +// is called for the nth time. Example usage: +// export MALLOC_FAIL_AT=50 && ./examples/cwebp input.png +// * if MALLOC_LIMIT is defined, the global environment variable $MALLOC_LIMIT +// sets the maximum amount of memory (in bytes) made available to libwebp. +// This can be used to emulate environment with very limited memory. +// Example: export MALLOC_LIMIT=64000000 && ./examples/dwebp picture.webp + +// #define PRINT_MEM_INFO +// #define PRINT_MEM_TRAFFIC +// #define MALLOC_FAIL_AT +// #define MALLOC_LIMIT //------------------------------------------------------------------------------ // Checked memory allocation +#if defined(PRINT_MEM_INFO) + +#include + +static int num_malloc_calls = 0; +static int num_calloc_calls = 0; +static int num_free_calls = 0; +static int countdown_to_fail = 0; // 0 = off + +typedef struct MemBlock MemBlock; +struct MemBlock { + void* ptr_; + size_t size_; + MemBlock* next_; +}; + +static MemBlock* all_blocks = NULL; +static size_t total_mem = 0; +static size_t total_mem_allocated = 0; +static size_t high_water_mark = 0; +static size_t mem_limit = 0; + +static int exit_registered = 0; + +static void PrintMemInfo(void) { + fprintf(stderr, "\nMEMORY INFO:\n"); + fprintf(stderr, "num calls to: malloc = %4d\n", num_malloc_calls); + fprintf(stderr, " calloc = %4d\n", num_calloc_calls); + fprintf(stderr, " free = %4d\n", num_free_calls); + fprintf(stderr, "total_mem: %u\n", (uint32_t)total_mem); + fprintf(stderr, "total_mem allocated: %u\n", (uint32_t)total_mem_allocated); + fprintf(stderr, "high-water mark: %u\n", (uint32_t)high_water_mark); + while (all_blocks != NULL) { + MemBlock* b = all_blocks; + all_blocks = b->next_; + free(b); + } +} + +static void Increment(int* const v) { + if (!exit_registered) { +#if defined(MALLOC_FAIL_AT) + { + const char* const malloc_fail_at_str = getenv("MALLOC_FAIL_AT"); + if (malloc_fail_at_str != NULL) { + countdown_to_fail = atoi(malloc_fail_at_str); + } + } +#endif +#if defined(MALLOC_LIMIT) + { + const char* const malloc_limit_str = getenv("MALLOC_LIMIT"); + if (malloc_limit_str != NULL) { + mem_limit = atoi(malloc_limit_str); + } + } +#endif + (void)countdown_to_fail; + (void)mem_limit; + atexit(PrintMemInfo); + exit_registered = 1; + } + ++*v; +} + +static void AddMem(void* ptr, size_t size) { + if (ptr != NULL) { + MemBlock* const b = (MemBlock*)malloc(sizeof(*b)); + if (b == NULL) abort(); + b->next_ = all_blocks; + all_blocks = b; + b->ptr_ = ptr; + b->size_ = size; + total_mem += size; + total_mem_allocated += size; +#if defined(PRINT_MEM_TRAFFIC) +#if defined(MALLOC_FAIL_AT) + fprintf(stderr, "fail-count: %5d [mem=%u]\n", + num_malloc_calls + num_calloc_calls, (uint32_t)total_mem); +#else + fprintf(stderr, "Mem: %u (+%u)\n", (uint32_t)total_mem, (uint32_t)size); +#endif +#endif + if (total_mem > high_water_mark) high_water_mark = total_mem; + } +} + +static void SubMem(void* ptr) { + if (ptr != NULL) { + MemBlock** b = &all_blocks; + // Inefficient search, but that's just for debugging. + while (*b != NULL && (*b)->ptr_ != ptr) b = &(*b)->next_; + if (*b == NULL) { + fprintf(stderr, "Invalid pointer free! (%p)\n", ptr); + abort(); + } + { + MemBlock* const block = *b; + *b = block->next_; + total_mem -= block->size_; +#if defined(PRINT_MEM_TRAFFIC) + fprintf(stderr, "Mem: %u (-%u)\n", + (uint32_t)total_mem, (uint32_t)block->size_); +#endif + free(block); + } + } +} + +#else +#define Increment(v) do {} while (0) +#define AddMem(p, s) do {} while (0) +#define SubMem(p) do {} while (0) +#endif + // Returns 0 in case of overflow of nmemb * size. static int CheckSizeArgumentsOverflow(uint64_t nmemb, size_t size) { const uint64_t total_size = nmemb * size; if (nmemb == 0) return 1; if ((uint64_t)size > WEBP_MAX_ALLOCABLE_MEMORY / nmemb) return 0; if (total_size != (size_t)total_size) return 0; +#if defined(PRINT_MEM_INFO) && defined(MALLOC_FAIL_AT) + if (countdown_to_fail > 0 && --countdown_to_fail == 0) { + return 0; // fake fail! + } +#endif +#if defined(MALLOC_LIMIT) + if (mem_limit > 0) { + const uint64_t new_total_mem = (uint64_t)total_mem + total_size; + if (new_total_mem != (size_t)new_total_mem || + new_total_mem > mem_limit) { + return 0; // fake fail! + } + } +#endif + return 1; } void* WebPSafeMalloc(uint64_t nmemb, size_t size) { + void* ptr; + Increment(&num_malloc_calls); if (!CheckSizeArgumentsOverflow(nmemb, size)) return NULL; assert(nmemb * size > 0); - return malloc((size_t)(nmemb * size)); + ptr = malloc((size_t)(nmemb * size)); + AddMem(ptr, (size_t)(nmemb * size)); + return ptr; } void* WebPSafeCalloc(uint64_t nmemb, size_t size) { + void* ptr; + Increment(&num_calloc_calls); if (!CheckSizeArgumentsOverflow(nmemb, size)) return NULL; assert(nmemb * size > 0); - return calloc((size_t)nmemb, size); + ptr = calloc((size_t)nmemb, size); + AddMem(ptr, (size_t)(nmemb * size)); + return ptr; +} + +void WebPSafeFree(void* const ptr) { + if (ptr != NULL) { + Increment(&num_free_calls); + SubMem(ptr); + } + free(ptr); +} + +// Public API function. +void WebPFree(void* ptr) { + free(ptr); } //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) -} // extern "C" +void WebPCopyPlane(const uint8_t* src, int src_stride, + uint8_t* dst, int dst_stride, int width, int height) { + assert(src != NULL && dst != NULL); + assert(src_stride >= width && dst_stride >= width); + while (height-- > 0) { + memcpy(dst, src, width); + src += src_stride; + dst += dst_stride; + } +} + +void WebPCopyPixels(const WebPPicture* const src, WebPPicture* const dst) { + assert(src != NULL && dst != NULL); + assert(src->width == dst->width && src->height == dst->height); + assert(src->use_argb && dst->use_argb); + WebPCopyPlane((uint8_t*)src->argb, 4 * src->argb_stride, (uint8_t*)dst->argb, + 4 * dst->argb_stride, 4 * src->width, src->height); +} + +//------------------------------------------------------------------------------ + +#define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4) +#define COLOR_HASH_RIGHT_SHIFT 22 // 32 - log2(COLOR_HASH_SIZE). + +int WebPGetColorPalette(const WebPPicture* const pic, uint32_t* const palette) { + int i; + int x, y; + int num_colors = 0; + uint8_t in_use[COLOR_HASH_SIZE] = { 0 }; + uint32_t colors[COLOR_HASH_SIZE]; + static const uint64_t kHashMul = 0x1e35a7bdull; + const uint32_t* argb = pic->argb; + const int width = pic->width; + const int height = pic->height; + uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0] + assert(pic != NULL); + assert(pic->use_argb); + + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { + int key; + if (argb[x] == last_pix) { + continue; + } + last_pix = argb[x]; + key = ((last_pix * kHashMul) & 0xffffffffu) >> COLOR_HASH_RIGHT_SHIFT; + while (1) { + if (!in_use[key]) { + colors[key] = last_pix; + in_use[key] = 1; + ++num_colors; + if (num_colors > MAX_PALETTE_SIZE) { + return MAX_PALETTE_SIZE + 1; // Exact count not needed. + } + break; + } else if (colors[key] == last_pix) { + break; // The color is already there. + } else { + // Some other color sits here, so do linear conflict resolution. + ++key; + key &= (COLOR_HASH_SIZE - 1); // Key mask. + } + } + } + argb += pic->argb_stride; + } + + if (palette != NULL) { // Fill the colors into palette. + num_colors = 0; + for (i = 0; i < COLOR_HASH_SIZE; ++i) { + if (in_use[i]) { + palette[num_colors] = colors[i]; + ++num_colors; + } + } + } + return num_colors; +} + +#undef COLOR_HASH_SIZE +#undef COLOR_HASH_RIGHT_SHIFT + +//------------------------------------------------------------------------------ + +#if defined(WEBP_NEED_LOG_TABLE_8BIT) +const uint8_t WebPLogTable8bit[256] = { // 31 ^ clz(i) + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +}; #endif + +//------------------------------------------------------------------------------ diff --git a/3rdparty/libwebp/utils/utils.h b/3rdparty/libwebp/utils/utils.h index e10aeeb9d7..3ab459050a 100644 --- a/3rdparty/libwebp/utils/utils.h +++ b/3rdparty/libwebp/utils/utils.h @@ -15,11 +15,17 @@ #ifndef WEBP_UTILS_UTILS_H_ #define WEBP_UTILS_UTILS_H_ -#include +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" +#endif +#include +#include + +#include "../dsp/dsp.h" #include "../webp/types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -27,7 +33,14 @@ extern "C" { // Memory allocation // This is the maximum memory amount that libwebp will ever try to allocate. -#define WEBP_MAX_ALLOCABLE_MEMORY (1ULL << 40) +#ifndef WEBP_MAX_ALLOCABLE_MEMORY +#if SIZE_MAX > (1ULL << 34) +#define WEBP_MAX_ALLOCABLE_MEMORY (1ULL << 34) +#else +// For 32-bit targets keep this below INT_MAX to avoid valgrind warnings. +#define WEBP_MAX_ALLOCABLE_MEMORY ((1ULL << 31) - (1 << 16)) +#endif +#endif // WEBP_MAX_ALLOCABLE_MEMORY // size-checking safe malloc/calloc: verify that the requested size is not too // large, or return NULL. You don't need to call these for constructs like @@ -35,10 +48,30 @@ extern "C" { // somewhere (like: malloc(num_pixels * sizeof(*something))). That's why this // safe malloc() borrows the signature from calloc(), pointing at the dangerous // underlying multiply involved. -void* WebPSafeMalloc(uint64_t nmemb, size_t size); +WEBP_EXTERN(void*) WebPSafeMalloc(uint64_t nmemb, size_t size); // Note that WebPSafeCalloc() expects the second argument type to be 'size_t' // in order to favor the "calloc(num_foo, sizeof(foo))" pattern. -void* WebPSafeCalloc(uint64_t nmemb, size_t size); +WEBP_EXTERN(void*) WebPSafeCalloc(uint64_t nmemb, size_t size); + +// Companion deallocation function to the above allocations. +WEBP_EXTERN(void) WebPSafeFree(void* const ptr); + +//------------------------------------------------------------------------------ +// Alignment + +#define WEBP_ALIGN_CST 31 +#define WEBP_ALIGN(PTR) (((uintptr_t)(PTR) + WEBP_ALIGN_CST) & ~WEBP_ALIGN_CST) + +#include +// memcpy() is the safe way of moving potentially unaligned 32b memory. +static WEBP_INLINE uint32_t WebPMemToUint32(const uint8_t* const ptr) { + uint32_t A; + memcpy(&A, (const int*)ptr, sizeof(A)); + return A; +} +static WEBP_INLINE void WebPUint32ToMem(uint8_t* const ptr, uint32_t val) { + memcpy(ptr, &val, sizeof(val)); +} //------------------------------------------------------------------------------ // Reading/writing data. @@ -53,7 +86,7 @@ static WEBP_INLINE int GetLE24(const uint8_t* const data) { } static WEBP_INLINE uint32_t GetLE32(const uint8_t* const data) { - return (uint32_t)GetLE16(data) | (GetLE16(data + 2) << 16); + return GetLE16(data) | ((uint32_t)GetLE16(data + 2) << 16); } // Store 16, 24 or 32 bits in little-endian order. @@ -74,9 +107,71 @@ static WEBP_INLINE void PutLE32(uint8_t* const data, uint32_t val) { PutLE16(data + 2, (int)(val >> 16)); } +// Returns 31 ^ clz(n) = log2(n). This is the default C-implementation, either +// based on table or not. Can be used as fallback if clz() is not available. +#define WEBP_NEED_LOG_TABLE_8BIT +extern const uint8_t WebPLogTable8bit[256]; +static WEBP_INLINE int WebPLog2FloorC(uint32_t n) { + int log = 0; + while (n >= 256) { + log += 8; + n >>= 8; + } + return log + WebPLogTable8bit[n]; +} + +// Returns (int)floor(log2(n)). n must be > 0. +// use GNU builtins where available. +#if defined(__GNUC__) && \ + ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ >= 4) +static WEBP_INLINE int BitsLog2Floor(uint32_t n) { + return 31 ^ __builtin_clz(n); +} +#elif defined(_MSC_VER) && _MSC_VER > 1310 && \ + (defined(_M_X64) || defined(_M_IX86)) +#include +#pragma intrinsic(_BitScanReverse) + +static WEBP_INLINE int BitsLog2Floor(uint32_t n) { + unsigned long first_set_bit; + _BitScanReverse(&first_set_bit, n); + return first_set_bit; +} +#else // default: use the C-version. +static WEBP_INLINE int BitsLog2Floor(uint32_t n) { return WebPLog2FloorC(n); } +#endif + +//------------------------------------------------------------------------------ +// Pixel copying. + +struct WebPPicture; + +// Copy width x height pixels from 'src' to 'dst' honoring the strides. +WEBP_EXTERN(void) WebPCopyPlane(const uint8_t* src, int src_stride, + uint8_t* dst, int dst_stride, + int width, int height); + +// Copy ARGB pixels from 'src' to 'dst' honoring strides. 'src' and 'dst' are +// assumed to be already allocated and using ARGB data. +WEBP_EXTERN(void) WebPCopyPixels(const struct WebPPicture* const src, + struct WebPPicture* const dst); + +//------------------------------------------------------------------------------ +// Unique colors. + +// Returns count of unique colors in 'pic', assuming pic->use_argb is true. +// If the unique color count is more than MAX_PALETTE_SIZE, returns +// MAX_PALETTE_SIZE+1. +// If 'palette' is not NULL and number of unique colors is less than or equal to +// MAX_PALETTE_SIZE, also outputs the actual unique colors into 'palette'. +// Note: 'palette' is assumed to be an array already allocated with at least +// MAX_PALETTE_SIZE elements. +WEBP_EXTERN(int) WebPGetColorPalette(const struct WebPPicture* const pic, + uint32_t* const palette); + //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/webp/decode.h b/3rdparty/libwebp/webp/decode.h index 141f8618f5..4c5e74ac36 100644 --- a/3rdparty/libwebp/webp/decode.h +++ b/3rdparty/libwebp/webp/decode.h @@ -16,11 +16,11 @@ #include "./types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -#define WEBP_DECODER_ABI_VERSION 0x0201 // MAJOR(8b) + MINOR(8b) +#define WEBP_DECODER_ABI_VERSION 0x0208 // MAJOR(8b) + MINOR(8b) // Note: forward declaring enumerations is not allowed in (strict) C and C++, // the types are left here for reference. @@ -39,8 +39,8 @@ typedef struct WebPDecoderConfig WebPDecoderConfig; WEBP_EXTERN(int) WebPGetDecoderVersion(void); // Retrieve basic header information: width, height. -// This function will also validate the header and return 0 in -// case of formatting error. +// This function will also validate the header, returning true on success, +// false otherwise. '*width' and '*height' are only valid on successful return. // Pointers 'width' and 'height' can be passed NULL if deemed irrelevant. WEBP_EXTERN(int) WebPGetInfo(const uint8_t* data, size_t data_size, int* width, int* height); @@ -48,7 +48,7 @@ WEBP_EXTERN(int) WebPGetInfo(const uint8_t* data, size_t data_size, // Decodes WebP images pointed to by 'data' and returns RGBA samples, along // with the dimensions in *width and *height. The ordering of samples in // memory is R, G, B, A, R, G, B, A... in scan order (endian-independent). -// The returned pointer should be deleted calling free(). +// The returned pointer should be deleted calling WebPFree(). // Returns NULL in case of error. WEBP_EXTERN(uint8_t*) WebPDecodeRGBA(const uint8_t* data, size_t data_size, int* width, int* height); @@ -73,9 +73,9 @@ WEBP_EXTERN(uint8_t*) WebPDecodeBGR(const uint8_t* data, size_t data_size, // Decode WebP images pointed to by 'data' to Y'UV format(*). The pointer // returned is the Y samples buffer. Upon return, *u and *v will point to -// the U and V chroma data. These U and V buffers need NOT be free()'d, -// unlike the returned Y luma one. The dimension of the U and V planes -// are both (*width + 1) / 2 and (*height + 1)/ 2. +// the U and V chroma data. These U and V buffers need NOT be passed to +// WebPFree(), unlike the returned Y luma one. The dimension of the U and V +// planes are both (*width + 1) / 2 and (*height + 1)/ 2. // Upon return, the Y buffer has a stride returned as '*stride', while U and V // have a common stride returned as '*uv_stride'. // Return NULL in case of error. @@ -85,6 +85,9 @@ WEBP_EXTERN(uint8_t*) WebPDecodeYUV(const uint8_t* data, size_t data_size, uint8_t** u, uint8_t** v, int* stride, int* uv_stride); +// Releases memory returned by the WebPDecode*() functions above. +WEBP_EXTERN(void) WebPFree(void* ptr); + // These five functions are variants of the above ones, that decode the image // directly into a pre-allocated buffer 'output_buffer'. The maximum storage // available in this buffer is indicated by 'output_buffer_size'. If this @@ -194,7 +197,10 @@ struct WebPYUVABuffer { // view as YUVA struct WebPDecBuffer { WEBP_CSP_MODE colorspace; // Colorspace. int width, height; // Dimensions. - int is_external_memory; // If true, 'internal_memory' pointer is not used. + int is_external_memory; // If non-zero, 'internal_memory' pointer is not + // used. If value is '2' or more, the external + // memory is considered 'slow' and multiple + // read/write will be avoided. union { WebPRGBABuffer RGBA; WebPYUVABuffer YUVA; @@ -202,7 +208,7 @@ struct WebPDecBuffer { uint32_t pad[4]; // padding for later use uint8_t* private_memory; // Internally allocated memory (only when - // is_external_memory is false). Should not be used + // is_external_memory is 0). Should not be used // externally, but accessed via the buffer union. }; @@ -242,19 +248,19 @@ typedef enum VP8StatusCode { // picture is only partially decoded, pending additional input. // Code example: // -// WebPInitDecBuffer(&buffer); -// buffer.colorspace = mode; +// WebPInitDecBuffer(&output_buffer); +// output_buffer.colorspace = mode; // ... -// WebPIDecoder* idec = WebPINewDecoder(&buffer); -// while (has_more_data) { -// // ... (get additional data) +// WebPIDecoder* idec = WebPINewDecoder(&output_buffer); +// while (additional_data_is_available) { +// // ... (get additional data in some new_data[] buffer) // status = WebPIAppend(idec, new_data, new_data_size); -// if (status != VP8_STATUS_SUSPENDED || -// break; +// if (status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED) { +// break; // an error occurred. // } // // // The above call decodes the current available buffer. -// // Part of the image can now be refreshed by calling to +// // Part of the image can now be refreshed by calling // // WebPIDecGetRGB()/WebPIDecGetYUVA() etc. // } // WebPIDelete(idec); @@ -266,7 +272,7 @@ typedef enum VP8StatusCode { // that of the returned WebPIDecoder object. // The supplied 'output_buffer' content MUST NOT be changed between calls to // WebPIAppend() or WebPIUpdate() unless 'output_buffer.is_external_memory' is -// set to 1. In such a case, it is allowed to modify the pointers, size and +// not set to 0. In such a case, it is allowed to modify the pointers, size and // stride of output_buffer.u.RGBA or output_buffer.u.YUVA, provided they remain // within valid bounds. // All other fields of WebPDecBuffer MUST remain constant between calls. @@ -404,14 +410,9 @@ struct WebPBitstreamFeatures { int height; // Height in pixels, as read from the bitstream. int has_alpha; // True if the bitstream contains an alpha channel. int has_animation; // True if the bitstream is an animation. + int format; // 0 = undefined (/mixed), 1 = lossy, 2 = lossless - // Unused for now: - int bitstream_version; // should be 0 for now. TODO(later) - int no_incremental_decoding; // if true, using incremental decoding is not - // recommended. - int rotate; // TODO(later) - int uv_sampling; // should be 0 for now. TODO(later) - uint32_t pad[2]; // padding for later use + uint32_t pad[5]; // padding for later use }; // Internal, version-checked, entry point @@ -441,11 +442,11 @@ struct WebPDecoderOptions { int use_scaling; // if true, scaling is applied _afterward_ int scaled_width, scaled_height; // final resolution int use_threads; // if true, use multi-threaded decoding + int dithering_strength; // dithering strength (0=Off, 100=full) + int flip; // flip output vertically + int alpha_dithering_strength; // alpha dithering strength in [0..100] - // Unused for now: - int force_rotation; // forced rotation (to be applied _last_) - int no_enhancement; // if true, discard enhancement layer - uint32_t pad[6]; // padding for later use + uint32_t pad[5]; // padding for later use }; // Main object storing the configuration for advanced decoding. @@ -470,20 +471,22 @@ static WEBP_INLINE int WebPInitDecoderConfig(WebPDecoderConfig* config) { // parameter, in which case the features will be parsed and stored into // config->input. Otherwise, 'data' can be NULL and no parsing will occur. // Note that 'config' can be NULL too, in which case a default configuration -// is used. +// is used. If 'config' is not NULL, it must outlive the WebPIDecoder object +// as some references to its fields will be used. No internal copy of 'config' +// is made. // The return WebPIDecoder object must always be deleted calling WebPIDelete(). // Returns NULL in case of error (and config->status will then reflect -// the error condition). +// the error condition, if available). WEBP_EXTERN(WebPIDecoder*) WebPIDecode(const uint8_t* data, size_t data_size, WebPDecoderConfig* config); // Non-incremental version. This version decodes the full data at once, taking // 'config' into account. Returns decoding status (which should be VP8_STATUS_OK -// if the decoding was successful). +// if the decoding was successful). Note that 'config' cannot be NULL. WEBP_EXTERN(VP8StatusCode) WebPDecode(const uint8_t* data, size_t data_size, WebPDecoderConfig* config); -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/webp/demux.h b/3rdparty/libwebp/webp/demux.h index c7cd5d66db..454f6914b2 100644 --- a/3rdparty/libwebp/webp/demux.h +++ b/3rdparty/libwebp/webp/demux.h @@ -12,48 +12,50 @@ // Code Example: Demuxing WebP data to extract all the frames, ICC profile // and EXIF/XMP metadata. -// -// WebPDemuxer* demux = WebPDemux(&webp_data); -// -// uint32_t width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); -// uint32_t height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); -// // ... (Get information about the features present in the WebP file). -// uint32_t flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS); -// -// // ... (Iterate over all frames). -// WebPIterator iter; -// if (WebPDemuxGetFrame(demux, 1, &iter)) { -// do { -// // ... (Consume 'iter'; e.g. Decode 'iter.fragment' with WebPDecode(), -// // ... and get other frame properties like width, height, offsets etc. -// // ... see 'struct WebPIterator' below for more info). -// } while (WebPDemuxNextFrame(&iter)); -// WebPDemuxReleaseIterator(&iter); -// } -// -// // ... (Extract metadata). -// WebPChunkIterator chunk_iter; -// if (flags & ICCP_FLAG) WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter); -// // ... (Consume the ICC profile in 'chunk_iter.chunk'). -// WebPDemuxReleaseChunkIterator(&chunk_iter); -// if (flags & EXIF_FLAG) WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter); -// // ... (Consume the EXIF metadata in 'chunk_iter.chunk'). -// WebPDemuxReleaseChunkIterator(&chunk_iter); -// if (flags & XMP_FLAG) WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter); -// // ... (Consume the XMP metadata in 'chunk_iter.chunk'). -// WebPDemuxReleaseChunkIterator(&chunk_iter); -// WebPDemuxDelete(demux); +/* + WebPDemuxer* demux = WebPDemux(&webp_data); + + uint32_t width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); + uint32_t height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); + // ... (Get information about the features present in the WebP file). + uint32_t flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS); + + // ... (Iterate over all frames). + WebPIterator iter; + if (WebPDemuxGetFrame(demux, 1, &iter)) { + do { + // ... (Consume 'iter'; e.g. Decode 'iter.fragment' with WebPDecode(), + // ... and get other frame properties like width, height, offsets etc. + // ... see 'struct WebPIterator' below for more info). + } while (WebPDemuxNextFrame(&iter)); + WebPDemuxReleaseIterator(&iter); + } + + // ... (Extract metadata). + WebPChunkIterator chunk_iter; + if (flags & ICCP_FLAG) WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter); + // ... (Consume the ICC profile in 'chunk_iter.chunk'). + WebPDemuxReleaseChunkIterator(&chunk_iter); + if (flags & EXIF_FLAG) WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter); + // ... (Consume the EXIF metadata in 'chunk_iter.chunk'). + WebPDemuxReleaseChunkIterator(&chunk_iter); + if (flags & XMP_FLAG) WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter); + // ... (Consume the XMP metadata in 'chunk_iter.chunk'). + WebPDemuxReleaseChunkIterator(&chunk_iter); + WebPDemuxDelete(demux); +*/ #ifndef WEBP_WEBP_DEMUX_H_ #define WEBP_WEBP_DEMUX_H_ +#include "./decode.h" // for WEBP_CSP_MODE #include "./mux_types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -#define WEBP_DEMUX_ABI_VERSION 0x0100 // MAJOR(8b) + MINOR(8b) +#define WEBP_DEMUX_ABI_VERSION 0x0107 // MAJOR(8b) + MINOR(8b) // Note: forward declaring enumerations is not allowed in (strict) C and C++, // the types are left here for reference. @@ -62,6 +64,8 @@ extern "C" { typedef struct WebPDemuxer WebPDemuxer; typedef struct WebPIterator WebPIterator; typedef struct WebPChunkIterator WebPChunkIterator; +typedef struct WebPAnimInfo WebPAnimInfo; +typedef struct WebPAnimDecoderOptions WebPAnimDecoderOptions; //------------------------------------------------------------------------------ @@ -73,16 +77,19 @@ WEBP_EXTERN(int) WebPGetDemuxVersion(void); // Life of a Demux object typedef enum WebPDemuxState { - WEBP_DEMUX_PARSING_HEADER, // Not enough data to parse full header. - WEBP_DEMUX_PARSED_HEADER, // Header parsing complete, data may be available. - WEBP_DEMUX_DONE // Entire file has been parsed. + WEBP_DEMUX_PARSE_ERROR = -1, // An error occurred while parsing. + WEBP_DEMUX_PARSING_HEADER = 0, // Not enough data to parse full header. + WEBP_DEMUX_PARSED_HEADER = 1, // Header parsing complete, + // data may be available. + WEBP_DEMUX_DONE = 2 // Entire file has been parsed. } WebPDemuxState; // Internal, version-checked, entry point WEBP_EXTERN(WebPDemuxer*) WebPDemuxInternal( const WebPData*, int, WebPDemuxState*, int); -// Parses the full WebP file given by 'data'. +// Parses the full WebP file given by 'data'. For single images the WebP file +// header alone or the file header and the chunk header may be absent. // Returns a WebPDemuxer object on successful parse, NULL otherwise. static WEBP_INLINE WebPDemuxer* WebPDemux(const WebPData* data) { return WebPDemuxInternal(data, 0, NULL, WEBP_DEMUX_ABI_VERSION); @@ -90,7 +97,12 @@ static WEBP_INLINE WebPDemuxer* WebPDemux(const WebPData* data) { // Parses the possibly incomplete WebP file given by 'data'. // If 'state' is non-NULL it will be set to indicate the status of the demuxer. -// Returns a WebPDemuxer object on successful parse, NULL otherwise. +// Returns NULL in case of error or if there isn't enough data to start parsing; +// and a WebPDemuxer object on successful parse. +// Note that WebPDemuxer keeps internal pointers to 'data' memory segment. +// If this data is volatile, the demuxer object should be deleted (by calling +// WebPDemuxDelete()) and WebPDemuxPartial() called again on the new data. +// This is usually an inexpensive operation. static WEBP_INLINE WebPDemuxer* WebPDemuxPartial( const WebPData* data, WebPDemuxState* state) { return WebPDemuxInternal(data, 1, state, WEBP_DEMUX_ABI_VERSION); @@ -126,24 +138,23 @@ WEBP_EXTERN(uint32_t) WebPDemuxGetI( struct WebPIterator { int frame_num; int num_frames; // equivalent to WEBP_FF_FRAME_COUNT. - int fragment_num; - int num_fragments; int x_offset, y_offset; // offset relative to the canvas. - int width, height; // dimensions of this frame or fragment. + int width, height; // dimensions of this frame. int duration; // display duration in milliseconds. WebPMuxAnimDispose dispose_method; // dispose method for the frame. int complete; // true if 'fragment' contains a full frame. partial images // may still be decoded with the WebP incremental decoder. - WebPData fragment; // The frame or fragment given by 'frame_num' and - // 'fragment_num'. + WebPData fragment; // The frame given by 'frame_num'. Note for historical + // reasons this is called a fragment. + int has_alpha; // True if the frame contains transparency. + WebPMuxAnimBlend blend_method; // Blend operation for the frame. - uint32_t pad[4]; // padding for later use. + uint32_t pad[2]; // padding for later use. void* private_; // for internal use only. }; // Retrieves frame 'frame_number' from 'dmux'. -// 'iter->fragment' points to the first fragment on return from this function. -// Individual fragments may be extracted using WebPDemuxSetFragment(). +// 'iter->fragment' points to the frame on return from this function. // Setting 'frame_number' equal to 0 will return the last frame of the image. // Returns false if 'dmux' is NULL or frame 'frame_number' is not present. // Call WebPDemuxReleaseIterator() when use of the iterator is complete. @@ -157,10 +168,6 @@ WEBP_EXTERN(int) WebPDemuxGetFrame( WEBP_EXTERN(int) WebPDemuxNextFrame(WebPIterator* iter); WEBP_EXTERN(int) WebPDemuxPrevFrame(WebPIterator* iter); -// Sets 'iter->fragment' to reflect fragment number 'fragment_num'. -// Returns true if fragment 'fragment_num' is present, false otherwise. -WEBP_EXTERN(int) WebPDemuxSelectFragment(WebPIterator* iter, int fragment_num); - // Releases any memory associated with 'iter'. // Must be called before any subsequent calls to WebPDemuxGetChunk() on the same // iter. Also, must be called before destroying the associated WebPDemuxer with @@ -206,8 +213,145 @@ WEBP_EXTERN(int) WebPDemuxPrevChunk(WebPChunkIterator* iter); WEBP_EXTERN(void) WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter); //------------------------------------------------------------------------------ +// WebPAnimDecoder API +// +// This API allows decoding (possibly) animated WebP images. +// +// Code Example: +/* + WebPAnimDecoderOptions dec_options; + WebPAnimDecoderOptionsInit(&dec_options); + // Tune 'dec_options' as needed. + WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data, &dec_options); + WebPAnimInfo anim_info; + WebPAnimDecoderGetInfo(dec, &anim_info); + for (uint32_t i = 0; i < anim_info.loop_count; ++i) { + while (WebPAnimDecoderHasMoreFrames(dec)) { + uint8_t* buf; + int timestamp; + WebPAnimDecoderGetNext(dec, &buf, ×tamp); + // ... (Render 'buf' based on 'timestamp'). + // ... (Do NOT free 'buf', as it is owned by 'dec'). + } + WebPAnimDecoderReset(dec); + } + const WebPDemuxer* demuxer = WebPAnimDecoderGetDemuxer(dec); + // ... (Do something using 'demuxer'; e.g. get EXIF/XMP/ICC data). + WebPAnimDecoderDelete(dec); +*/ -#if defined(__cplusplus) || defined(c_plusplus) +typedef struct WebPAnimDecoder WebPAnimDecoder; // Main opaque object. + +// Global options. +struct WebPAnimDecoderOptions { + // Output colorspace. Only the following modes are supported: + // MODE_RGBA, MODE_BGRA, MODE_rgbA and MODE_bgrA. + WEBP_CSP_MODE color_mode; + int use_threads; // If true, use multi-threaded decoding. + uint32_t padding[7]; // Padding for later use. +}; + +// Internal, version-checked, entry point. +WEBP_EXTERN(int) WebPAnimDecoderOptionsInitInternal( + WebPAnimDecoderOptions*, int); + +// Should always be called, to initialize a fresh WebPAnimDecoderOptions +// structure before modification. Returns false in case of version mismatch. +// WebPAnimDecoderOptionsInit() must have succeeded before using the +// 'dec_options' object. +static WEBP_INLINE int WebPAnimDecoderOptionsInit( + WebPAnimDecoderOptions* dec_options) { + return WebPAnimDecoderOptionsInitInternal(dec_options, + WEBP_DEMUX_ABI_VERSION); +} + +// Internal, version-checked, entry point. +WEBP_EXTERN(WebPAnimDecoder*) WebPAnimDecoderNewInternal( + const WebPData*, const WebPAnimDecoderOptions*, int); + +// Creates and initializes a WebPAnimDecoder object. +// Parameters: +// webp_data - (in) WebP bitstream. This should remain unchanged during the +// lifetime of the output WebPAnimDecoder object. +// dec_options - (in) decoding options. Can be passed NULL to choose +// reasonable defaults (in particular, color mode MODE_RGBA +// will be picked). +// Returns: +// A pointer to the newly created WebPAnimDecoder object, or NULL in case of +// parsing error, invalid option or memory error. +static WEBP_INLINE WebPAnimDecoder* WebPAnimDecoderNew( + const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options) { + return WebPAnimDecoderNewInternal(webp_data, dec_options, + WEBP_DEMUX_ABI_VERSION); +} + +// Global information about the animation.. +struct WebPAnimInfo { + uint32_t canvas_width; + uint32_t canvas_height; + uint32_t loop_count; + uint32_t bgcolor; + uint32_t frame_count; + uint32_t pad[4]; // padding for later use +}; + +// Get global information about the animation. +// Parameters: +// dec - (in) decoder instance to get information from. +// info - (out) global information fetched from the animation. +// Returns: +// True on success. +WEBP_EXTERN(int) WebPAnimDecoderGetInfo(const WebPAnimDecoder* dec, + WebPAnimInfo* info); + +// Fetch the next frame from 'dec' based on options supplied to +// WebPAnimDecoderNew(). This will be a fully reconstructed canvas of size +// 'canvas_width * 4 * canvas_height', and not just the frame sub-rectangle. The +// returned buffer 'buf' is valid only until the next call to +// WebPAnimDecoderGetNext(), WebPAnimDecoderReset() or WebPAnimDecoderDelete(). +// Parameters: +// dec - (in/out) decoder instance from which the next frame is to be fetched. +// buf - (out) decoded frame. +// timestamp - (out) timestamp of the frame in milliseconds. +// Returns: +// False if any of the arguments are NULL, or if there is a parsing or +// decoding error, or if there are no more frames. Otherwise, returns true. +WEBP_EXTERN(int) WebPAnimDecoderGetNext(WebPAnimDecoder* dec, + uint8_t** buf, int* timestamp); + +// Check if there are more frames left to decode. +// Parameters: +// dec - (in) decoder instance to be checked. +// Returns: +// True if 'dec' is not NULL and some frames are yet to be decoded. +// Otherwise, returns false. +WEBP_EXTERN(int) WebPAnimDecoderHasMoreFrames(const WebPAnimDecoder* dec); + +// Resets the WebPAnimDecoder object, so that next call to +// WebPAnimDecoderGetNext() will restart decoding from 1st frame. This would be +// helpful when all frames need to be decoded multiple times (e.g. +// info.loop_count times) without destroying and recreating the 'dec' object. +// Parameters: +// dec - (in/out) decoder instance to be reset +WEBP_EXTERN(void) WebPAnimDecoderReset(WebPAnimDecoder* dec); + +// Grab the internal demuxer object. +// Getting the demuxer object can be useful if one wants to use operations only +// available through demuxer; e.g. to get XMP/EXIF/ICC metadata. The returned +// demuxer object is owned by 'dec' and is valid only until the next call to +// WebPAnimDecoderDelete(). +// +// Parameters: +// dec - (in) decoder instance from which the demuxer object is to be fetched. +WEBP_EXTERN(const WebPDemuxer*) WebPAnimDecoderGetDemuxer( + const WebPAnimDecoder* dec); + +// Deletes the WebPAnimDecoder object. +// Parameters: +// dec - (in/out) decoder instance to be deleted +WEBP_EXTERN(void) WebPAnimDecoderDelete(WebPAnimDecoder* dec); + +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/webp/encode.h b/3rdparty/libwebp/webp/encode.h index 726992f570..35fde1d052 100644 --- a/3rdparty/libwebp/webp/encode.h +++ b/3rdparty/libwebp/webp/encode.h @@ -16,11 +16,11 @@ #include "./types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -#define WEBP_ENCODER_ABI_VERSION 0x0201 // MAJOR(8b) + MINOR(8b) +#define WEBP_ENCODER_ABI_VERSION 0x020e // MAJOR(8b) + MINOR(8b) // Note: forward declaring enumerations is not allowed in (strict) C and C++, // the types are left here for reference. @@ -42,7 +42,7 @@ WEBP_EXTERN(int) WebPGetEncoderVersion(void); // Returns the size of the compressed data (pointed to by *output), or 0 if // an error occurred. The compressed data must be released by the caller -// using the call 'free(*output)'. +// using the call 'WebPFree(*output)'. // These functions compress using the lossy format, and the quality_factor // can go from 0 (smaller output, lower quality) to 100 (best quality, // larger output). @@ -75,6 +75,9 @@ WEBP_EXTERN(size_t) WebPEncodeLosslessBGRA(const uint8_t* bgra, int width, int height, int stride, uint8_t** output); +// Releases memory returned by the WebPEncode*() functions above. +WEBP_EXTERN(void) WebPFree(void* ptr); + //------------------------------------------------------------------------------ // Coding parameters @@ -117,7 +120,8 @@ struct WebPConfig { int show_compressed; // if true, export the compressed picture back. // In-loop filtering is not applied. - int preprocessing; // preprocessing filter (0=none, 1=segment-smooth) + int preprocessing; // preprocessing filter: + // 0=none, 1=segment-smooth, 2=pseudo-random dithering int partitions; // log2(number of token partitions) in [0..3]. Default // is set to 0 for easier progressive decoding. int partition_limit; // quality degradation allowed to fit the 512k limit @@ -130,7 +134,17 @@ struct WebPConfig { int thread_level; // If non-zero, try and use multi-threaded encoding. int low_memory; // If set, reduce memory usage (but increase CPU use). - uint32_t pad[5]; // padding for later use + int near_lossless; // Near lossless encoding [0 = max loss .. 100 = off + // (default)]. + int exact; // if non-zero, preserve the exact RGB values under + // transparent area. Otherwise, discard this invisible + // RGB information for better compression. The default + // value is 0. + + int use_delta_palette; // reserved for future lossless feature + int use_sharp_yuv; // if needed, use sharp (and slow) RGB->YUV conversion + + uint32_t pad[2]; // padding for later use }; // Enumerate some predefined settings for WebPConfig, depending on the type @@ -166,6 +180,14 @@ static WEBP_INLINE int WebPConfigPreset(WebPConfig* config, WEBP_ENCODER_ABI_VERSION); } +// Activate the lossless compression mode with the desired efficiency level +// between 0 (fastest, lowest compression) and 9 (slower, best compression). +// A good default level is '6', providing a fair tradeoff between compression +// speed and final compressed size. +// This function will overwrite several fields from config: 'method', 'quality' +// and 'lossless'. Returns false in case of parameter error. +WEBP_EXTERN(int) WebPConfigLosslessPreset(WebPConfig* config, int level); + // Returns true if 'config' is non-NULL and all configuration parameters are // within their valid ranges. WEBP_EXTERN(int) WebPValidateConfig(const WebPConfig* config); @@ -198,8 +220,10 @@ struct WebPAuxStats { int cache_bits; // number of bits for color cache lookup int palette_size; // number of color in palette, if used int lossless_size; // final lossless size + int lossless_hdr_size; // lossless header (transform, huffman etc) size + int lossless_data_size; // lossless image data size - uint32_t pad[4]; // padding for later use + uint32_t pad[2]; // padding for later use }; // Signature for output function. Should return true if writing was successful. @@ -220,9 +244,12 @@ struct WebPMemoryWriter { // The following must be called first before any use. WEBP_EXTERN(void) WebPMemoryWriterInit(WebPMemoryWriter* writer); +// The following must be called to deallocate writer->mem memory. The 'writer' +// object itself is not deallocated. +WEBP_EXTERN(void) WebPMemoryWriterClear(WebPMemoryWriter* writer); // The custom writer to be used with WebPMemoryWriter as custom_ptr. Upon // completion, writer.mem and writer.size will hold the coded data. -// writer.mem must be freed using the call 'free(writer.mem)'. +// writer.mem must be freed by calling WebPMemoryWriterClear. WEBP_EXTERN(int) WebPMemoryWrite(const uint8_t* data, size_t data_size, const WebPPicture* picture); @@ -234,16 +261,9 @@ typedef int (*WebPProgressHook)(int percent, const WebPPicture* picture); // Color spaces. typedef enum WebPEncCSP { // chroma sampling - WEBP_YUV420 = 0, // 4:2:0 - WEBP_YUV422 = 1, // 4:2:2 - WEBP_YUV444 = 2, // 4:4:4 - WEBP_YUV400 = 3, // grayscale - WEBP_CSP_UV_MASK = 3, // bit-mask to get the UV sampling factors - // alpha channel variants - WEBP_YUV420A = 4, - WEBP_YUV422A = 5, - WEBP_YUV444A = 6, - WEBP_YUV400A = 7, // grayscale + alpha + WEBP_YUV420 = 0, // 4:2:0 + WEBP_YUV420A = 4, // alpha channel variant + WEBP_CSP_UV_MASK = 3, // bit-mask to get the UV sampling factors WEBP_CSP_ALPHA_BIT = 4 // bit that is set if alpha is present } WebPEncCSP; @@ -322,17 +342,15 @@ struct WebPPicture { uint32_t pad3[3]; // padding for later use - // Unused for now: original samples (for non-YUV420 modes) - uint8_t *u0, *v0; - int uv0_stride; - - uint32_t pad4[7]; // padding for later use + // Unused for now + uint8_t *pad4, *pad5; + uint32_t pad6[8]; // padding for later use // PRIVATE FIELDS //////////////////// void* memory_; // row chunk of memory for yuva planes void* memory_argb_; // and for argb too. - void* pad5[2]; // padding for later use + void* pad7[2]; // padding for later use }; // Internal, version-checked, entry point @@ -368,9 +386,24 @@ WEBP_EXTERN(void) WebPPictureFree(WebPPicture* picture); // Returns false in case of memory allocation error. WEBP_EXTERN(int) WebPPictureCopy(const WebPPicture* src, WebPPicture* dst); -// Compute PSNR, SSIM or LSIM distortion metric between two pictures. -// Result is in dB, stores in result[] in the Y/U/V/Alpha/All order. -// Returns false in case of error (src and ref don't have same dimension, ...) +// Compute the single distortion for packed planes of samples. +// 'src' will be compared to 'ref', and the raw distortion stored into +// '*distortion'. The refined metric (log(MSE), log(1 - ssim),...' will be +// stored in '*result'. +// 'x_step' is the horizontal stride (in bytes) between samples. +// 'src/ref_stride' is the byte distance between rows. +// Returns false in case of error (bad parameter, memory allocation error, ...). +WEBP_EXTERN(int) WebPPlaneDistortion(const uint8_t* src, size_t src_stride, + const uint8_t* ref, size_t ref_stride, + int width, int height, + size_t x_step, + int type, // 0 = PSNR, 1 = SSIM, 2 = LSIM + float* distortion, float* result); + +// Compute PSNR, SSIM or LSIM distortion metric between two pictures. Results +// are in dB, stored in result[] in the B/G/R/A/All order. The distortion is +// always performed using ARGB samples. Hence if the input is YUV(A), the +// picture will be internally converted to ARGB (just for the measurement). // Warning: this function is rather CPU-intensive. WEBP_EXTERN(int) WebPPictureDistortion( const WebPPicture* src, const WebPPicture* ref, @@ -408,7 +441,9 @@ WEBP_EXTERN(int) WebPPictureView(const WebPPicture* src, WEBP_EXTERN(int) WebPPictureIsView(const WebPPicture* picture); // Rescale a picture to new dimension width x height. -// Now gamma correction is applied. +// If either 'width' or 'height' (but not both) is 0 the corresponding +// dimension will be calculated preserving the aspect ratio. +// No gamma correction is applied. // Returns false in case of error (invalid parameter or insufficient memory). WEBP_EXTERN(int) WebPPictureRescale(WebPPicture* pic, int width, int height); @@ -435,25 +470,42 @@ WEBP_EXTERN(int) WebPPictureImportBGRA( WEBP_EXTERN(int) WebPPictureImportBGRX( WebPPicture* picture, const uint8_t* bgrx, int bgrx_stride); -// Converts picture->argb data to the YUVA format specified by 'colorspace'. +// Converts picture->argb data to the YUV420A format. The 'colorspace' +// parameter is deprecated and should be equal to WEBP_YUV420. // Upon return, picture->use_argb is set to false. The presence of real // non-opaque transparent values is detected, and 'colorspace' will be // adjusted accordingly. Note that this method is lossy. // Returns false in case of error. WEBP_EXTERN(int) WebPPictureARGBToYUVA(WebPPicture* picture, - WebPEncCSP colorspace); + WebPEncCSP /*colorspace = WEBP_YUV420*/); + +// Same as WebPPictureARGBToYUVA(), but the conversion is done using +// pseudo-random dithering with a strength 'dithering' between +// 0.0 (no dithering) and 1.0 (maximum dithering). This is useful +// for photographic picture. +WEBP_EXTERN(int) WebPPictureARGBToYUVADithered( + WebPPicture* picture, WebPEncCSP colorspace, float dithering); + +// Performs 'sharp' RGBA->YUVA420 downsampling and colorspace conversion. +// Downsampling is handled with extra care in case of color clipping. This +// method is roughly 2x slower than WebPPictureARGBToYUVA() but produces better +// and sharper YUV representation. +// Returns false in case of error. +WEBP_EXTERN(int) WebPPictureSharpARGBToYUVA(WebPPicture* picture); +// kept for backward compatibility: +WEBP_EXTERN(int) WebPPictureSmartARGBToYUVA(WebPPicture* picture); // Converts picture->yuv to picture->argb and sets picture->use_argb to true. -// The input format must be YUV_420 or YUV_420A. -// Note that the use of this method is discouraged if one has access to the -// raw ARGB samples, since using YUV420 is comparatively lossy. Also, the -// conversion from YUV420 to ARGB incurs a small loss too. +// The input format must be YUV_420 or YUV_420A. The conversion from YUV420 to +// ARGB incurs a small loss too. +// Note that the use of this colorspace is discouraged if one has access to the +// raw ARGB samples, since using YUV420 is comparatively lossy. // Returns false in case of error. WEBP_EXTERN(int) WebPPictureYUVAToARGB(WebPPicture* picture); -// Helper function: given a width x height plane of YUV(A) samples -// (with stride 'stride'), clean-up the YUV samples under fully transparent -// area, to help compressibility (no guarantee, though). +// Helper function: given a width x height plane of RGBA or YUV(A) samples +// clean-up the YUV or RGB samples under fully transparent area, to help +// compressibility (no guarantee, though). WEBP_EXTERN(void) WebPCleanupTransparentArea(WebPPicture* picture); // Scan the picture 'picture' for the presence of non fully opaque alpha values. @@ -461,6 +513,11 @@ WEBP_EXTERN(void) WebPCleanupTransparentArea(WebPPicture* picture); // alpha plane can be ignored altogether e.g.). WEBP_EXTERN(int) WebPPictureHasTransparency(const WebPPicture* picture); +// Remove the transparency information (if present) by blending the color with +// the background color 'background_rgb' (specified as 24bit RGB triplet). +// After this call, all alpha values are reset to 0xff. +WEBP_EXTERN(void) WebPBlendAlpha(WebPPicture* pic, uint32_t background_rgb); + //------------------------------------------------------------------------------ // Main call @@ -478,7 +535,7 @@ WEBP_EXTERN(int) WebPEncode(const WebPConfig* config, WebPPicture* picture); //------------------------------------------------------------------------------ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/webp/format_constants.h b/3rdparty/libwebp/webp/format_constants.h index 4c04b50c6a..329fc8a3b0 100644 --- a/3rdparty/libwebp/webp/format_constants.h +++ b/3rdparty/libwebp/webp/format_constants.h @@ -15,7 +15,7 @@ #define WEBP_WEBP_FORMAT_CONSTANTS_H_ // Create fourcc of the chunk from the chunk tag characters. -#define MKFOURCC(a, b, c, d) ((uint32_t)(a) | (b) << 8 | (c) << 16 | (d) << 24) +#define MKFOURCC(a, b, c, d) ((a) | (b) << 8 | (c) << 16 | (uint32_t)(d) << 24) // VP8 related constants. #define VP8_SIGNATURE 0x9d012a // Signature in VP8 data. @@ -72,14 +72,13 @@ typedef enum { #define RIFF_HEADER_SIZE 12 // Size of the RIFF header ("RIFFnnnnWEBP"). #define ANMF_CHUNK_SIZE 16 // Size of an ANMF chunk. #define ANIM_CHUNK_SIZE 6 // Size of an ANIM chunk. -#define FRGM_CHUNK_SIZE 6 // Size of a FRGM chunk. #define VP8X_CHUNK_SIZE 10 // Size of a VP8X chunk. #define MAX_CANVAS_SIZE (1 << 24) // 24-bit max for VP8X width/height. #define MAX_IMAGE_AREA (1ULL << 32) // 32-bit max for width x height. #define MAX_LOOP_COUNT (1 << 16) // maximum value for loop-count #define MAX_DURATION (1 << 24) // maximum duration -#define MAX_POSITION_OFFSET (1 << 24) // maximum frame/fragment x/y offset +#define MAX_POSITION_OFFSET (1 << 24) // maximum frame x/y offset // Maximum chunk payload is such that adding the header and padding won't // overflow a uint32_t. diff --git a/3rdparty/libwebp/webp/mux.h b/3rdparty/libwebp/webp/mux.h index b8c7dc62ec..daccc65e86 100644 --- a/3rdparty/libwebp/webp/mux.h +++ b/3rdparty/libwebp/webp/mux.h @@ -7,53 +7,58 @@ // be found in the AUTHORS file in the root of the source tree. // ----------------------------------------------------------------------------- // -// RIFF container manipulation for WEBP images. +// RIFF container manipulation and encoding for WebP images. // // Authors: Urvang (urvang@google.com) // Vikas (vikasa@google.com) -// This API allows manipulation of WebP container images containing features -// like color profile, metadata, animation and fragmented images. -// -// Code Example#1: Creating a MUX with image data, color profile and XMP -// metadata. -// -// int copy_data = 0; -// WebPMux* mux = WebPMuxNew(); -// // ... (Prepare image data). -// WebPMuxSetImage(mux, &image, copy_data); -// // ... (Prepare ICCP color profile data). -// WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data); -// // ... (Prepare XMP metadata). -// WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data); -// // Get data from mux in WebP RIFF format. -// WebPMuxAssemble(mux, &output_data); -// WebPMuxDelete(mux); -// // ... (Consume output_data; e.g. write output_data.bytes to file). -// WebPDataClear(&output_data); -// -// Code Example#2: Get image and color profile data from a WebP file. -// -// int copy_data = 0; -// // ... (Read data from file). -// WebPMux* mux = WebPMuxCreate(&data, copy_data); -// WebPMuxGetFrame(mux, 1, &image); -// // ... (Consume image; e.g. call WebPDecode() to decode the data). -// WebPMuxGetChunk(mux, "ICCP", &icc_profile); -// // ... (Consume icc_data). -// WebPMuxDelete(mux); -// free(data); - #ifndef WEBP_WEBP_MUX_H_ #define WEBP_WEBP_MUX_H_ #include "./mux_types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -#define WEBP_MUX_ABI_VERSION 0x0100 // MAJOR(8b) + MINOR(8b) +#define WEBP_MUX_ABI_VERSION 0x0108 // MAJOR(8b) + MINOR(8b) + +//------------------------------------------------------------------------------ +// Mux API +// +// This API allows manipulation of WebP container images containing features +// like color profile, metadata, animation. +// +// Code Example#1: Create a WebPMux object with image data, color profile and +// XMP metadata. +/* + int copy_data = 0; + WebPMux* mux = WebPMuxNew(); + // ... (Prepare image data). + WebPMuxSetImage(mux, &image, copy_data); + // ... (Prepare ICCP color profile data). + WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data); + // ... (Prepare XMP metadata). + WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data); + // Get data from mux in WebP RIFF format. + WebPMuxAssemble(mux, &output_data); + WebPMuxDelete(mux); + // ... (Consume output_data; e.g. write output_data.bytes to file). + WebPDataClear(&output_data); +*/ + +// Code Example#2: Get image and color profile data from a WebP file. +/* + int copy_data = 0; + // ... (Read data from file). + WebPMux* mux = WebPMuxCreate(&data, copy_data); + WebPMuxGetFrame(mux, 1, &image); + // ... (Consume image; e.g. call WebPDecode() to decode the data). + WebPMuxGetChunk(mux, "ICCP", &icc_profile); + // ... (Consume icc_data). + WebPMuxDelete(mux); + free(data); +*/ // Note: forward declaring enumerations is not allowed in (strict) C and C++, // the types are left here for reference. @@ -62,6 +67,7 @@ extern "C" { typedef struct WebPMux WebPMux; // main opaque object. typedef struct WebPMuxFrameInfo WebPMuxFrameInfo; typedef struct WebPMuxAnimParams WebPMuxAnimParams; +typedef struct WebPAnimEncoderOptions WebPAnimEncoderOptions; // Error codes typedef enum WebPMuxError { @@ -75,23 +81,23 @@ typedef enum WebPMuxError { // IDs for different types of chunks. typedef enum WebPChunkId { - WEBP_CHUNK_VP8X, // VP8X - WEBP_CHUNK_ICCP, // ICCP - WEBP_CHUNK_ANIM, // ANIM - WEBP_CHUNK_ANMF, // ANMF - WEBP_CHUNK_FRGM, // FRGM - WEBP_CHUNK_ALPHA, // ALPH - WEBP_CHUNK_IMAGE, // VP8/VP8L - WEBP_CHUNK_EXIF, // EXIF - WEBP_CHUNK_XMP, // XMP - WEBP_CHUNK_UNKNOWN, // Other chunks. + WEBP_CHUNK_VP8X, // VP8X + WEBP_CHUNK_ICCP, // ICCP + WEBP_CHUNK_ANIM, // ANIM + WEBP_CHUNK_ANMF, // ANMF + WEBP_CHUNK_DEPRECATED, // (deprecated from FRGM) + WEBP_CHUNK_ALPHA, // ALPH + WEBP_CHUNK_IMAGE, // VP8/VP8L + WEBP_CHUNK_EXIF, // EXIF + WEBP_CHUNK_XMP, // XMP + WEBP_CHUNK_UNKNOWN, // Other chunks. WEBP_CHUNK_NIL } WebPChunkId; //------------------------------------------------------------------------------ // Returns the version number of the mux library, packed in hexadecimal using -// 8bits or each of major/minor/revision. E.g: v2.5.7 is 0x020507. +// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507. WEBP_EXTERN(int) WebPGetMuxVersion(void); //------------------------------------------------------------------------------ @@ -103,6 +109,7 @@ WEBP_EXTERN(WebPMux*) WebPNewInternal(int); // Creates an empty mux object. // Returns: // A pointer to the newly created empty mux object. +// Or NULL in case of memory error. static WEBP_INLINE WebPMux* WebPMuxNew(void) { return WebPNewInternal(WEBP_MUX_ABI_VERSION); } @@ -122,7 +129,7 @@ WEBP_EXTERN(WebPMux*) WebPMuxCreateInternal(const WebPData*, int, int); // Parameters: // bitstream - (in) the bitstream data in WebP RIFF format // copy_data - (in) value 1 indicates given data WILL be copied to the mux -// and value 0 indicates data will NOT be copied. +// object and value 0 indicates data will NOT be copied. // Returns: // A pointer to the mux object created from given data - on success. // NULL - In case of invalid data or memory error. @@ -135,9 +142,9 @@ static WEBP_INLINE WebPMux* WebPMuxCreate(const WebPData* bitstream, // Non-image chunks. // Note: Only non-image related chunks should be managed through chunk APIs. -// (Image related chunks are: "ANMF", "FRGM", "VP8 ", "VP8L" and "ALPH"). -// To add, get and delete images, use APIs WebPMuxSetImage(), -// WebPMuxPushFrame(), WebPMuxGetFrame() and WebPMuxDeleteFrame(). +// (Image related chunks are: "ANMF", "VP8 ", "VP8L" and "ALPH"). +// To add, get and delete images, use WebPMuxSetImage(), WebPMuxPushFrame(), +// WebPMuxGetFrame() and WebPMuxDeleteFrame(). // Adds a chunk with id 'fourcc' and data 'chunk_data' in the mux object. // Any existing chunk(s) with the same id will be removed. @@ -147,7 +154,7 @@ static WEBP_INLINE WebPMux* WebPMuxCreate(const WebPData* bitstream, // e.g., "ICCP", "XMP ", "EXIF" etc. // chunk_data - (in) the chunk data to be added // copy_data - (in) value 1 indicates given data WILL be copied to the mux -// and value 0 indicates data will NOT be copied. +// object and value 0 indicates data will NOT be copied. // Returns: // WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL // or if fourcc corresponds to an image chunk. @@ -165,7 +172,7 @@ WEBP_EXTERN(WebPMuxError) WebPMuxSetChunk( // e.g., "ICCP", "XMP ", "EXIF" etc. // chunk_data - (out) returned chunk data // Returns: -// WEBP_MUX_INVALID_ARGUMENT - if either mux, fourcc or chunk_data is NULL +// WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL // or if fourcc corresponds to an image chunk. // WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given id. // WEBP_MUX_OK - on success. @@ -188,28 +195,29 @@ WEBP_EXTERN(WebPMuxError) WebPMuxDeleteChunk( //------------------------------------------------------------------------------ // Images. -// Encapsulates data about a single frame/fragment. +// Encapsulates data about a single frame. struct WebPMuxFrameInfo { - WebPData bitstream; // image data: can either be a raw VP8/VP8L bitstream + WebPData bitstream; // image data: can be a raw VP8/VP8L bitstream // or a single-image WebP file. int x_offset; // x-offset of the frame. int y_offset; // y-offset of the frame. int duration; // duration of the frame (in milliseconds). - WebPChunkId id; // frame type: should be one of WEBP_CHUNK_ANMF, - // WEBP_CHUNK_FRGM or WEBP_CHUNK_IMAGE + WebPChunkId id; // frame type: should be one of WEBP_CHUNK_ANMF + // or WEBP_CHUNK_IMAGE WebPMuxAnimDispose dispose_method; // Disposal method for the frame. - uint32_t pad[2]; // padding for later use + WebPMuxAnimBlend blend_method; // Blend operation for the frame. + uint32_t pad[1]; // padding for later use }; -// Sets the (non-animated and non-fragmented) image in the mux object. -// Note: Any existing images (including frames/fragments) will be removed. +// Sets the (non-animated) image in the mux object. +// Note: Any existing images (including frames) will be removed. // Parameters: // mux - (in/out) object in which the image is to be set -// bitstream - (in) can either be a raw VP8/VP8L bitstream or a single-image -// WebP file (non-animated and non-fragmented) +// bitstream - (in) can be a raw VP8/VP8L bitstream or a single-image +// WebP file (non-animated) // copy_data - (in) value 1 indicates given data WILL be copied to the mux -// and value 0 indicates data will NOT be copied. +// object and value 0 indicates data will NOT be copied. // Returns: // WEBP_MUX_INVALID_ARGUMENT - if mux is NULL or bitstream is NULL. // WEBP_MUX_MEMORY_ERROR - on memory allocation error. @@ -218,9 +226,8 @@ WEBP_EXTERN(WebPMuxError) WebPMuxSetImage( WebPMux* mux, const WebPData* bitstream, int copy_data); // Adds a frame at the end of the mux object. -// Notes: (1) frame.id should be one of WEBP_CHUNK_ANMF or WEBP_CHUNK_FRGM -// (2) For setting a non-animated non-fragmented image, use -// WebPMuxSetImage() instead. +// Notes: (1) frame.id should be WEBP_CHUNK_ANMF +// (2) For setting a non-animated image, use WebPMuxSetImage() instead. // (3) Type of frame being pushed must be same as the frames in mux. // (4) As WebP only supports even offsets, any odd offset will be snapped // to an even location using: offset &= ~1 @@ -228,7 +235,7 @@ WEBP_EXTERN(WebPMuxError) WebPMuxSetImage( // mux - (in/out) object to which the frame is to be added // frame - (in) frame data. // copy_data - (in) value 1 indicates given data WILL be copied to the mux -// and value 0 indicates data will NOT be copied. +// object and value 0 indicates data will NOT be copied. // Returns: // WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL // or if content of 'frame' is invalid. @@ -250,6 +257,7 @@ WEBP_EXTERN(WebPMuxError) WebPMuxPushFrame( // WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL. // WEBP_MUX_NOT_FOUND - if there are less than nth frames in the mux object. // WEBP_MUX_BAD_DATA - if nth frame chunk in mux is invalid. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. // WEBP_MUX_OK - on success. WEBP_EXTERN(WebPMuxError) WebPMuxGetFrame( const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame); @@ -285,7 +293,7 @@ struct WebPMuxAnimParams { // mux - (in/out) object in which ANIM chunk is to be set/added // params - (in) animation parameters. // Returns: -// WEBP_MUX_INVALID_ARGUMENT - if either mux or params is NULL +// WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL. // WEBP_MUX_MEMORY_ERROR - on memory allocation error. // WEBP_MUX_OK - on success. WEBP_EXTERN(WebPMuxError) WebPMuxSetAnimationParams( @@ -296,7 +304,7 @@ WEBP_EXTERN(WebPMuxError) WebPMuxSetAnimationParams( // mux - (in) object from which the animation parameters to be fetched // params - (out) animation parameters extracted from the ANIM chunk // Returns: -// WEBP_MUX_INVALID_ARGUMENT - if either of mux or params is NULL +// WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL. // WEBP_MUX_NOT_FOUND - if ANIM chunk is not present in mux object. // WEBP_MUX_OK - on success. WEBP_EXTERN(WebPMuxError) WebPMuxGetAnimationParams( @@ -305,27 +313,62 @@ WEBP_EXTERN(WebPMuxError) WebPMuxGetAnimationParams( //------------------------------------------------------------------------------ // Misc Utilities. +// Sets the canvas size for the mux object. The width and height can be +// specified explicitly or left as zero (0, 0). +// * When width and height are specified explicitly, then this frame bound is +// enforced during subsequent calls to WebPMuxAssemble() and an error is +// reported if any animated frame does not completely fit within the canvas. +// * When unspecified (0, 0), the constructed canvas will get the frame bounds +// from the bounding-box over all frames after calling WebPMuxAssemble(). +// Parameters: +// mux - (in) object to which the canvas size is to be set +// width - (in) canvas width +// height - (in) canvas height +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL; or +// width or height are invalid or out of bounds +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxSetCanvasSize(WebPMux* mux, + int width, int height); + +// Gets the canvas size from the mux object. +// Note: This method assumes that the VP8X chunk, if present, is up-to-date. +// That is, the mux object hasn't been modified since the last call to +// WebPMuxAssemble() or WebPMuxCreate(). +// Parameters: +// mux - (in) object from which the canvas size is to be fetched +// width - (out) canvas width +// height - (out) canvas height +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux, width or height is NULL. +// WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxGetCanvasSize(const WebPMux* mux, + int* width, int* height); + // Gets the feature flags from the mux object. +// Note: This method assumes that the VP8X chunk, if present, is up-to-date. +// That is, the mux object hasn't been modified since the last call to +// WebPMuxAssemble() or WebPMuxCreate(). // Parameters: // mux - (in) object from which the features are to be fetched // flags - (out) the flags specifying which features are present in the // mux object. This will be an OR of various flag values. // Enum 'WebPFeatureFlags' can be used to test individual flag values. // Returns: -// WEBP_MUX_INVALID_ARGUMENT - if mux or flags is NULL -// WEBP_MUX_NOT_FOUND - if VP8X chunk is not present in mux object. -// WEBP_MUX_BAD_DATA - if VP8X chunk in mux is invalid. +// WEBP_MUX_INVALID_ARGUMENT - if mux or flags is NULL. +// WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid. // WEBP_MUX_OK - on success. WEBP_EXTERN(WebPMuxError) WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags); -// Gets number of chunks having tag value tag in the mux object. +// Gets number of chunks with the given 'id' in the mux object. // Parameters: // mux - (in) object from which the info is to be fetched // id - (in) chunk id specifying the type of chunk // num_elements - (out) number of chunks with the given chunk id // Returns: -// WEBP_MUX_INVALID_ARGUMENT - if either mux, or num_elements is NULL +// WEBP_MUX_INVALID_ARGUMENT - if mux, or num_elements is NULL. // WEBP_MUX_OK - on success. WEBP_EXTERN(WebPMuxError) WebPMuxNumChunks(const WebPMux* mux, WebPChunkId id, int* num_elements); @@ -335,22 +378,152 @@ WEBP_EXTERN(WebPMuxError) WebPMuxNumChunks(const WebPMux* mux, // Note: The content of 'assembled_data' will be ignored and overwritten. // Also, the content of 'assembled_data' is allocated using malloc(), and NOT // owned by the 'mux' object. It MUST be deallocated by the caller by calling -// WebPDataClear(). +// WebPDataClear(). It's always safe to call WebPDataClear() upon return, +// even in case of error. // Parameters: // mux - (in/out) object whose chunks are to be assembled // assembled_data - (out) assembled WebP data // Returns: // WEBP_MUX_BAD_DATA - if mux object is invalid. -// WEBP_MUX_INVALID_ARGUMENT - if either mux, output_data or output_size is -// NULL. +// WEBP_MUX_INVALID_ARGUMENT - if mux or assembled_data is NULL. // WEBP_MUX_MEMORY_ERROR - on memory allocation error. -// WEBP_MUX_OK - on success +// WEBP_MUX_OK - on success. WEBP_EXTERN(WebPMuxError) WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data); //------------------------------------------------------------------------------ +// WebPAnimEncoder API +// +// This API allows encoding (possibly) animated WebP images. +// +// Code Example: +/* + WebPAnimEncoderOptions enc_options; + WebPAnimEncoderOptionsInit(&enc_options); + // Tune 'enc_options' as needed. + WebPAnimEncoder* enc = WebPAnimEncoderNew(width, height, &enc_options); + while() { + WebPConfig config; + WebPConfigInit(&config); + // Tune 'config' as needed. + WebPAnimEncoderAdd(enc, frame, timestamp_ms, &config); + } + WebPAnimEncoderAdd(enc, NULL, timestamp_ms, NULL); + WebPAnimEncoderAssemble(enc, webp_data); + WebPAnimEncoderDelete(enc); + // Write the 'webp_data' to a file, or re-mux it further. +*/ -#if defined(__cplusplus) || defined(c_plusplus) +typedef struct WebPAnimEncoder WebPAnimEncoder; // Main opaque object. + +// Forward declarations. Defined in encode.h. +struct WebPPicture; +struct WebPConfig; + +// Global options. +struct WebPAnimEncoderOptions { + WebPMuxAnimParams anim_params; // Animation parameters. + int minimize_size; // If true, minimize the output size (slow). Implicitly + // disables key-frame insertion. + int kmin; + int kmax; // Minimum and maximum distance between consecutive key + // frames in the output. The library may insert some key + // frames as needed to satisfy this criteria. + // Note that these conditions should hold: kmax > kmin + // and kmin >= kmax / 2 + 1. Also, if kmax <= 0, then + // key-frame insertion is disabled; and if kmax == 1, + // then all frames will be key-frames (kmin value does + // not matter for these special cases). + int allow_mixed; // If true, use mixed compression mode; may choose + // either lossy and lossless for each frame. + int verbose; // If true, print info and warning messages to stderr. + + uint32_t padding[4]; // Padding for later use. +}; + +// Internal, version-checked, entry point. +WEBP_EXTERN(int) WebPAnimEncoderOptionsInitInternal( + WebPAnimEncoderOptions*, int); + +// Should always be called, to initialize a fresh WebPAnimEncoderOptions +// structure before modification. Returns false in case of version mismatch. +// WebPAnimEncoderOptionsInit() must have succeeded before using the +// 'enc_options' object. +static WEBP_INLINE int WebPAnimEncoderOptionsInit( + WebPAnimEncoderOptions* enc_options) { + return WebPAnimEncoderOptionsInitInternal(enc_options, WEBP_MUX_ABI_VERSION); +} + +// Internal, version-checked, entry point. +WEBP_EXTERN(WebPAnimEncoder*) WebPAnimEncoderNewInternal( + int, int, const WebPAnimEncoderOptions*, int); + +// Creates and initializes a WebPAnimEncoder object. +// Parameters: +// width/height - (in) canvas width and height of the animation. +// enc_options - (in) encoding options; can be passed NULL to pick +// reasonable defaults. +// Returns: +// A pointer to the newly created WebPAnimEncoder object. +// Or NULL in case of memory error. +static WEBP_INLINE WebPAnimEncoder* WebPAnimEncoderNew( + int width, int height, const WebPAnimEncoderOptions* enc_options) { + return WebPAnimEncoderNewInternal(width, height, enc_options, + WEBP_MUX_ABI_VERSION); +} + +// Optimize the given frame for WebP, encode it and add it to the +// WebPAnimEncoder object. +// The last call to 'WebPAnimEncoderAdd' should be with frame = NULL, which +// indicates that no more frames are to be added. This call is also used to +// determine the duration of the last frame. +// Parameters: +// enc - (in/out) object to which the frame is to be added. +// frame - (in/out) frame data in ARGB or YUV(A) format. If it is in YUV(A) +// format, it will be converted to ARGB, which incurs a small loss. +// timestamp_ms - (in) timestamp of this frame in milliseconds. +// Duration of a frame would be calculated as +// "timestamp of next frame - timestamp of this frame". +// Hence, timestamps should be in non-decreasing order. +// config - (in) encoding options; can be passed NULL to pick +// reasonable defaults. +// Returns: +// On error, returns false and frame->error_code is set appropriately. +// Otherwise, returns true. +WEBP_EXTERN(int) WebPAnimEncoderAdd( + WebPAnimEncoder* enc, struct WebPPicture* frame, int timestamp_ms, + const struct WebPConfig* config); + +// Assemble all frames added so far into a WebP bitstream. +// This call should be preceded by a call to 'WebPAnimEncoderAdd' with +// frame = NULL; if not, the duration of the last frame will be internally +// estimated. +// Parameters: +// enc - (in/out) object from which the frames are to be assembled. +// webp_data - (out) generated WebP bitstream. +// Returns: +// True on success. +WEBP_EXTERN(int) WebPAnimEncoderAssemble(WebPAnimEncoder* enc, + WebPData* webp_data); + +// Get error string corresponding to the most recent call using 'enc'. The +// returned string is owned by 'enc' and is valid only until the next call to +// WebPAnimEncoderAdd() or WebPAnimEncoderAssemble() or WebPAnimEncoderDelete(). +// Parameters: +// enc - (in/out) object from which the error string is to be fetched. +// Returns: +// NULL if 'enc' is NULL. Otherwise, returns the error string if the last call +// to 'enc' had an error, or an empty string if the last call was a success. +WEBP_EXTERN(const char*) WebPAnimEncoderGetError(WebPAnimEncoder* enc); + +// Deletes the WebPAnimEncoder object. +// Parameters: +// enc - (in/out) object to be deleted +WEBP_EXTERN(void) WebPAnimEncoderDelete(WebPAnimEncoder* enc); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/webp/mux_types.h b/3rdparty/libwebp/webp/mux_types.h index b8bce363e5..b37e2c67aa 100644 --- a/3rdparty/libwebp/webp/mux_types.h +++ b/3rdparty/libwebp/webp/mux_types.h @@ -18,7 +18,7 @@ #include // memset() #include "./types.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -26,16 +26,18 @@ extern "C" { // the types are left here for reference. // typedef enum WebPFeatureFlags WebPFeatureFlags; // typedef enum WebPMuxAnimDispose WebPMuxAnimDispose; +// typedef enum WebPMuxAnimBlend WebPMuxAnimBlend; typedef struct WebPData WebPData; // VP8X Feature Flags. typedef enum WebPFeatureFlags { - FRAGMENTS_FLAG = 0x00000001, ANIMATION_FLAG = 0x00000002, XMP_FLAG = 0x00000004, EXIF_FLAG = 0x00000008, ALPHA_FLAG = 0x00000010, - ICCP_FLAG = 0x00000020 + ICCP_FLAG = 0x00000020, + + ALL_VALID_FLAGS = 0x0000003e } WebPFeatureFlags; // Dispose method (animation only). Indicates how the area used by the current @@ -45,6 +47,13 @@ typedef enum WebPMuxAnimDispose { WEBP_MUX_DISPOSE_BACKGROUND // Dispose to background color. } WebPMuxAnimDispose; +// Blend operation (animation only). Indicates how transparent pixels of the +// current frame are blended with those of the previous canvas. +typedef enum WebPMuxAnimBlend { + WEBP_MUX_BLEND, // Blend. + WEBP_MUX_NO_BLEND // Do not blend. +} WebPMuxAnimBlend; + // Data type used to describe 'raw' data, e.g., chunk data // (ICC profile, metadata) and WebP compressed image data. struct WebPData { @@ -82,7 +91,7 @@ static WEBP_INLINE int WebPDataCopy(const WebPData* src, WebPData* dst) { return 1; } -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } // extern "C" #endif diff --git a/3rdparty/libwebp/webp/types.h b/3rdparty/libwebp/webp/types.h index 568d1f263f..98fff35a11 100644 --- a/3rdparty/libwebp/webp/types.h +++ b/3rdparty/libwebp/webp/types.h @@ -18,10 +18,11 @@ #ifndef _MSC_VER #include -#ifdef __STRICT_ANSI__ -#define WEBP_INLINE -#else /* __STRICT_ANSI__ */ +#if defined(__cplusplus) || !defined(__STRICT_ANSI__) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) #define WEBP_INLINE inline +#else +#define WEBP_INLINE #endif #else typedef signed char int8_t; @@ -38,7 +39,11 @@ typedef long long int int64_t; #ifndef WEBP_EXTERN // This explicitly marks library functions and allows for changing the // signature for e.g., Windows DLL builds. -#define WEBP_EXTERN(type) extern type +# if defined(__GNUC__) && __GNUC__ >= 4 +# define WEBP_EXTERN(type) extern __attribute__ ((visibility ("default"))) type +# else +# define WEBP_EXTERN(type) extern type +# endif /* __GNUC__ >= 4 */ #endif /* WEBP_EXTERN */ // Macro to check ABI compatibility (same major revision number) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91fad31958..4b522e0234 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,7 +191,7 @@ OCV_OPTION(WITH_GTK_2_X "Use GTK version 2" OFF OCV_OPTION(WITH_IPP "Include Intel IPP support" NOT MINGW IF (X86_64 OR X86) AND NOT WINRT ) OCV_OPTION(WITH_JASPER "Include JPEG2K support" ON IF (NOT IOS) ) OCV_OPTION(WITH_JPEG "Include JPEG support" ON) -OCV_OPTION(WITH_WEBP "Include WebP support" ON IF (NOT IOS AND NOT WINRT) ) +OCV_OPTION(WITH_WEBP "Include WebP support" ON IF (NOT WINRT) ) OCV_OPTION(WITH_OPENEXR "Include ILM support via OpenEXR" ON IF (NOT IOS AND NOT WINRT) ) OCV_OPTION(WITH_OPENGL "Include OpenGL support" OFF IF (NOT ANDROID AND NOT WINRT) ) OCV_OPTION(WITH_OPENVX "Include OpenVX support" OFF) diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index 68232796ff..981ce7bcc0 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -116,7 +116,7 @@ bool WebPDecoder::readHeader() return false; } - data.create(1, wfile_size, CV_8U); + data.create(1, (int)wfile_size, CV_8U); size_t data_size = fread(data.ptr(), 1, wfile_size, wfile); From a3475693500093e6f0af8df92653ae4c75d3c461 Mon Sep 17 00:00:00 2001 From: cclauss Date: Fri, 10 Feb 2017 13:12:11 +0100 Subject: [PATCH 374/409] Merge pull request #8150 from cclauss/patch-1 cv2.findContours() no longer modifies source image (#8150) * cv2.findContours() no longer modifies source image Since OpenCV 3.2, cv2.findContours() no longer modifies the source image but returns a modified image as the first of three return parameters. ??? Do I have that correct ??? Also fixed up the code blocks to be valid markdown. * ```python --> @code{.py} Enables syntax highlighting in docs. --- .../py_contours_begin.markdown | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/doc/py_tutorials/py_imgproc/py_contours/py_contours_begin/py_contours_begin.markdown b/doc/py_tutorials/py_imgproc/py_contours/py_contours_begin/py_contours_begin.markdown index 62a892d095..a3e3928aed 100644 --- a/doc/py_tutorials/py_imgproc/py_contours/py_contours_begin/py_contours_begin.markdown +++ b/doc/py_tutorials/py_imgproc/py_contours/py_contours_begin/py_contours_begin.markdown @@ -17,8 +17,7 @@ detection and recognition. - For better accuracy, use binary images. So before finding contours, apply threshold or canny edge detection. -- findContours function modifies the source image. So if you want source image even after - finding contours, already store it to some other variables. +- Since OpenCV 3.2, findContours() no longer modifies the source image but returns a modified image as the first of three return parameters. - In OpenCV, finding contours is like finding white object from black background. So remember, object to be found should be white and background should be black. @@ -28,12 +27,12 @@ import numpy as np import cv2 im = cv2.imread('test.jpg') -imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY) -ret,thresh = cv2.threshold(imgray,127,255,0) -im2, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) +imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) +ret, thresh = cv2.threshold(imgray, 127, 255, 0) +im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) @endcode See, there are three arguments in **cv2.findContours()** function, first one is source image, second -is contour retrieval mode, third is contour approximation method. And it outputs the contours and +is contour retrieval mode, third is contour approximation method. And it outputs a modified image, the contours and hierarchy. contours is a Python list of all the contours in the image. Each individual contour is a Numpy array of (x,y) coordinates of boundary points of the object. @@ -49,15 +48,15 @@ contours which should be passed as a Python list, third argument is index of con drawing individual contour. To draw all contours, pass -1) and remaining arguments are color, thickness etc. -To draw all the contours in an image: +* To draw all the contours in an image: @code{.py} cv2.drawContours(img, contours, -1, (0,255,0), 3) @endcode -To draw an individual contour, say 4th contour: +* To draw an individual contour, say 4th contour: @code{.py} cv2.drawContours(img, contours, 3, (0,255,0), 3) @endcode -But most of the time, below method will be useful: +* But most of the time, below method will be useful: @code{.py} cnt = contours[4] cv2.drawContours(img, [cnt], 0, (0,255,0), 3) From 8633ef451e7166f8c7f380db24e616fa2d913377 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 10 Feb 2017 15:18:29 +0300 Subject: [PATCH 375/409] cmake: cleanup unused defines --- modules/highgui/CMakeLists.txt | 4 ---- modules/imgcodecs/CMakeLists.txt | 4 ---- modules/videoio/CMakeLists.txt | 4 ---- 3 files changed, 12 deletions(-) diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index eb5617751e..b0363c4482 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -138,10 +138,6 @@ if(APPLE) add_apple_compiler_options(the_module) endif() -if(BUILD_SHARED_LIBS) - add_definitions(-DHIGHGUI_EXPORTS) -endif() - if(MSVC) set_target_properties(${the_module} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:atlthunk.lib /NODEFAULTLIB:atlsd.lib /NODEFAULTLIB:libcmt.lib /DEBUG") endif() diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index 78006403d8..8da6c176cd 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -116,10 +116,6 @@ if(APPLE) add_apple_compiler_options(the_module) endif() -if(BUILD_SHARED_LIBS) - add_definitions(-DIMGCODECS_EXPORTS) -endif() - if(MSVC) set_target_properties(${the_module} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:atlthunk.lib /NODEFAULTLIB:atlsd.lib /NODEFAULTLIB:libcmt.lib /DEBUG") endif() diff --git a/modules/videoio/CMakeLists.txt b/modules/videoio/CMakeLists.txt index 8a5b9d7a36..763304adbb 100644 --- a/modules/videoio/CMakeLists.txt +++ b/modules/videoio/CMakeLists.txt @@ -244,10 +244,6 @@ if(APPLE) add_apple_compiler_options(the_module) endif() -if(BUILD_SHARED_LIBS) - add_definitions(-DVIDEOIO_EXPORTS) -endif() - if(MSVC) set_target_properties(${the_module} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:atlthunk.lib /NODEFAULTLIB:atlsd.lib /NODEFAULTLIB:libcmt.lib /DEBUG") endif() From 28f9ce4e331095dc9b8adf0519e90a824c78c11e Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 10 Feb 2017 15:28:50 +0300 Subject: [PATCH 376/409] cmake: cleanup MSVC checks --- 3rdparty/libtiff/CMakeLists.txt | 6 ++++-- 3rdparty/zlib/CMakeLists.txt | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/3rdparty/libtiff/CMakeLists.txt b/3rdparty/libtiff/CMakeLists.txt index 93bcf7ee6a..67ceac9e3f 100644 --- a/3rdparty/libtiff/CMakeLists.txt +++ b/3rdparty/libtiff/CMakeLists.txt @@ -11,11 +11,13 @@ check_include_file(assert.h HAVE_ASSERT_H) check_include_file(fcntl.h HAVE_FCNTL_H) check_include_file(io.h HAVE_IO_H) check_function_exists(jbg_newlen HAVE_JBG_NEWLEN) -check_function_exists(mmap HAVE_MMAP) check_include_file(search.h HAVE_SEARCH_H) check_include_file(string.h HAVE_STRING_H) check_include_file(sys/types.h HAVE_SYS_TYPES_H) -check_include_file(unistd.h HAVE_UNISTD_H) +if(NOT MSVC) + check_include_file(unistd.h HAVE_UNISTD_H) + check_function_exists(mmap HAVE_MMAP) +endif() if(WIN32 AND NOT WINRT) set(USE_WIN32_FILEIO 1) diff --git a/3rdparty/zlib/CMakeLists.txt b/3rdparty/zlib/CMakeLists.txt index 3376c3efda..84d46189a6 100644 --- a/3rdparty/zlib/CMakeLists.txt +++ b/3rdparty/zlib/CMakeLists.txt @@ -21,7 +21,9 @@ endif() # # Check for unistd.h # -check_include_file(unistd.h Z_HAVE_UNISTD_H) +if(NOT MSVC) + check_include_file(unistd.h Z_HAVE_UNISTD_H) +endif() if(MSVC) add_definitions(-D_CRT_SECURE_NO_DEPRECATE) From 37be9ddeec7dbafdfd195d730dcdaf0d01e4b61b Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Sat, 11 Feb 2017 00:20:57 +0900 Subject: [PATCH 377/409] add enum Bayer**2BGRA - let it possible to reach Bayer2BGRA conversion --- modules/imgproc/include/opencv2/imgproc.hpp | 12 +++++++++++- modules/imgproc/include/opencv2/imgproc/types_c.h | 12 +++++++++++- modules/imgproc/perf/perf_cvt_color.cpp | 10 ++++++---- modules/imgproc/src/color.cpp | 1 + modules/imgproc/src/demosaicing.cpp | 7 +++++-- 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 243d72b32e..87c5d835f4 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -769,8 +769,18 @@ enum ColorConversionCodes { COLOR_BayerRG2RGB_EA = COLOR_BayerBG2BGR_EA, COLOR_BayerGR2RGB_EA = COLOR_BayerGB2BGR_EA, + //! Demosaicing with alpha channel + COLOR_BayerBG2BGRA = 139, + COLOR_BayerGB2BGRA = 140, + COLOR_BayerRG2BGRA = 141, + COLOR_BayerGR2BGRA = 142, - COLOR_COLORCVT_MAX = 139 + COLOR_BayerBG2RGBA = COLOR_BayerRG2BGRA, + COLOR_BayerGB2RGBA = COLOR_BayerGR2BGRA, + COLOR_BayerRG2RGBA = COLOR_BayerBG2BGRA, + COLOR_BayerGR2RGBA = COLOR_BayerGB2BGRA, + + COLOR_COLORCVT_MAX = 143 }; /** types of intersection between rectangles diff --git a/modules/imgproc/include/opencv2/imgproc/types_c.h b/modules/imgproc/include/opencv2/imgproc/types_c.h index eacba02f96..ca487d2bd4 100644 --- a/modules/imgproc/include/opencv2/imgproc/types_c.h +++ b/modules/imgproc/include/opencv2/imgproc/types_c.h @@ -349,7 +349,17 @@ enum CV_BayerRG2RGB_EA = CV_BayerBG2BGR_EA, CV_BayerGR2RGB_EA = CV_BayerGB2BGR_EA, - CV_COLORCVT_MAX = 139 + CV_BayerBG2BGRA =139, + CV_BayerGB2BGRA =140, + CV_BayerRG2BGRA =141, + CV_BayerGR2BGRA =142, + + CV_BayerBG2RGBA =CV_BayerRG2BGRA, + CV_BayerGB2RGBA =CV_BayerGR2BGRA, + CV_BayerRG2RGBA =CV_BayerBG2BGRA, + CV_BayerGR2RGBA =CV_BayerGB2BGRA, + + CV_COLORCVT_MAX = 143 }; diff --git a/modules/imgproc/perf/perf_cvt_color.cpp b/modules/imgproc/perf/perf_cvt_color.cpp index fc9a2cd8e2..9bc2b89ee9 100644 --- a/modules/imgproc/perf/perf_cvt_color.cpp +++ b/modules/imgproc/perf/perf_cvt_color.cpp @@ -103,10 +103,10 @@ CV_ENUM(CvtMode, CV_ENUM(CvtModeBayer, - COLOR_BayerBG2BGR, COLOR_BayerBG2BGR_VNG, COLOR_BayerBG2GRAY, - COLOR_BayerGB2BGR, COLOR_BayerGB2BGR_VNG, COLOR_BayerGB2GRAY, - COLOR_BayerGR2BGR, COLOR_BayerGR2BGR_VNG, COLOR_BayerGR2GRAY, - COLOR_BayerRG2BGR, COLOR_BayerRG2BGR_VNG, COLOR_BayerRG2GRAY + COLOR_BayerBG2BGR, COLOR_BayerBG2BGRA, COLOR_BayerBG2BGR_VNG, COLOR_BayerBG2GRAY, + COLOR_BayerGB2BGR, COLOR_BayerGB2BGRA, COLOR_BayerGB2BGR_VNG, COLOR_BayerGB2GRAY, + COLOR_BayerGR2BGR, COLOR_BayerGR2BGRA, COLOR_BayerGR2BGR_VNG, COLOR_BayerGR2GRAY, + COLOR_BayerRG2BGR, COLOR_BayerRG2BGRA, COLOR_BayerRG2BGR_VNG, COLOR_BayerRG2GRAY ) @@ -149,6 +149,8 @@ ChPair getConversionInfo(int cvtMode) case COLOR_YUV2BGRA_NV21: case COLOR_YUV2RGBA_NV21: case COLOR_YUV2BGRA_YV12: case COLOR_YUV2RGBA_YV12: case COLOR_YUV2BGRA_IYUV: case COLOR_YUV2RGBA_IYUV: + case COLOR_BayerBG2BGRA: case COLOR_BayerGB2BGRA: + case COLOR_BayerGR2BGRA: case COLOR_BayerRG2BGRA: return ChPair(1,4); case COLOR_BGR5552GRAY: case COLOR_BGR5652GRAY: return ChPair(2,1); diff --git a/modules/imgproc/src/color.cpp b/modules/imgproc/src/color.cpp index b571e1e566..8aefa4ce72 100644 --- a/modules/imgproc/src/color.cpp +++ b/modules/imgproc/src/color.cpp @@ -9852,6 +9852,7 @@ void cv::cvtColor( InputArray _src, OutputArray _dst, int code, int dcn ) case CV_BayerBG2BGR: case CV_BayerGB2BGR: case CV_BayerRG2BGR: case CV_BayerGR2BGR: case CV_BayerBG2BGR_VNG: case CV_BayerGB2BGR_VNG: case CV_BayerRG2BGR_VNG: case CV_BayerGR2BGR_VNG: case CV_BayerBG2BGR_EA: case CV_BayerGB2BGR_EA: case CV_BayerRG2BGR_EA: case CV_BayerGR2BGR_EA: + case CV_BayerBG2BGRA: case CV_BayerGB2BGRA: case CV_BayerRG2BGRA: case CV_BayerGR2BGRA: demosaicing(src, _dst, code, dcn); break; diff --git a/modules/imgproc/src/demosaicing.cpp b/modules/imgproc/src/demosaicing.cpp index 296ace8a25..c096cabea4 100644 --- a/modules/imgproc/src/demosaicing.cpp +++ b/modules/imgproc/src/demosaicing.cpp @@ -1643,6 +1643,7 @@ void cv::demosaicing(InputArray _src, OutputArray _dst, int code, int dcn) break; case CV_BayerBG2BGR: case CV_BayerGB2BGR: case CV_BayerRG2BGR: case CV_BayerGR2BGR: + case CV_BayerBG2BGRA: case CV_BayerGB2BGRA: case CV_BayerRG2BGRA: case CV_BayerGR2BGRA: case CV_BayerBG2BGR_VNG: case CV_BayerGB2BGR_VNG: case CV_BayerRG2BGR_VNG: case CV_BayerGR2BGR_VNG: { if (dcn <= 0) @@ -1652,8 +1653,10 @@ void cv::demosaicing(InputArray _src, OutputArray _dst, int code, int dcn) _dst.create(sz, CV_MAKE_TYPE(depth, dcn)); Mat dst_ = _dst.getMat(); - if( code == CV_BayerBG2BGR || code == CV_BayerGB2BGR || - code == CV_BayerRG2BGR || code == CV_BayerGR2BGR ) + if( code == CV_BayerBG2BGR || code == CV_BayerBG2BGRA || + code == CV_BayerGB2BGR || code == CV_BayerGB2BGRA || + code == CV_BayerRG2BGR || code == CV_BayerRG2BGRA || + code == CV_BayerGR2BGR || code == CV_BayerGR2BGRA ) { if( depth == CV_8U ) Bayer2RGB_(src, dst_, code); From 88e54ca1c0f9476ede27d647619d2d8e42d8c315 Mon Sep 17 00:00:00 2001 From: "Gabriel B. Nunes" Date: Sat, 11 Feb 2017 20:40:37 -0500 Subject: [PATCH 378/409] Work around CMake bug that mangles install dir CMake has a long-standing bug/feature (see [here](https://cmake.org/pipermail/cmake/2015-March/060204.html) and reply [here](https://cmake.org/pipermail/cmake/2015-March/060209.html)) which can mangle certain path variables by attempting to make them into relative paths if you try to set them with CACHE PATH. Say you have your OpenCV download at `/path/on/my/computer/to/opencv/`. What actually happens is that if you try to set this variable by invoking CMAKE with `-DCMAKE_INSTALL_PREFIX=/my/desired/install/path`, what you end up is *not* `/usr/local/` and *not* `my/desired/install/path`, but instead, this monstrosity: `/path/on/my/computer/to/opencv/src/OpenCV-build//my/desired/install/path`. That is, CMake attempts, for some reason, to turn the path that you passed into a path relative to `${CMAKE_BINARY_DIR}`. See the links I posted above: this is a known (and apparently unfixable) issue with CMake. In OpenCV's case, among other potential issues, this leads to broken paths in `opencv_tests_config.hpp`, which can break the build or cause bizarre behaviour. The fix for this issue, as stated in my links above, is to test that the variable hasn't been set yet with an `if(NOT DEFINED ...)` before attempting to set it. This is what I've implemented here. I admit I don't know enough about OpenCV's internals to know whether you *really* need to force the install to be in `/usr/local`, but as it stands right now you get *neither* a clean `/usr/local` path *nor* a customized `/my/desired/install/path`, but a broken mess. This change at least allows the user to customize their install directory. In the meantime, there's a workaround for this, by explicitly defining the variable as a path with `-DCMAKE_INSTALL_PREFIX:PATH=my/desired/install/path`. But if this change can save anyone else the hours of headaches that I had today, I'll be happy. --- CMakeLists.txt | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91fad31958..16e039d324 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,19 +25,21 @@ endif() # Following block can broke build in case of cross-compilng # but CMAKE_CROSSCOMPILING variable will be set only on project(OpenCV) command # so we will try to detect crosscompiling by presense of CMAKE_TOOLCHAIN_FILE -if(NOT CMAKE_TOOLCHAIN_FILE) - # it _must_ go before project(OpenCV) in order to work - if(WIN32) +if(NOT DEFINED CMAKE_INSTALL_PREFIX) + if(NOT CMAKE_TOOLCHAIN_FILE) + # it _must_ go before project(OpenCV) in order to work + if(WIN32) + set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Installation Directory") + else() + set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "Installation Directory") + endif() + else(NOT CMAKE_TOOLCHAIN_FILE) + #Android: set output folder to ${CMAKE_BINARY_DIR} + set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_BINARY_DIR} CACHE PATH "root for library output, set this to change where android libs are compiled to" ) + # any crosscompiling set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Installation Directory") - else() - set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "Installation Directory") - endif() -else(NOT CMAKE_TOOLCHAIN_FILE) - #Android: set output folder to ${CMAKE_BINARY_DIR} - set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_BINARY_DIR} CACHE PATH "root for library output, set this to change where android libs are compiled to" ) - # any crosscompiling - set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Installation Directory") -endif(NOT CMAKE_TOOLCHAIN_FILE) + endif(NOT CMAKE_TOOLCHAIN_FILE) +endif() if(CMAKE_SYSTEM_NAME MATCHES WindowsPhone OR CMAKE_SYSTEM_NAME MATCHES WindowsStore) set(WINRT TRUE) From b76e88354ca6157265e3123760411c7e1c58a39a Mon Sep 17 00:00:00 2001 From: Wenju He Date: Sun, 12 Feb 2017 17:08:05 +0800 Subject: [PATCH 379/409] improve MultiBandBlender cuda, add normalizeUsingWeight and addSrcWeight kernels --- .../opencv2/stitching/detail/blenders.hpp | 4 + modules/stitching/src/blenders.cpp | 202 +++++++++++++++--- modules/stitching/src/cuda/multiband_blend.cu | 112 ++++++++++ 3 files changed, 290 insertions(+), 28 deletions(-) create mode 100644 modules/stitching/src/cuda/multiband_blend.cu diff --git a/modules/stitching/include/opencv2/stitching/detail/blenders.hpp b/modules/stitching/include/opencv2/stitching/detail/blenders.hpp index 4ccaa70e1c..c89e00346a 100644 --- a/modules/stitching/include/opencv2/stitching/detail/blenders.hpp +++ b/modules/stitching/include/opencv2/stitching/detail/blenders.hpp @@ -142,6 +142,10 @@ private: Rect dst_roi_final_; bool can_use_gpu_; int weight_type_; //CV_32F or CV_16S +#if defined(HAVE_OPENCV_CUDAARITHM) && defined(HAVE_OPENCV_CUDAWARPING) + std::vector gpu_dst_pyr_laplace_; + std::vector gpu_dst_band_weights_; +#endif }; diff --git a/modules/stitching/src/blenders.cpp b/modules/stitching/src/blenders.cpp index 1d2fe9e597..dc7aecb08c 100644 --- a/modules/stitching/src/blenders.cpp +++ b/modules/stitching/src/blenders.cpp @@ -43,6 +43,23 @@ #include "precomp.hpp" #include "opencl_kernels_stitching.hpp" +#ifdef HAVE_CUDA + namespace cv { namespace cuda { namespace device + { + namespace blend + { + void addSrcWeightGpu16S(const PtrStep src, const PtrStep src_weight, + PtrStep dst, PtrStep dst_weight, cv::Rect &rc); + void addSrcWeightGpu32F(const PtrStep src, const PtrStepf src_weight, + PtrStep dst, PtrStepf dst_weight, cv::Rect &rc); + void normalizeUsingWeightMapGpu16S(const PtrStep weight, PtrStep src, + const int width, const int height); + void normalizeUsingWeightMapGpu32F(const PtrStepf weight, PtrStep src, + const int width, const int height); + } + }}} +#endif + namespace cv { namespace detail { @@ -228,21 +245,46 @@ void MultiBandBlender::prepare(Rect dst_roi) Blender::prepare(dst_roi); - dst_pyr_laplace_.resize(num_bands_ + 1); - dst_pyr_laplace_[0] = dst_; - - dst_band_weights_.resize(num_bands_ + 1); - dst_band_weights_[0].create(dst_roi.size(), weight_type_); - dst_band_weights_[0].setTo(0); - - for (int i = 1; i <= num_bands_; ++i) +#if defined(HAVE_OPENCV_CUDAARITHM) && defined(HAVE_OPENCV_CUDAWARPING) + if (can_use_gpu_) { - dst_pyr_laplace_[i].create((dst_pyr_laplace_[i - 1].rows + 1) / 2, - (dst_pyr_laplace_[i - 1].cols + 1) / 2, CV_16SC3); - dst_band_weights_[i].create((dst_band_weights_[i - 1].rows + 1) / 2, - (dst_band_weights_[i - 1].cols + 1) / 2, weight_type_); - dst_pyr_laplace_[i].setTo(Scalar::all(0)); - dst_band_weights_[i].setTo(0); + gpu_dst_pyr_laplace_.resize(num_bands_ + 1); + gpu_dst_pyr_laplace_[0].create(dst_roi.size(), CV_16SC3); + gpu_dst_pyr_laplace_[0].setTo(Scalar::all(0)); + + gpu_dst_band_weights_.resize(num_bands_ + 1); + gpu_dst_band_weights_[0].create(dst_roi.size(), weight_type_); + gpu_dst_band_weights_[0].setTo(0); + + for (int i = 1; i <= num_bands_; ++i) + { + gpu_dst_pyr_laplace_[i].create((gpu_dst_pyr_laplace_[i - 1].rows + 1) / 2, + (gpu_dst_pyr_laplace_[i - 1].cols + 1) / 2, CV_16SC3); + gpu_dst_band_weights_[i].create((gpu_dst_band_weights_[i - 1].rows + 1) / 2, + (gpu_dst_band_weights_[i - 1].cols + 1) / 2, weight_type_); + gpu_dst_pyr_laplace_[i].setTo(Scalar::all(0)); + gpu_dst_band_weights_[i].setTo(0); + } + } + else +#endif + { + dst_pyr_laplace_.resize(num_bands_ + 1); + dst_pyr_laplace_[0] = dst_; + + dst_band_weights_.resize(num_bands_ + 1); + dst_band_weights_[0].create(dst_roi.size(), weight_type_); + dst_band_weights_[0].setTo(0); + + for (int i = 1; i <= num_bands_; ++i) + { + dst_pyr_laplace_[i].create((dst_pyr_laplace_[i - 1].rows + 1) / 2, + (dst_pyr_laplace_[i - 1].cols + 1) / 2, CV_16SC3); + dst_band_weights_[i].create((dst_band_weights_[i - 1].rows + 1) / 2, + (dst_band_weights_[i - 1].cols + 1) / 2, weight_type_); + dst_pyr_laplace_[i].setTo(Scalar::all(0)); + dst_band_weights_[i].setTo(0); + } } } @@ -312,6 +354,76 @@ void MultiBandBlender::feed(InputArray _img, InputArray mask, Point tl) int bottom = br_new.y - tl.y - img.rows; int right = br_new.x - tl.x - img.cols; +#if defined(HAVE_OPENCV_CUDAARITHM) && defined(HAVE_OPENCV_CUDAWARPING) + if (can_use_gpu_) + { + // Create the source image Laplacian pyramid + cuda::GpuMat gpu_img; + gpu_img.upload(img); + cuda::GpuMat img_with_border; + cuda::copyMakeBorder(gpu_img, img_with_border, top, bottom, left, right, BORDER_REFLECT); + std::vector gpu_src_pyr_laplace(num_bands_ + 1); + img_with_border.convertTo(gpu_src_pyr_laplace[0], CV_16S); + for (int i = 0; i < num_bands_; ++i) + cuda::pyrDown(gpu_src_pyr_laplace[i], gpu_src_pyr_laplace[i + 1]); + for (int i = 0; i < num_bands_; ++i) + { + cuda::GpuMat up; + cuda::pyrUp(gpu_src_pyr_laplace[i + 1], up); + cuda::subtract(gpu_src_pyr_laplace[i], up, gpu_src_pyr_laplace[i]); + } + + // Create the weight map Gaussian pyramid + cuda::GpuMat gpu_mask; + gpu_mask.upload(mask); + cuda::GpuMat weight_map; + std::vector gpu_weight_pyr_gauss(num_bands_ + 1); + + if (weight_type_ == CV_32F) + { + gpu_mask.convertTo(weight_map, CV_32F, 1. / 255.); + } + else // weight_type_ == CV_16S + { + gpu_mask.convertTo(weight_map, CV_16S); + cuda::GpuMat add_mask; + cuda::compare(gpu_mask, 0, add_mask, CMP_NE); + cuda::add(weight_map, Scalar::all(1), weight_map, add_mask); + } + cuda::copyMakeBorder(weight_map, gpu_weight_pyr_gauss[0], top, bottom, left, right, BORDER_CONSTANT); + for (int i = 0; i < num_bands_; ++i) + cuda::pyrDown(gpu_weight_pyr_gauss[i], gpu_weight_pyr_gauss[i + 1]); + + int y_tl = tl_new.y - dst_roi_.y; + int y_br = br_new.y - dst_roi_.y; + int x_tl = tl_new.x - dst_roi_.x; + int x_br = br_new.x - dst_roi_.x; + + // Add weighted layer of the source image to the final Laplacian pyramid layer + for (int i = 0; i <= num_bands_; ++i) + { + Rect rc(x_tl, y_tl, x_br - x_tl, y_br - y_tl); + cuda::GpuMat &_src_pyr_laplace = gpu_src_pyr_laplace[i]; + cuda::GpuMat _dst_pyr_laplace = gpu_dst_pyr_laplace_[i](rc); + cuda::GpuMat &_weight_pyr_gauss = gpu_weight_pyr_gauss[i]; + cuda::GpuMat _dst_band_weights = gpu_dst_band_weights_[i](rc); + + using namespace cv::cuda::device::blend; + if (weight_type_ == CV_32F) + { + addSrcWeightGpu32F(_src_pyr_laplace, _weight_pyr_gauss, _dst_pyr_laplace, _dst_band_weights, rc); + } + else + { + addSrcWeightGpu16S(_src_pyr_laplace, _weight_pyr_gauss, _dst_pyr_laplace, _dst_band_weights, rc); + } + x_tl /= 2; y_tl /= 2; + x_br /= 2; y_br /= 2; + } + return; + } +#endif + // Create the source image Laplacian pyramid UMat img_with_border; copyMakeBorder(_img, img_with_border, top, bottom, left, right, @@ -322,10 +434,7 @@ void MultiBandBlender::feed(InputArray _img, InputArray mask, Point tl) #endif std::vector src_pyr_laplace; - if (can_use_gpu_ && img_with_border.depth() == CV_16S) - createLaplacePyrGpu(img_with_border, num_bands_, src_pyr_laplace); - else - createLaplacePyr(img_with_border, num_bands_, src_pyr_laplace); + createLaplacePyr(img_with_border, num_bands_, src_pyr_laplace); LOGLN(" Create the source image Laplacian pyramid, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); #if ENABLE_LOG @@ -431,20 +540,57 @@ void MultiBandBlender::feed(InputArray _img, InputArray mask, Point tl) void MultiBandBlender::blend(InputOutputArray dst, InputOutputArray dst_mask) { - for (int i = 0; i <= num_bands_; ++i) - normalizeUsingWeightMap(dst_band_weights_[i], dst_pyr_laplace_[i]); - + cv::UMat dst_band_weights_0; + Rect dst_rc(0, 0, dst_roi_final_.width, dst_roi_final_.height); +#if defined(HAVE_OPENCV_CUDAARITHM) && defined(HAVE_OPENCV_CUDAWARPING) if (can_use_gpu_) - restoreImageFromLaplacePyrGpu(dst_pyr_laplace_); + { + for (int i = 0; i <= num_bands_; ++i) + { + cuda::GpuMat dst_i = gpu_dst_pyr_laplace_[i]; + cuda::GpuMat weight_i = gpu_dst_band_weights_[i]; + + using namespace ::cv::cuda::device::blend; + if (weight_type_ == CV_32F) + { + normalizeUsingWeightMapGpu32F(weight_i, dst_i, weight_i.cols, weight_i.rows); + } + else + { + normalizeUsingWeightMapGpu16S(weight_i, dst_i, weight_i.cols, weight_i.rows); + } + } + + // Restore image from Laplacian pyramid + for (size_t i = num_bands_; i > 0; --i) + { + cuda::GpuMat up; + cuda::pyrUp(gpu_dst_pyr_laplace_[i], up); + cuda::add(up, gpu_dst_pyr_laplace_[i - 1], gpu_dst_pyr_laplace_[i - 1]); + } + + gpu_dst_pyr_laplace_[0](dst_rc).download(dst_); + gpu_dst_band_weights_[0].download(dst_band_weights_0); + + gpu_dst_pyr_laplace_.clear(); + gpu_dst_band_weights_.clear(); + } else +#endif + { + for (int i = 0; i <= num_bands_; ++i) + normalizeUsingWeightMap(dst_band_weights_[i], dst_pyr_laplace_[i]); + restoreImageFromLaplacePyr(dst_pyr_laplace_); - Rect dst_rc(0, 0, dst_roi_final_.width, dst_roi_final_.height); - dst_ = dst_pyr_laplace_[0](dst_rc); - UMat _dst_mask; - compare(dst_band_weights_[0](dst_rc), WEIGHT_EPS, dst_mask_, CMP_GT); - dst_pyr_laplace_.clear(); - dst_band_weights_.clear(); + dst_ = dst_pyr_laplace_[0](dst_rc); + dst_band_weights_0 = dst_band_weights_[0]; + + dst_pyr_laplace_.clear(); + dst_band_weights_.clear(); + } + + compare(dst_band_weights_0(dst_rc), WEIGHT_EPS, dst_mask_, CMP_GT); Blender::blend(dst, dst_mask); } diff --git a/modules/stitching/src/cuda/multiband_blend.cu b/modules/stitching/src/cuda/multiband_blend.cu new file mode 100644 index 0000000000..daa0005ec0 --- /dev/null +++ b/modules/stitching/src/cuda/multiband_blend.cu @@ -0,0 +1,112 @@ +#if !defined CUDA_DISABLER + +#include "opencv2/core/cuda/common.hpp" +#include "opencv2/core/types.hpp" + +namespace cv { namespace cuda { namespace device +{ + namespace blend + { + __global__ void addSrcWeightKernel16S(const PtrStep src, const PtrStep src_weight, + PtrStep dst, PtrStep dst_weight, int rows, int cols) + { + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + + if (y < rows && x < cols) + { + const short3 v = ((const short3*)src.ptr(y))[x]; + short w = src_weight.ptr(y)[x]; + ((short3*)dst.ptr(y))[x].x += short((v.x * w) >> 8); + ((short3*)dst.ptr(y))[x].y += short((v.y * w) >> 8); + ((short3*)dst.ptr(y))[x].z += short((v.z * w) >> 8); + dst_weight.ptr(y)[x] += w; + } + } + + void addSrcWeightGpu16S(const PtrStep src, const PtrStep src_weight, + PtrStep dst, PtrStep dst_weight, cv::Rect &rc) + { + dim3 threads(16, 16); + dim3 grid(divUp(rc.width, threads.x), divUp(rc.height, threads.y)); + addSrcWeightKernel16S<<>>(src, src_weight, dst, dst_weight, rc.height, rc.width); + cudaSafeCall(cudaGetLastError()); + } + + __global__ void addSrcWeightKernel32F(const PtrStep src, const PtrStepf src_weight, + PtrStep dst, PtrStepf dst_weight, int rows, int cols) + { + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + + if (y < rows && x < cols) + { + const short3 v = ((const short3*)src.ptr(y))[x]; + float w = src_weight.ptr(y)[x]; + ((short3*)dst.ptr(y))[x].x += static_cast(v.x * w); + ((short3*)dst.ptr(y))[x].y += static_cast(v.y * w); + ((short3*)dst.ptr(y))[x].z += static_cast(v.z * w); + dst_weight.ptr(y)[x] += w; + } + } + + void addSrcWeightGpu32F(const PtrStep src, const PtrStepf src_weight, + PtrStep dst, PtrStepf dst_weight, cv::Rect &rc) + { + dim3 threads(16, 16); + dim3 grid(divUp(rc.width, threads.x), divUp(rc.height, threads.y)); + addSrcWeightKernel32F<<>>(src, src_weight, dst, dst_weight, rc.height, rc.width); + cudaSafeCall(cudaGetLastError()); + } + + __global__ void normalizeUsingWeightKernel16S(const PtrStep weight, PtrStep src, + const int width, const int height) + { + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + + if (x < width && y < height) + { + const short3 v = ((short3*)src.ptr(y))[x]; + short w = weight.ptr(y)[x]; + ((short3*)src.ptr(y))[x] = make_short3(short((v.x << 8) / w), + short((v.y << 8) / w), short((v.z << 8) / w)); + } + } + + void normalizeUsingWeightMapGpu16S(const PtrStep weight, PtrStep src, + const int width, const int height) + { + dim3 threads(16, 16); + dim3 grid(divUp(width, threads.x), divUp(height, threads.y)); + normalizeUsingWeightKernel16S<<>> (weight, src, width, height); + } + + __global__ void normalizeUsingWeightKernel32F(const PtrStepf weight, PtrStep src, + const int width, const int height) + { + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + + if (x < width && y < height) + { + static const float WEIGHT_EPS = 1e-5f; + const short3 v = ((short3*)src.ptr(y))[x]; + float w = weight.ptr(y)[x]; + ((short3*)src.ptr(y))[x] = make_short3(static_cast(v.x / (w + WEIGHT_EPS)), + static_cast(v.y / (w + WEIGHT_EPS)), + static_cast(v.z / (w + WEIGHT_EPS))); + } + } + + void normalizeUsingWeightMapGpu32F(const PtrStepf weight, PtrStep src, + const int width, const int height) + { + dim3 threads(16, 16); + dim3 grid(divUp(width, threads.x), divUp(height, threads.y)); + normalizeUsingWeightKernel32F<<>> (weight, src, width, height); + } + } +}}} + +#endif From 272213320603052ba756e14bfe95e037df699de0 Mon Sep 17 00:00:00 2001 From: Wenju He Date: Sun, 12 Feb 2017 17:16:49 +0800 Subject: [PATCH 380/409] add accuracy test CUDA_MultiBandBlender --- modules/stitching/test/test_blenders.cuda.cpp | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 modules/stitching/test/test_blenders.cuda.cpp diff --git a/modules/stitching/test/test_blenders.cuda.cpp b/modules/stitching/test/test_blenders.cuda.cpp new file mode 100644 index 0000000000..1b60838b91 --- /dev/null +++ b/modules/stitching/test/test_blenders.cuda.cpp @@ -0,0 +1,93 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "test_precomp.hpp" +#include "opencv2/ts/cuda_test.hpp" + +#if defined(HAVE_OPENCV_CUDAARITHM) && defined(HAVE_OPENCV_CUDAWARPING) + +using namespace cv; +using namespace std; + +namespace +{ + void multiBandBlend(const cv::Mat& im1, const cv::Mat& im2, const cv::Mat& mask1, const cv::Mat& mask2, cv::Mat& result, bool try_cuda) + { + detail::MultiBandBlender blender(try_cuda, 5); + + blender.prepare(Rect(0, 0, max(im1.cols, im2.cols), max(im1.rows, im2.rows))); + blender.feed(im1, mask1, Point(0,0)); + blender.feed(im2, mask2, Point(0,0)); + + Mat result_s, result_mask; + blender.blend(result_s, result_mask); + result_s.convertTo(result, CV_8U); + } +} + +TEST(CUDA_MultiBandBlender, Accuracy) +{ + Mat image1 = imread(string(cvtest::TS::ptr()->get_data_path()) + "cv/shared/baboon.png"); + Mat image2 = imread(string(cvtest::TS::ptr()->get_data_path()) + "cv/shared/lena.png"); + ASSERT_EQ(image1.rows, image2.rows); ASSERT_EQ(image1.cols, image2.cols); + + Mat image1s, image2s; + image1.convertTo(image1s, CV_16S); + image2.convertTo(image2s, CV_16S); + + Mat mask1(image1s.size(), CV_8U); + mask1(Rect(0, 0, mask1.cols/2, mask1.rows)).setTo(255); + mask1(Rect(mask1.cols/2, 0, mask1.cols - mask1.cols/2, mask1.rows)).setTo(0); + + Mat mask2(image2s.size(), CV_8U); + mask2(Rect(0, 0, mask2.cols/2, mask2.rows)).setTo(0); + mask2(Rect(mask2.cols/2, 0, mask2.cols - mask2.cols/2, mask2.rows)).setTo(255); + + cv::Mat result; + multiBandBlend(image1s, image2s, mask1, mask2, result, false); + + cv::Mat result_cuda; + multiBandBlend(image1s, image2s, mask1, mask2, result_cuda, true); + + EXPECT_MAT_NEAR(result, result_cuda, 3); +} + +#endif From 9552206a4c49e6e6cd88b9ab2382b920759dcfe2 Mon Sep 17 00:00:00 2001 From: atinfinity Date: Sun, 12 Feb 2017 23:33:57 +0900 Subject: [PATCH 381/409] added option to specify flow type --- samples/gpu/pyrlk_optical_flow.cpp | 98 ++++++++++++++++++------------ 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/samples/gpu/pyrlk_optical_flow.cpp b/samples/gpu/pyrlk_optical_flow.cpp index 315301b6e3..11c8b731a0 100644 --- a/samples/gpu/pyrlk_optical_flow.cpp +++ b/samples/gpu/pyrlk_optical_flow.cpp @@ -207,15 +207,16 @@ template inline T mapValue(T x, T a, T b, T c, T d) int main(int argc, const char* argv[]) { const char* keys = - "{ h help | | print help message }" + "{ h help | | print help message }" "{ l left | ../data/pic1.png | specify left image }" "{ r right | ../data/pic2.png | specify right image }" - "{ gray | | use grayscale sources [PyrLK Sparse] }" - "{ win_size | 21 | specify windows size [PyrLK] }" - "{ max_level | 3 | specify max level [PyrLK] }" - "{ iters | 30 | specify iterations count [PyrLK] }" - "{ points | 4000 | specify points count [GoodFeatureToTrack] }" - "{ min_dist | 0 | specify minimal distance between points [GoodFeatureToTrack] }"; + "{ flow | sparse | specify flow type [PyrLK] }" + "{ gray | | use grayscale sources [PyrLK Sparse] }" + "{ win_size | 21 | specify windows size [PyrLK] }" + "{ max_level | 3 | specify max level [PyrLK] }" + "{ iters | 30 | specify iterations count [PyrLK] }" + "{ points | 4000 | specify points count [GoodFeatureToTrack] }" + "{ min_dist | 0 | specify minimal distance between points [GoodFeatureToTrack] }"; CommandLineParser cmd(argc, argv, keys); @@ -235,6 +236,22 @@ int main(int argc, const char* argv[]) return -1; } + string flow_type = cmd.get("flow"); + bool is_sparse = true; + if (flow_type == "sparse") + { + is_sparse = true; + } + else if (flow_type == "dense") + { + is_sparse = false; + } + else + { + cerr << "please specify 'sparse' or 'dense' as flow type" << endl; + return -1; + } + bool useGray = cmd.has("gray"); int winSize = cmd.get("win_size"); int maxLevel = cmd.get("max_level"); @@ -251,8 +268,14 @@ int main(int argc, const char* argv[]) return -1; } - namedWindow("PyrLK [Sparse]", WINDOW_NORMAL); - namedWindow("PyrLK [Dense] Flow Field", WINDOW_NORMAL); + if(is_sparse) + { + namedWindow("PyrLK [Sparse]", WINDOW_NORMAL); + } + else + { + namedWindow("PyrLK [Dense] Flow Field", WINDOW_NORMAL); + } cout << "Image size : " << frame0.cols << " x " << frame0.rows << endl; cout << "Points count : " << points << endl; @@ -265,55 +288,50 @@ int main(int argc, const char* argv[]) cv::cvtColor(frame1, frame1Gray, COLOR_BGR2GRAY); // goodFeaturesToTrack - GpuMat d_frame0Gray(frame0Gray); GpuMat d_prevPts; Ptr detector = cuda::createGoodFeaturesToTrackDetector(d_frame0Gray.type(), points, 0.01, minDist); - detector->detect(d_frame0Gray, d_prevPts); - // Sparse - - Ptr d_pyrLK_sparse = cuda::SparsePyrLKOpticalFlow::create( - Size(winSize, winSize), maxLevel, iters); - GpuMat d_frame0(frame0); GpuMat d_frame1(frame1); GpuMat d_frame1Gray(frame1Gray); GpuMat d_nextPts; GpuMat d_status; - - d_pyrLK_sparse->calc(useGray ? d_frame0Gray : d_frame0, useGray ? d_frame1Gray : d_frame1, d_prevPts, d_nextPts, d_status); - - // Dense - - Ptr d_pyrLK_dense = cuda::DensePyrLKOpticalFlow::create( - Size(winSize, winSize), maxLevel, iters); - GpuMat d_flow(frame0.size(), CV_32FC2); - d_pyrLK_dense->calc(d_frame0Gray, d_frame1Gray, d_flow); + if (is_sparse) + { + // Sparse + Ptr d_pyrLK_sparse = cuda::SparsePyrLKOpticalFlow::create( + Size(winSize, winSize), maxLevel, iters); + d_pyrLK_sparse->calc(useGray ? d_frame0Gray : d_frame0, useGray ? d_frame1Gray : d_frame1, d_prevPts, d_nextPts, d_status); - // Draw arrows + // Draw arrows + vector prevPts(d_prevPts.cols); + download(d_prevPts, prevPts); - vector prevPts(d_prevPts.cols); - download(d_prevPts, prevPts); + vector nextPts(d_nextPts.cols); + download(d_nextPts, nextPts); - vector nextPts(d_nextPts.cols); - download(d_nextPts, nextPts); + vector status(d_status.cols); + download(d_status, status); - vector status(d_status.cols); - download(d_status, status); + drawArrows(frame0, prevPts, nextPts, status, Scalar(255, 0, 0)); + imshow("PyrLK [Sparse]", frame0); + } + else { + // Dense + Ptr d_pyrLK_dense = cuda::DensePyrLKOpticalFlow::create( + Size(winSize, winSize), maxLevel, iters); + d_pyrLK_dense->calc(d_frame0Gray, d_frame1Gray, d_flow); - drawArrows(frame0, prevPts, nextPts, status, Scalar(255, 0, 0)); - imshow("PyrLK [Sparse]", frame0); + // Draw flows + showFlow("PyrLK [Dense] Flow Field", d_flow); + } - // Draw flows - - showFlow("PyrLK [Dense] Flow Field", d_flow); - - waitKey(); + waitKey(0); return 0; -} +} \ No newline at end of file From c2a880d7c9aa2170baa6a476f2632ead935a0403 Mon Sep 17 00:00:00 2001 From: atinfinity Date: Mon, 13 Feb 2017 00:26:48 +0900 Subject: [PATCH 382/409] fixed indent --- samples/gpu/pyrlk_optical_flow.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/gpu/pyrlk_optical_flow.cpp b/samples/gpu/pyrlk_optical_flow.cpp index 11c8b731a0..096fe86bee 100644 --- a/samples/gpu/pyrlk_optical_flow.cpp +++ b/samples/gpu/pyrlk_optical_flow.cpp @@ -269,7 +269,7 @@ int main(int argc, const char* argv[]) } if(is_sparse) - { + { namedWindow("PyrLK [Sparse]", WINDOW_NORMAL); } else @@ -321,7 +321,8 @@ int main(int argc, const char* argv[]) drawArrows(frame0, prevPts, nextPts, status, Scalar(255, 0, 0)); imshow("PyrLK [Sparse]", frame0); } - else { + else + { // Dense Ptr d_pyrLK_dense = cuda::DensePyrLKOpticalFlow::create( Size(winSize, winSize), maxLevel, iters); From bbe0f2cef53ae460ffaf27d5d1dff22c1b5843a0 Mon Sep 17 00:00:00 2001 From: atinfinity Date: Mon, 13 Feb 2017 00:35:52 +0900 Subject: [PATCH 383/409] refactoring --- samples/gpu/pyrlk_optical_flow.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/samples/gpu/pyrlk_optical_flow.cpp b/samples/gpu/pyrlk_optical_flow.cpp index 096fe86bee..e540fa736d 100644 --- a/samples/gpu/pyrlk_optical_flow.cpp +++ b/samples/gpu/pyrlk_optical_flow.cpp @@ -268,15 +268,6 @@ int main(int argc, const char* argv[]) return -1; } - if(is_sparse) - { - namedWindow("PyrLK [Sparse]", WINDOW_NORMAL); - } - else - { - namedWindow("PyrLK [Dense] Flow Field", WINDOW_NORMAL); - } - cout << "Image size : " << frame0.cols << " x " << frame0.rows << endl; cout << "Points count : " << points << endl; @@ -318,6 +309,7 @@ int main(int argc, const char* argv[]) vector status(d_status.cols); download(d_status, status); + namedWindow("PyrLK [Sparse]", WINDOW_NORMAL); drawArrows(frame0, prevPts, nextPts, status, Scalar(255, 0, 0)); imshow("PyrLK [Sparse]", frame0); } @@ -329,6 +321,7 @@ int main(int argc, const char* argv[]) d_pyrLK_dense->calc(d_frame0Gray, d_frame1Gray, d_flow); // Draw flows + namedWindow("PyrLK [Dense] Flow Field", WINDOW_NORMAL); showFlow("PyrLK [Dense] Flow Field", d_flow); } From ca5e0f53b826a1ee5ff61a0755aaa6a7e375dac8 Mon Sep 17 00:00:00 2001 From: atinfinity Date: Mon, 13 Feb 2017 00:53:35 +0900 Subject: [PATCH 384/409] refactoring --- samples/gpu/pyrlk_optical_flow.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/samples/gpu/pyrlk_optical_flow.cpp b/samples/gpu/pyrlk_optical_flow.cpp index e540fa736d..fa6b376362 100644 --- a/samples/gpu/pyrlk_optical_flow.cpp +++ b/samples/gpu/pyrlk_optical_flow.cpp @@ -1,14 +1,14 @@ #include #include -#include "opencv2/core.hpp" -#include "opencv2/core/utility.hpp" -#include "opencv2/imgproc.hpp" -#include "opencv2/highgui.hpp" -#include "opencv2/video.hpp" -#include "opencv2/cudaoptflow.hpp" -#include "opencv2/cudaimgproc.hpp" -#include "opencv2/cudaarithm.hpp" +#include +#include +#include +#include +#include +#include +#include +#include using namespace std; using namespace cv; From 1e11657ba495fb6a002d29945547e0dd71b90b2b Mon Sep 17 00:00:00 2001 From: Fangjun KUANG Date: Mon, 13 Feb 2017 11:58:44 +0100 Subject: [PATCH 385/409] Merge pull request #8197 from csukuangfj/csukuangfj-patch-1 Fix typos in the documentation for AutoBuffer. (#8197) * Allocate 1000 floats to match the documentation Fix the documentation of `AutoBuffer`. By default, the following code ```.cpp cv::AutoBuffer m; ```` allocates only 264 floats. But the comment in the demonstration code says it allocates 1000 floats, which is not correct. * fix typo in the comment. --- modules/core/include/opencv2/core/utility.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/core/include/opencv2/core/utility.hpp b/modules/core/include/opencv2/core/utility.hpp index e7a7c2d789..dab039c4b9 100644 --- a/modules/core/include/opencv2/core/utility.hpp +++ b/modules/core/include/opencv2/core/utility.hpp @@ -102,7 +102,7 @@ CV_EXPORTS void setUseCollection(bool flag); // set implementation collection st \code void my_func(const cv::Mat& m) { - cv::AutoBuffer buf; // create automatic buffer containing 1000 floats + cv::AutoBuffer buf(1000); // create automatic buffer containing 1000 floats buf.allocate(m.rows); // if m.rows <= 1000, the pre-allocated buffer is used, // otherwise the buffer of "m.rows" floats will be allocated @@ -137,9 +137,9 @@ public: void resize(size_t _size); //! returns the current buffer size size_t size() const; - //! returns pointer to the real buffer, stack-allocated or head-allocated + //! returns pointer to the real buffer, stack-allocated or heap-allocated operator _Tp* (); - //! returns read-only pointer to the real buffer, stack-allocated or head-allocated + //! returns read-only pointer to the real buffer, stack-allocated or heap-allocated operator const _Tp* () const; protected: @@ -147,7 +147,7 @@ protected: _Tp* ptr; //! size of the real buffer size_t sz; - //! pre-allocated buffer. At least 1 element to confirm C++ standard reqirements + //! pre-allocated buffer. At least 1 element to confirm C++ standard requirements _Tp buf[(fixed_size > 0) ? fixed_size : 1]; }; From 4c7aa8645abd2ce9dc1e64088febb778b175c49e Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 14 Feb 2017 14:58:52 +0300 Subject: [PATCH 386/409] ocl: validate arguments in KernelArgs constructor - don't use undefined flag=0. It should be CONSTANT instead. - don't allow 'UMat* m=NULL' argument (except LOCAL/CONSTANT flags). This case is not handled well to provide NULL __global pointers. It is better to use '-D' macro defines instead (at least for performance) --- modules/core/src/arithm.cpp | 16 ++++++++-------- modules/core/src/ocl.cpp | 1 + modules/core/src/umatrix.cpp | 2 +- modules/imgproc/src/imgwarp.cpp | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/modules/core/src/arithm.cpp b/modules/core/src/arithm.cpp index 4b1c67339b..9c2c126ffb 100644 --- a/modules/core/src/arithm.cpp +++ b/modules/core/src/arithm.cpp @@ -138,7 +138,7 @@ static bool ocl_binary_op(InputArray _src1, InputArray _src2, OutputArray _dst, convertAndUnrollScalar(src2sc, srctype, (uchar*)buf, 1); } - ocl::KernelArg scalararg = ocl::KernelArg(0, 0, 0, 0, buf, esz); + ocl::KernelArg scalararg = ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, buf, esz); if( !haveMask ) k.args(src1arg, dstarg, scalararg); @@ -550,7 +550,7 @@ static bool ocl_arithm_op(InputArray _src1, InputArray _src2, OutputArray _dst, if( !src2sc.empty() ) convertAndUnrollScalar(src2sc, wtype, (uchar*)buf, 1); - ocl::KernelArg scalararg = ocl::KernelArg(0, 0, 0, 0, buf, esz); + ocl::KernelArg scalararg = ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, buf, esz); if( !haveMask ) { @@ -558,7 +558,7 @@ static bool ocl_arithm_op(InputArray _src1, InputArray _src2, OutputArray _dst, k.args(src1arg, dstarg, scalararg); else if(n == 1) k.args(src1arg, dstarg, scalararg, - ocl::KernelArg(0, 0, 0, 0, usrdata_p, usrdata_esz)); + ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, usrdata_p, usrdata_esz)); else CV_Error(Error::StsNotImplemented, "unsupported number of extra parameters"); } @@ -576,12 +576,12 @@ static bool ocl_arithm_op(InputArray _src1, InputArray _src2, OutputArray _dst, k.args(src1arg, src2arg, dstarg); else if (n == 1) k.args(src1arg, src2arg, dstarg, - ocl::KernelArg(0, 0, 0, 0, usrdata_p, usrdata_esz)); + ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, usrdata_p, usrdata_esz)); else if (n == 3) k.args(src1arg, src2arg, dstarg, - ocl::KernelArg(0, 0, 0, 0, usrdata_p, usrdata_esz), - ocl::KernelArg(0, 0, 0, 0, usrdata_p + usrdata_esz, usrdata_esz), - ocl::KernelArg(0, 0, 0, 0, usrdata_p + usrdata_esz*2, usrdata_esz)); + ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, usrdata_p, usrdata_esz), + ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, usrdata_p + usrdata_esz, usrdata_esz), + ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, usrdata_p + usrdata_esz*2, usrdata_esz)); else CV_Error(Error::StsNotImplemented, "unsupported number of extra parameters"); } @@ -1204,7 +1204,7 @@ static bool ocl_compare(InputArray _src1, InputArray _src2, OutputArray _dst, in convertAndUnrollScalar(Mat(1, 1, CV_32S, &ival), depth1, (uchar *)buf, kercn); } - ocl::KernelArg scalararg = ocl::KernelArg(0, 0, 0, 0, buf, esz); + ocl::KernelArg scalararg = ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, buf, esz); k.args(ocl::KernelArg::ReadOnlyNoSize(src1, cn, kercn), ocl::KernelArg::WriteOnly(dst, cn, kercn), scalararg); diff --git a/modules/core/src/ocl.cpp b/modules/core/src/ocl.cpp index 60ecc69546..6197e5716d 100644 --- a/modules/core/src/ocl.cpp +++ b/modules/core/src/ocl.cpp @@ -3155,6 +3155,7 @@ KernelArg::KernelArg() KernelArg::KernelArg(int _flags, UMat* _m, int _wscale, int _iwscale, const void* _obj, size_t _sz) : flags(_flags), m(_m), obj(_obj), sz(_sz), wscale(_wscale), iwscale(_iwscale) { + CV_Assert(_flags == LOCAL || _flags == CONSTANT || _m != NULL); } KernelArg KernelArg::Constant(const Mat& m) diff --git a/modules/core/src/umatrix.cpp b/modules/core/src/umatrix.cpp index 5874be82d3..dd5b9a0c2c 100644 --- a/modules/core/src/umatrix.cpp +++ b/modules/core/src/umatrix.cpp @@ -986,7 +986,7 @@ UMat& UMat::setTo(InputArray _value, InputArray _mask) ocl::Kernel setK(haveMask ? "setMask" : "set", ocl::core::copyset_oclsrc, opts); if( !setK.empty() ) { - ocl::KernelArg scalararg(0, 0, 0, 0, buf, CV_ELEM_SIZE(d) * scalarcn); + ocl::KernelArg scalararg(ocl::KernelArg::CONSTANT, 0, 0, 0, buf, CV_ELEM_SIZE(d) * scalarcn); UMat mask; if( haveMask ) diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 0fa520228e..2f0f2e9d4e 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -5814,7 +5814,7 @@ static bool ocl_warpTransform_cols4(InputArray _src, OutputArray _dst, InputArra matM.convertTo(M0, CV_32F); k.args(ocl::KernelArg::ReadOnly(src), ocl::KernelArg::WriteOnly(dst), ocl::KernelArg::PtrReadOnly(M0), - ocl::KernelArg(0, 0, 0, 0, borderBuf, CV_ELEM_SIZE(sctype))); + ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, borderBuf, CV_ELEM_SIZE(sctype))); size_t globalThreads[2]; globalThreads[0] = (size_t)(dst.cols / 4); @@ -5913,7 +5913,7 @@ static bool ocl_warpTransform(InputArray _src, OutputArray _dst, InputArray _M0, matM.convertTo(M0, doubleSupport ? CV_64F : CV_32F); k.args(ocl::KernelArg::ReadOnly(src), ocl::KernelArg::WriteOnly(dst), ocl::KernelArg::PtrReadOnly(M0), - ocl::KernelArg(0, 0, 0, 0, borderBuf, CV_ELEM_SIZE(sctype))); + ocl::KernelArg(ocl::KernelArg::CONSTANT, 0, 0, 0, borderBuf, CV_ELEM_SIZE(sctype))); size_t globalThreads[2] = { (size_t)dst.cols, ((size_t)dst.rows + rowsPerWI - 1) / rowsPerWI }; return k.run(2, globalThreads, NULL, false); From df86f0752ab87cb5cc26f2a7df6bf805b508e6fd Mon Sep 17 00:00:00 2001 From: Pavel Rojtberg Date: Wed, 15 Feb 2017 12:18:35 +0100 Subject: [PATCH 387/409] add missing casts to _Tp as determinant() always returns double --- modules/core/include/opencv2/core/operations.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/operations.hpp b/modules/core/include/opencv2/core/operations.hpp index 4a4ad9e182..f69790f069 100644 --- a/modules/core/include/opencv2/core/operations.hpp +++ b/modules/core/include/opencv2/core/operations.hpp @@ -82,7 +82,7 @@ template struct Matx_FastInvOp<_Tp, 2> { bool operator()(const Matx<_Tp, 2, 2>& a, Matx<_Tp, 2, 2>& b, int) const { - _Tp d = determinant(a); + _Tp d = (_Tp)determinant(a); if( d == 0 ) return false; d = 1/d; @@ -137,7 +137,7 @@ template struct Matx_FastSolveOp<_Tp, 2, 1> bool operator()(const Matx<_Tp, 2, 2>& a, const Matx<_Tp, 2, 1>& b, Matx<_Tp, 2, 1>& x, int) const { - _Tp d = determinant(a); + _Tp d = (_Tp)determinant(a); if( d == 0 ) return false; d = 1/d; From 46fe74177dbeaeca8d0cec7e3761ddc45b4b0650 Mon Sep 17 00:00:00 2001 From: Fangjun KUANG Date: Wed, 15 Feb 2017 14:52:00 +0100 Subject: [PATCH 388/409] Fix typos. --- modules/core/include/opencv2/core/base.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/core/include/opencv2/core/base.hpp b/modules/core/include/opencv2/core/base.hpp index 3705ac9eb8..b319df6f38 100644 --- a/modules/core/include/opencv2/core/base.hpp +++ b/modules/core/include/opencv2/core/base.hpp @@ -66,7 +66,7 @@ namespace cv namespace Error { //! error codes enum Code { - StsOk= 0, //!< everithing is ok + StsOk= 0, //!< everything is ok StsBackTrace= -1, //!< pseudo error for back trace StsError= -2, //!< unknown /unspecified error StsInternal= -3, //!< internal error (bad state) @@ -97,7 +97,7 @@ enum Code { StsVecLengthErr= -28, //!< incorrect vector length StsFilterStructContentErr= -29, //!< incorr. filter structure content StsKernelStructContentErr= -30, //!< incorr. transform kernel content - StsFilterOffsetErr= -31, //!< incorrect filter ofset value + StsFilterOffsetErr= -31, //!< incorrect filter offset value StsBadSize= -201, //!< the input/output structure size is incorrect StsDivByZero= -202, //!< division by zero StsInplaceNotSupported= -203, //!< in-place operation is not supported @@ -338,8 +338,8 @@ It is possible to alternate error processing by using redirectError(). @param _code - error code (Error::Code) @param _err - error description @param _func - function name. Available only when the compiler supports getting it -@param _file - source file name where the error has occured -@param _line - line number in the source file where the error has occured +@param _file - source file name where the error has occurred +@param _line - line number in the source file where the error has occurred @see CV_Error, CV_Error_, CV_ErrorNoReturn, CV_ErrorNoReturn_, CV_Assert, CV_DbgAssert */ CV_EXPORTS void error(int _code, const String& _err, const char* _func, const char* _file, int _line); From 05248a3dea893ba8683558b90854196ab3cfa5d0 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 15 Feb 2017 17:12:28 +0300 Subject: [PATCH 389/409] cmake: fix order of MKL libraries, don't pass additional libraries as modules --- cmake/OpenCVFindMKL.cmake | 6 +++--- modules/core/CMakeLists.txt | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmake/OpenCVFindMKL.cmake b/cmake/OpenCVFindMKL.cmake index 0f5462acd2..fd95dcb446 100644 --- a/cmake/OpenCVFindMKL.cmake +++ b/cmake/OpenCVFindMKL.cmake @@ -96,9 +96,7 @@ if(${MKL_VERSION_STR} VERSION_GREATER "11.3.0" OR ${MKL_VERSION_STR} VERSION_EQU ${MKL_ROOT_DIR}/lib ${MKL_ROOT_DIR}/lib/${MKL_ARCH} ${MKL_ROOT_DIR}/../tbb/lib/${MKL_ARCH}) - set(mkl_lib_list - mkl_core - mkl_intel_${MKL_ARCH_SUFFIX}) + set(mkl_lib_list "mkl_intel_${MKL_ARCH_SUFFIX}") if(MKL_WITH_TBB) list(APPEND mkl_lib_list mkl_tbb_thread tbb) @@ -111,6 +109,8 @@ if(${MKL_VERSION_STR} VERSION_GREATER "11.3.0" OR ${MKL_VERSION_STR} VERSION_EQU else() list(APPEND mkl_lib_list mkl_sequential) endif() + + list(APPEND mkl_lib_list mkl_core) else() message(STATUS "MKL version ${MKL_VERSION_STR} is not supported") mkl_fail() diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index 41da825457..01b272e97e 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -1,7 +1,6 @@ set(the_description "The Core Functionality") ocv_add_module(core "${OPENCV_HAL_LINKER_LIBS}" - PRIVATE_REQUIRED ${ZLIB_LIBRARIES} "${OPENCL_LIBRARIES}" "${VA_LIBRARIES}" "${LAPACK_LIBRARIES}" OPTIONAL opencv_cudev WRAP java python) @@ -33,5 +32,7 @@ ocv_glob_module_sources(SOURCES "${OPENCV_MODULE_opencv_core_BINARY_DIR}/version ocv_module_include_directories(${the_module} ${ZLIB_INCLUDE_DIRS} ${OPENCL_INCLUDE_DIRS}) ocv_create_module(${extra_libs}) +ocv_target_link_libraries(${the_module} ${ZLIB_LIBRARIES} "${OPENCL_LIBRARIES}" "${VA_LIBRARIES}" "${LAPACK_LIBRARIES}") + ocv_add_accuracy_tests() ocv_add_perf_tests() From 5e4dfb954688271f87ac9676a6eaad6622ce31ee Mon Sep 17 00:00:00 2001 From: Fangjun KUANG Date: Wed, 15 Feb 2017 16:44:40 +0100 Subject: [PATCH 390/409] Change the argument type to cl_platform_info. Although both `cl_platform_info` and `cl_device_info` are defined as macro `cl_uint`, it needs to use `cl_platform_info` to get the platform information. --- modules/core/src/ocl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/ocl.cpp b/modules/core/src/ocl.cpp index 60ecc69546..dfa1adb997 100644 --- a/modules/core/src/ocl.cpp +++ b/modules/core/src/ocl.cpp @@ -5528,7 +5528,7 @@ struct PlatformInfo::Impl getDevices(devices, handle); } - String getStrProp(cl_device_info prop) const + String getStrProp(cl_platform_info prop) const { char buf[1024]; size_t sz=0; From a8a208e0fee106ab03ed4f2777320a20bdba8411 Mon Sep 17 00:00:00 2001 From: Fangjun KUANG Date: Wed, 15 Feb 2017 19:50:14 +0100 Subject: [PATCH 391/409] Merge pull request #8208 from csukuangfj:complex_support Add support for printing out cv::Complex. (#8208) * Add support for printing out cv::Complex. * Conform to the format of std::complex. * Remove extra spaces. * Remove extra spaces. --- modules/core/include/opencv2/core/cvstd.inl.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/core/include/opencv2/core/cvstd.inl.hpp b/modules/core/include/opencv2/core/cvstd.inl.hpp index 876def8254..55e35fe69b 100644 --- a/modules/core/include/opencv2/core/cvstd.inl.hpp +++ b/modules/core/include/opencv2/core/cvstd.inl.hpp @@ -183,6 +183,12 @@ std::ostream& operator << (std::ostream& out, const Mat& mtx) return out << Formatter::get()->format(mtx); } +template static inline +std::ostream& operator << (std::ostream& out, const Complex<_Tp>& c) +{ + return out << "(" << c.re << "," << c.im << ")"; +} + template static inline std::ostream& operator << (std::ostream& out, const std::vector >& vec) { From f710d966020b62fd6d5af1e97c844bbe41abf287 Mon Sep 17 00:00:00 2001 From: Jinay Patel Date: Thu, 16 Feb 2017 15:20:57 +0530 Subject: [PATCH 392/409] Update ocl.cpp function Device::OpenCLVersion() Instead of CL_DEVICE_EXTENSIONS in Querying OpenCLVersion, it should be CL_DEVICE_VERSION flag which needs to be passed to the function. --- modules/core/src/ocl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/ocl.cpp b/modules/core/src/ocl.cpp index dfa1adb997..f38571ac4d 100644 --- a/modules/core/src/ocl.cpp +++ b/modules/core/src/ocl.cpp @@ -1935,7 +1935,7 @@ String Device::OpenCL_C_Version() const { return p ? p->getStrProp(CL_DEVICE_OPENCL_C_VERSION) : String(); } String Device::OpenCLVersion() const -{ return p ? p->getStrProp(CL_DEVICE_EXTENSIONS) : String(); } +{ return p ? p->getStrProp(CL_DEVICE_VERSION) : String(); } int Device::deviceVersionMajor() const { return p ? p->deviceVersionMajor_ : 0; } From 57ed0e57f0b422cb13b5f4bd7f9c83187a2576e9 Mon Sep 17 00:00:00 2001 From: Fangjun KUANG Date: Thu, 16 Feb 2017 16:00:32 +0100 Subject: [PATCH 393/409] Fix the documentation for Mat::diag(int). (#8199) * Fix the documentation for Mat::diag(int). Fix issue #8181 * Fix the documentation for Mat::diag(int). Fix issue #8181. * Add support for printing out cv::Complex. * Remove extra spaces. * cv::Complex is submitted as a new pull request. --- modules/core/include/opencv2/core/mat.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 0953b362b9..70ecac1bfc 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -1081,9 +1081,9 @@ public: single-column matrix. Similarly to Mat::row and Mat::col, this is an O(1) operation. @param d index of the diagonal, with the following values: - `d=0` is the main diagonal. - - `d>0` is a diagonal from the lower half. For example, d=1 means the diagonal is set + - `d<0` is a diagonal from the lower half. For example, d=-1 means the diagonal is set immediately below the main one. - - `d<0` is a diagonal from the upper half. For example, d=-1 means the diagonal is set + - `d>0` is a diagonal from the upper half. For example, d=1 means the diagonal is set immediately above the main one. */ Mat diag(int d=0) const; From e827a5bd9ead5f809273f5c580d7ff4e8f3d28ea Mon Sep 17 00:00:00 2001 From: Fangjun KUANG Date: Sat, 18 Feb 2017 10:14:29 +0100 Subject: [PATCH 394/409] Fix an error in the demo code for cv::Mat::forEach --- modules/core/include/opencv2/core/mat.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 0953b362b9..c27f3f6c6a 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -1893,7 +1893,7 @@ public: // first. raw pointer access. for (int r = 0; r < image.rows; ++r) { - Pixel* ptr = image.ptr(0, r); + Pixel* ptr = image.ptr(r, 0); const Pixel* ptr_end = ptr + image.cols; for (; ptr != ptr_end; ++ptr) { ptr->x = 255; From 8638bba670aed47dd1e8a268267a147de16f2cf8 Mon Sep 17 00:00:00 2001 From: LaurentBerger Date: Sun, 19 Feb 2017 11:15:58 +0100 Subject: [PATCH 395/409] Merge pull request #8220 from LaurentBerger:Issue5035 --- samples/cpp/detect_mser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/cpp/detect_mser.cpp b/samples/cpp/detect_mser.cpp index 04429ccc56..b6c68bda91 100644 --- a/samples/cpp/detect_mser.cpp +++ b/samples/cpp/detect_mser.cpp @@ -271,7 +271,7 @@ static void DrawOpenGLMSER(Mat img, Mat result) data->tex.bind(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glDisable(GL_CULL_FACE); setOpenGlDrawCallback("OpenGL", draw, data); From 64cf206fb503788f468a28929efdeb9240abbacc Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Mon, 20 Feb 2017 19:09:26 +0900 Subject: [PATCH 396/409] optimize blend using universal intrinsic - add more channels/depth performance test for blend --- .../include/opencv2/core/hal/intrin_sse.hpp | 27 ++ modules/imgproc/perf/opencl/perf_blend.cpp | 2 +- modules/imgproc/src/blend.cpp | 259 +++++++++++++++++- 3 files changed, 286 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/hal/intrin_sse.hpp b/modules/core/include/opencv2/core/hal/intrin_sse.hpp index 40821e09fe..60003082ea 100644 --- a/modules/core/include/opencv2/core/hal/intrin_sse.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_sse.hpp @@ -1368,6 +1368,24 @@ OPENCV_HAL_IMPL_SSE_TRANSPOSE4x4(v_int32x4, epi32, OPENCV_HAL_NOP, OPENCV_HAL_NO OPENCV_HAL_IMPL_SSE_TRANSPOSE4x4(v_float32x4, ps, _mm_castps_si128, _mm_castsi128_ps) // adopted from sse_utils.hpp +inline void v_load_deinterleave(const uchar* ptr, v_uint8x16& a, v_uint8x16& b) +{ + __m128i t00 = _mm_loadu_si128((const __m128i*)ptr); + __m128i t01 = _mm_loadu_si128((const __m128i*)(ptr + 16)); + + __m128i t10 = _mm_unpacklo_epi8(t00, t01); + __m128i t11 = _mm_unpackhi_epi8(t00, t01); + + __m128i t20 = _mm_unpacklo_epi8(t10, t11); + __m128i t21 = _mm_unpackhi_epi8(t10, t11); + + __m128i t30 = _mm_unpacklo_epi8(t20, t21); + __m128i t31 = _mm_unpackhi_epi8(t20, t21); + + a.val = _mm_unpacklo_epi8(t30, t31); + b.val = _mm_unpackhi_epi8(t30, t31); +} + inline void v_load_deinterleave(const uchar* ptr, v_uint8x16& a, v_uint8x16& b, v_uint8x16& c) { __m128i t00 = _mm_loadu_si128((const __m128i*)ptr); @@ -1507,6 +1525,15 @@ inline void v_store_interleave( short* ptr, const v_int16x8& a, const v_int16x8& _mm_storeu_si128((__m128i*)(ptr + 8), t1); } +inline void v_store_interleave( uchar* ptr, const v_uint8x16& a, const v_uint8x16& b) +{ + __m128i v0 = _mm_unpacklo_epi8(a.val, b.val); + __m128i v1 = _mm_unpackhi_epi8(a.val, b.val); + + _mm_storeu_si128((__m128i*)(ptr), v0); + _mm_storeu_si128((__m128i*)(ptr + 16), v1); +} + inline void v_store_interleave( uchar* ptr, const v_uint8x16& a, const v_uint8x16& b, const v_uint8x16& c ) { diff --git a/modules/imgproc/perf/opencl/perf_blend.cpp b/modules/imgproc/perf/opencl/perf_blend.cpp index 6396fef7e2..4feda65144 100644 --- a/modules/imgproc/perf/opencl/perf_blend.cpp +++ b/modules/imgproc/perf/opencl/perf_blend.cpp @@ -56,7 +56,7 @@ namespace ocl { typedef Size_MatType BlendLinearFixture; -OCL_PERF_TEST_P(BlendLinearFixture, BlendLinear, ::testing::Combine(OCL_TEST_SIZES, OCL_PERF_ENUM(CV_32FC1, CV_32FC4))) +OCL_PERF_TEST_P(BlendLinearFixture, BlendLinear, ::testing::Combine(OCL_TEST_SIZES, OCL_PERF_ENUM(CV_32FC1, CV_32FC3, CV_32FC4, CV_8UC1, CV_8UC3, CV_8UC4))) { Size_MatType_t params = GetParam(); const Size srcSize = get<0>(params); diff --git a/modules/imgproc/src/blend.cpp b/modules/imgproc/src/blend.cpp index 17e31aa4ab..bcb5c80f4a 100644 --- a/modules/imgproc/src/blend.cpp +++ b/modules/imgproc/src/blend.cpp @@ -45,8 +45,260 @@ #include "precomp.hpp" #include "opencl_kernels_imgproc.hpp" +#include "opencv2/core/hal/intrin.hpp" namespace cv { +#if CV_SIMD128 +static inline v_float32x4 blend(const v_float32x4& v_src1, const v_float32x4& v_src2, const v_float32x4& v_w1, const v_float32x4& v_w2) +{ + const v_float32x4 v_eps = v_setall_f32(1e-5f); + v_float32x4 v_denom = v_w1 + v_w2 + v_eps; + return (v_src1 * v_w1 + v_src2 * v_w2) / v_denom; +} +static inline v_float32x4 blend(const v_float32x4& v_src1, const v_float32x4& v_src2, const float* w_ptr1, const float* w_ptr2, int offset) +{ + v_float32x4 v_w1 = v_load(w_ptr1 + offset); + v_float32x4 v_w2 = v_load(w_ptr2 + offset); + return blend(v_src1, v_src2, v_w1, v_w2); +} +static inline v_uint32x4 saturate_f32_u32(const v_float32x4& vec) +{ + const v_int32x4 z = v_setzero_s32(); + const v_int32x4 x = v_setall_s32(255); + return v_reinterpret_as_u32(v_min(v_max(v_round(vec), z), x)); +} +static inline v_uint8x16 pack_f32tou8(v_float32x4& val0, v_float32x4& val1, v_float32x4& val2, v_float32x4& val3) +{ + v_uint32x4 a = saturate_f32_u32(val0); + v_uint32x4 b = saturate_f32_u32(val1); + v_uint32x4 c = saturate_f32_u32(val2); + v_uint32x4 d = saturate_f32_u32(val3); + v_uint16x8 e = v_pack(a, b); + v_uint16x8 f = v_pack(c, d); + return v_pack(e, f); +} +static inline void store_pack_f32tou8(uchar* ptr, v_float32x4& val0, v_float32x4& val1, v_float32x4& val2, v_float32x4& val3) +{ + v_store((ptr), pack_f32tou8(val0, val1, val2, val3)); +} +static inline void expand_u8tof32(const v_uint8x16& src, v_float32x4& dst0, v_float32x4& dst1, v_float32x4& dst2, v_float32x4& dst3) +{ + v_uint16x8 a0, a1; + v_expand(src, a0, a1); + v_uint32x4 b0, b1,b2,b3; + v_expand(a0, b0, b1); + v_expand(a1, b2, b3); + dst0 = v_cvt_f32(v_reinterpret_as_s32(b0)); + dst1 = v_cvt_f32(v_reinterpret_as_s32(b1)); + dst2 = v_cvt_f32(v_reinterpret_as_s32(b2)); + dst3 = v_cvt_f32(v_reinterpret_as_s32(b3)); +} +static inline void load_expand_u8tof32(const uchar* ptr, v_float32x4& dst0, v_float32x4& dst1, v_float32x4& dst2, v_float32x4& dst3) +{ + v_uint8x16 a = v_load((ptr)); + expand_u8tof32(a, dst0, dst1, dst2, dst3); +} +int blendLinearSimd128(const uchar* src1, const uchar* src2, const float* weights1, const float* weights2, uchar* dst, int x, int width, int cn) +{ + const v_float32x4 v_eps = v_setall_f32(1e-5f); + int weight_offset = 0; + int step = v_uint8x16::nlanes * cn; + int weight_step = v_uint8x16::nlanes*cn; + switch(cn) + { + case 1: + for( ; x <= width - step; x += step, weight_offset += weight_step) + { + v_float32x4 v_src10, v_src11, v_src12, v_src13; + v_float32x4 v_src20, v_src21, v_src22, v_src23; + load_expand_u8tof32(src1 + x, v_src10, v_src11, v_src12, v_src13); + load_expand_u8tof32(src2 + x, v_src20, v_src21, v_src22, v_src23); + + v_float32x4 v_dst0 = blend(v_src10, v_src20, weights1, weights2, weight_offset); + v_float32x4 v_dst1 = blend(v_src11, v_src21, weights1, weights2, weight_offset + 4); + v_float32x4 v_dst2 = blend(v_src12, v_src22, weights1, weights2, weight_offset + 8); + v_float32x4 v_dst3 = blend(v_src13, v_src23, weights1, weights2, weight_offset + 12); + + store_pack_f32tou8(dst + x, v_dst0, v_dst1, v_dst2, v_dst3); + } + break; + case 2: + for( ; x <= width - step; x += step, weight_offset += weight_step) + { + v_uint8x16 v_src10, v_src11, v_src20, v_src21; + v_load_deinterleave(src1 + x, v_src10, v_src11); + v_load_deinterleave(src2 + x, v_src20, v_src21); + v_float32x4 v_src100, v_src101, v_src102, v_src103, v_src110, v_src111, v_src112, v_src113; + v_float32x4 v_src200, v_src201, v_src202, v_src203, v_src210, v_src211, v_src212, v_src213; + expand_u8tof32(v_src10, v_src100, v_src101, v_src102, v_src103); + expand_u8tof32(v_src11, v_src110, v_src111, v_src112, v_src113); + expand_u8tof32(v_src20, v_src200, v_src201, v_src202, v_src203); + expand_u8tof32(v_src21, v_src210, v_src211, v_src212, v_src213); + + v_float32x4 v_dst0 = blend(v_src100, v_src200, weights1, weights2, weight_offset); + v_float32x4 v_dst1 = blend(v_src110, v_src210, weights1, weights2, weight_offset); + v_float32x4 v_dst2 = blend(v_src101, v_src201, weights1, weights2, weight_offset + 4); + v_float32x4 v_dst3 = blend(v_src111, v_src211, weights1, weights2, weight_offset + 4); + v_float32x4 v_dst4 = blend(v_src102, v_src202, weights1, weights2, weight_offset + 8); + v_float32x4 v_dst5 = blend(v_src112, v_src212, weights1, weights2, weight_offset + 8); + v_float32x4 v_dst6 = blend(v_src103, v_src203, weights1, weights2, weight_offset + 12); + v_float32x4 v_dst7 = blend(v_src113, v_src213, weights1, weights2, weight_offset + 12); + + v_uint8x16 v_dsta = pack_f32tou8(v_dst0, v_dst2, v_dst4, v_dst6); + v_uint8x16 v_dstb = pack_f32tou8(v_dst1, v_dst3, v_dst5, v_dst7); + v_store_interleave(dst + x, v_dsta, v_dstb); + } + break; + case 3: + for( ; x <= width - step; x += step, weight_offset += weight_step) + { + v_uint8x16 v_src10, v_src11, v_src12, v_src20, v_src21, v_src22; + v_load_deinterleave(src1 + x, v_src10, v_src11, v_src12); + v_load_deinterleave(src2 + x, v_src20, v_src21, v_src22); + + v_float32x4 v_src100, v_src101, v_src102, v_src103, v_src110, v_src111, v_src112, v_src113, v_src120, v_src121, v_src122, v_src123; + v_float32x4 v_src200, v_src201, v_src202, v_src203, v_src210, v_src211, v_src212, v_src213, v_src220, v_src221, v_src222, v_src223; + expand_u8tof32(v_src10, v_src100, v_src101, v_src102, v_src103); + expand_u8tof32(v_src11, v_src110, v_src111, v_src112, v_src113); + expand_u8tof32(v_src12, v_src120, v_src121, v_src122, v_src123); + expand_u8tof32(v_src20, v_src200, v_src201, v_src202, v_src203); + expand_u8tof32(v_src21, v_src210, v_src211, v_src212, v_src213); + expand_u8tof32(v_src22, v_src220, v_src221, v_src222, v_src223); + + v_float32x4 v_w10 = v_load(weights1 + weight_offset); + v_float32x4 v_w11 = v_load(weights1 + weight_offset + 4); + v_float32x4 v_w12 = v_load(weights1 + weight_offset + 8); + v_float32x4 v_w13 = v_load(weights1 + weight_offset + 12); + v_float32x4 v_w20 = v_load(weights2 + weight_offset); + v_float32x4 v_w21 = v_load(weights2 + weight_offset + 4); + v_float32x4 v_w22 = v_load(weights2 + weight_offset + 8); + v_float32x4 v_w23 = v_load(weights2 + weight_offset + 12); + v_src100 = blend(v_src100, v_src200, v_w10, v_w20); + v_src110 = blend(v_src110, v_src210, v_w10, v_w20); + v_src120 = blend(v_src120, v_src220, v_w10, v_w20); + v_src101 = blend(v_src101, v_src201, v_w11, v_w21); + v_src111 = blend(v_src111, v_src211, v_w11, v_w21); + v_src121 = blend(v_src121, v_src221, v_w11, v_w21); + v_src102 = blend(v_src102, v_src202, v_w12, v_w22); + v_src112 = blend(v_src112, v_src212, v_w12, v_w22); + v_src122 = blend(v_src122, v_src222, v_w12, v_w22); + v_src103 = blend(v_src103, v_src203, v_w13, v_w23); + v_src113 = blend(v_src113, v_src213, v_w13, v_w23); + v_src123 = blend(v_src123, v_src223, v_w13, v_w23); + + + v_uint8x16 v_dst0 = pack_f32tou8(v_src100, v_src101, v_src102, v_src103); + v_uint8x16 v_dst1 = pack_f32tou8(v_src110, v_src111, v_src112, v_src113); + v_uint8x16 v_dst2 = pack_f32tou8(v_src120, v_src121, v_src122, v_src123); + + v_store_interleave(dst + x, v_dst0, v_dst1, v_dst2); + } + break; + case 4: + step = v_uint8x16::nlanes; + weight_step = v_float32x4::nlanes; + for( ; x <= width - step; x += step, weight_offset += weight_step) + { + v_float32x4 v_src10, v_src11, v_src12, v_src13, v_src14, v_src15, v_src16, v_src17; + v_float32x4 v_src20, v_src21, v_src22, v_src23, v_src24, v_src25, v_src26, v_src27; + load_expand_u8tof32(src1 + x, v_src10, v_src11, v_src12, v_src13); + load_expand_u8tof32(src2 + x, v_src20, v_src21, v_src22, v_src23); + v_transpose4x4(v_src10, v_src11, v_src12, v_src13, v_src14, v_src15, v_src16, v_src17); + v_transpose4x4(v_src20, v_src21, v_src22, v_src23, v_src24, v_src25, v_src26, v_src27); + + v_float32x4 v_w1 = v_load(weights1 + weight_offset); + v_float32x4 v_w2 = v_load(weights2 + weight_offset); + v_float32x4 v_denom = v_w1 + v_w2 + v_eps; + v_src10 = (v_src14 * v_w1 + v_src24 * v_w2) / v_denom; + v_src11 = (v_src15 * v_w1 + v_src25 * v_w2) / v_denom; + v_src12 = (v_src16 * v_w1 + v_src26 * v_w2) / v_denom; + v_src13 = (v_src17 * v_w1 + v_src27 * v_w2) / v_denom; + v_float32x4 v_dst0, v_dst1, v_dst2, v_dst3; + v_transpose4x4(v_src10, v_src11, v_src12, v_src13, v_dst0, v_dst1, v_dst2, v_dst3); + + store_pack_f32tou8(dst + x, v_dst0, v_dst1, v_dst2, v_dst3); + } + break; + default: + break; + } + return x; +} + +int blendLinearSimd128(const float* src1, const float* src2, const float* weights1, const float* weights2, float* dst, int x, int width, int cn) +{ + const v_float32x4 v_eps = v_setall_f32(1e-5f); + int weight_offset = 0; + int step = v_float32x4::nlanes*cn; + switch(cn) + { + case 1: + for( ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) + { + v_float32x4 v_src1 = v_load(src1 + x); + v_float32x4 v_src2 = v_load(src2 + x); + v_float32x4 v_w1 = v_load(weights1 + weight_offset); + v_float32x4 v_w2 = v_load(weights2 + weight_offset); + v_float32x4 v_denom = v_w1 + v_w2 + v_eps; + v_float32x4 v_dst = (v_src1 * v_w1 + v_src2 * v_w2) / v_denom; + + v_store(dst + x, v_dst); + } + break; + case 2: + for( ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) + { + v_float32x4 v_src10, v_src11, v_src20, v_src21; + v_load_deinterleave(src1 + x, v_src10, v_src11); + v_load_deinterleave(src2 + x, v_src20, v_src21); + v_float32x4 v_w1 = v_load(weights1 + weight_offset); + v_float32x4 v_w2 = v_load(weights2 + weight_offset); + v_float32x4 v_denom = v_w1 + v_w2 + v_eps; + v_float32x4 v_dst0 = (v_src10 * v_w1 + v_src20 * v_w2) / v_denom; + v_float32x4 v_dst1 = (v_src11 * v_w1 + v_src21 * v_w2) / v_denom; + + v_store_interleave(dst + x, v_dst0, v_dst1); + } + break; + case 3: + for( ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) + { + v_float32x4 v_src10, v_src11, v_src12, v_src20, v_src21, v_src22; + v_load_deinterleave(src1 + x, v_src10, v_src11, v_src12); + v_load_deinterleave(src2 + x, v_src20, v_src21, v_src22); + v_float32x4 v_w1 = v_load(weights1 + weight_offset); + v_float32x4 v_w2 = v_load(weights2 + weight_offset); + v_float32x4 v_denom = v_w1 + v_w2 + v_eps; + v_float32x4 v_dst0 = (v_src10 * v_w1 + v_src20 * v_w2) / v_denom; + v_float32x4 v_dst1 = (v_src11 * v_w1 + v_src21 * v_w2) / v_denom; + v_float32x4 v_dst2 = (v_src12 * v_w1 + v_src22 * v_w2) / v_denom; + + v_store_interleave(dst + x, v_dst0, v_dst1, v_dst2); + } + break; + case 4: + for( ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) + { + v_float32x4 v_src10, v_src11, v_src12, v_src13, v_src20, v_src21, v_src22, v_src23; + v_load_deinterleave(src1 + x, v_src10, v_src11, v_src12, v_src13); + v_load_deinterleave(src2 + x, v_src20, v_src21, v_src22, v_src23); + v_float32x4 v_w1 = v_load(weights1 + weight_offset); + v_float32x4 v_w2 = v_load(weights2 + weight_offset); + v_float32x4 v_denom = v_w1 + v_w2 + v_eps; + v_float32x4 v_dst0 = (v_src10 * v_w1 + v_src20 * v_w2) / v_denom; + v_float32x4 v_dst1 = (v_src11 * v_w1 + v_src21 * v_w2) / v_denom; + v_float32x4 v_dst2 = (v_src12 * v_w1 + v_src22 * v_w2) / v_denom; + v_float32x4 v_dst3 = (v_src13 * v_w1 + v_src23 * v_w2) / v_denom; + + v_store_interleave(dst + x, v_dst0, v_dst1, v_dst2, v_dst3); + } + break; + default: + break; + } + return x; +} +#endif template class BlendLinearInvoker : @@ -71,7 +323,12 @@ public: const T * const src2_row = src2->ptr(y); T * const dst_row = dst->ptr(y); - for (int x = 0; x < width; ++x) + int x = 0; + #if CV_SIMD128 + x = blendLinearSimd128(src1_row, src2_row, weights1_row, weights2_row, dst_row, x, width, cn); + #endif + + for ( ; x < width; ++x) { int x1 = x / cn; float w1 = weights1_row[x1], w2 = weights2_row[x1]; From b1851e2f1667c40ae6580622308534a0ce32ef2e Mon Sep 17 00:00:00 2001 From: Fangjun KUANG Date: Mon, 20 Feb 2017 16:22:46 +0100 Subject: [PATCH 397/409] Add support to print `cv::UMat`. Now a user can use `std::cout` to print an object of `cv::UMat` just like `cv::Mat`. --- modules/core/include/opencv2/core/cvstd.inl.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/core/include/opencv2/core/cvstd.inl.hpp b/modules/core/include/opencv2/core/cvstd.inl.hpp index 876def8254..d08b72b333 100644 --- a/modules/core/include/opencv2/core/cvstd.inl.hpp +++ b/modules/core/include/opencv2/core/cvstd.inl.hpp @@ -183,6 +183,12 @@ std::ostream& operator << (std::ostream& out, const Mat& mtx) return out << Formatter::get()->format(mtx); } +static inline +std::ostream& operator << (std::ostream& out, const UMat& m) +{ + return out << m.getMat(ACCESS_READ); +} + template static inline std::ostream& operator << (std::ostream& out, const std::vector >& vec) { From 232d6b87f44a284e39a18e15e10ff29efbfa4352 Mon Sep 17 00:00:00 2001 From: Hamdi Sahloul Date: Tue, 21 Feb 2017 11:29:28 +0900 Subject: [PATCH 398/409] cv::Matx python wrapper --- modules/python/src2/cv2.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index 66e3733cec..25f4554ece 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -411,6 +411,11 @@ PyObject* pyopencv_from(const Mat& m) return o; } +template +PyObject* pyopencv_from(const Matx<_Tp, m, n>& matx) +{ + return pyopencv_from(Mat(matx)); +} typedef struct { PyObject_HEAD From 526220a171d4d7d6b2065445b5794b7a50930166 Mon Sep 17 00:00:00 2001 From: Fangjun KUANG Date: Tue, 21 Feb 2017 10:48:15 +0100 Subject: [PATCH 399/409] Fix typos in the documentation (#8226) * fix typos. * Fix typos. * Fix typos. * Fix typos. * Fix typos. --- modules/core/include/opencv2/core.hpp | 6 +++--- modules/core/include/opencv2/core/core_c.h | 4 ++-- modules/core/include/opencv2/core/cvdef.h | 2 +- modules/core/include/opencv2/core/utility.hpp | 14 +++++++------- modules/core/src/system.cpp | 8 ++++---- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index 6b89c42efa..b6c1c7942c 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -114,7 +114,7 @@ public: */ Exception(); /*! - Full constructor. Normally the constuctor is not called explicitly. + Full constructor. Normally the constructor is not called explicitly. Instead, the macros CV_Error(), CV_Error_() and CV_Assert() are used. */ Exception(int _code, const String& _err, const String& _func, const String& _file, int _line); @@ -131,8 +131,8 @@ public: int code; ///< error code @see CVStatus String err; ///< error description String func; ///< function name. Available only when the compiler supports getting it - String file; ///< source file name where the error has occured - int line; ///< line number in the source file where the error has occured + String file; ///< source file name where the error has occurred + int line; ///< line number in the source file where the error has occurred }; /*! @brief Signals an error and raises the exception. diff --git a/modules/core/include/opencv2/core/core_c.h b/modules/core/include/opencv2/core/core_c.h index e12f79de80..b992c2073c 100644 --- a/modules/core/include/opencv2/core/core_c.h +++ b/modules/core/include/opencv2/core/core_c.h @@ -2654,7 +2654,7 @@ CVAPI(int) cvGetErrMode( void ); /** Sets error processing mode, returns previously used mode */ CVAPI(int) cvSetErrMode( int mode ); -/** Sets error status and performs some additonal actions (displaying message box, +/** Sets error status and performs some additional actions (displaying message box, writing message to stderr, terminating application etc.) depending on the current error mode */ CVAPI(void) cvError( int status, const char* func_name, @@ -2663,7 +2663,7 @@ CVAPI(void) cvError( int status, const char* func_name, /** Retrieves textual description of the error given its code */ CVAPI(const char*) cvErrorStr( int status ); -/** Retrieves detailed information about the last error occured */ +/** Retrieves detailed information about the last error occurred */ CVAPI(int) cvGetErrInfo( const char** errcode_desc, const char** description, const char** filename, int* line ); diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index 9e2b1ed7fd..3f55cc1d8c 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -416,7 +416,7 @@ Cv64suf; #define CV_IS_SUBMAT(flags) ((flags) & CV_MAT_SUBMAT_FLAG) /** Size of each channel item, - 0x124489 = 1000 0100 0100 0010 0010 0001 0001 ~ array of sizeof(arr_type_elem) */ + 0x8442211 = 1000 0100 0100 0010 0010 0001 0001 ~ array of sizeof(arr_type_elem) */ #define CV_ELEM_SIZE1(type) \ ((((sizeof(size_t)<<28)|0x8442211) >> CV_MAT_DEPTH(type)*4) & 15) diff --git a/modules/core/include/opencv2/core/utility.hpp b/modules/core/include/opencv2/core/utility.hpp index dab039c4b9..0b9038303e 100644 --- a/modules/core/include/opencv2/core/utility.hpp +++ b/modules/core/include/opencv2/core/utility.hpp @@ -61,8 +61,8 @@ namespace cv #ifdef CV_COLLECT_IMPL_DATA CV_EXPORTS void setImpl(int flags); // set implementation flags and reset storage arrays CV_EXPORTS void addImpl(int flag, const char* func = 0); // add implementation and function name to storage arrays -// Get stored implementation flags and fucntions names arrays -// Each implementation entry correspond to function name entry, so you can find which implementation was executed in which fucntion +// Get stored implementation flags and functions names arrays +// Each implementation entry correspond to function name entry, so you can find which implementation was executed in which function CV_EXPORTS int getImpl(std::vector &impl, std::vector &funName); CV_EXPORTS bool useCollection(); // return implementation collection state @@ -196,7 +196,7 @@ be called outside of parallel region. OpenCV will try to run it's functions with specified threads number, but some behaviour differs from framework: - `TBB` – User-defined parallel constructions will run with the same threads number, if - another does not specified. If late on user creates own scheduler, OpenCV will be use it. + another does not specified. If later on user creates own scheduler, OpenCV will use it. - `OpenMP` – No special defined behaviour. - `Concurrency` – If threads == 1, OpenCV will disable threading optimizations and run it's functions sequentially. @@ -229,7 +229,7 @@ CV_EXPORTS_W int getNumThreads(); returns 0 if called outside of parallel region. The exact meaning of return value depends on the threading framework used by OpenCV library: -- `TBB` – Unsupported with current 4.1 TBB release. May be will be supported in future. +- `TBB` – Unsupported with current 4.1 TBB release. Maybe will be supported in future. - `OpenMP` – The thread number, within the current team, of the calling thread. - `Concurrency` – An ID for the virtual processor that the current context is executing on (0 for master thread and unique number for others, but not necessary 1,2,3,...). @@ -636,7 +636,7 @@ class TLSData : protected TLSDataContainer public: inline TLSData() {} inline ~TLSData() { release(); } // Release key and delete associated data - inline T* get() const { return (T*)getData(); } // Get data assosiated with key + inline T* get() const { return (T*)getData(); } // Get data associated with key // Get data from all threads inline void gather(std::vector &data) const @@ -687,7 +687,7 @@ The sample below demonstrates how to use CommandLineParser: ### Keys syntax -The keys parameter is a string containing several blocks, each one is enclosed in curley braces and +The keys parameter is a string containing several blocks, each one is enclosed in curly braces and describes one argument. Each argument contains three parts separated by the `|` symbol: -# argument names is a space-separated list of option synonyms (to mark argument as positional, prefix it with the `@` symbol) @@ -845,7 +845,7 @@ public: /** @brief Check for parsing errors - Returns true if error occured while accessing the parameters (bad conversion, missing arguments, + Returns true if error occurred while accessing the parameters (bad conversion, missing arguments, etc.). Call @ref printErrors to print error messages list. */ bool check() const; diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index 3c8f39d74b..890ca96f10 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -751,7 +751,7 @@ CV_IMPL const char* cvErrorStr( int status ) case CV_StsAutoTrace : return "Autotrace call"; case CV_StsBadSize : return "Incorrect size of input array"; case CV_StsNullPtr : return "Null pointer"; - case CV_StsDivByZero : return "Division by zero occured"; + case CV_StsDivByZero : return "Division by zero occurred"; case CV_BadStep : return "Image step is wrong"; case CV_StsInplaceNotSupported : return "Inplace operation is not supported"; case CV_StsObjectNotFound : return "Requested object was not found"; @@ -1035,7 +1035,7 @@ public: { if(threads[i]) { - /* Current architecture doesn't allow proper global objects relase, so this check can cause crashes + /* Current architecture doesn't allow proper global objects release, so this check can cause crashes // Check if all slots were properly cleared for(size_t j = 0; j < threads[i]->slots.size(); j++) @@ -1085,7 +1085,7 @@ public: return (tlsSlots.size()-1); } - // Release TLS storage index and pass assosiated data to caller + // Release TLS storage index and pass associated data to caller void releaseSlot(size_t slotIdx, std::vector &dataVec) { AutoLock guard(mtxGlobalAccess); @@ -1196,7 +1196,7 @@ void TLSDataContainer::release() std::vector data; data.reserve(32); getTlsStorage().releaseSlot(key_, data); // Release key and get stored data for proper destruction - for(size_t i = 0; i < data.size(); i++) // Delete all assosiated data + for(size_t i = 0; i < data.size(); i++) // Delete all associated data deleteDataInstance(data[i]); key_ = -1; } From a78d143c98d92acc115046b479db13aed9f19557 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 21 Feb 2017 12:50:09 +0300 Subject: [PATCH 400/409] ios framework: refactor code (no functional changes) --- platforms/ios/build_framework.py | 61 +++++++++++++------------------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/platforms/ios/build_framework.py b/platforms/ios/build_framework.py index 2770a5a102..5b08f69bfe 100644 --- a/platforms/ios/build_framework.py +++ b/platforms/ios/build_framework.py @@ -122,37 +122,32 @@ class Builder: def getCMakeArgs(self, arch, target): - if self.dynamic: - args = [ - "cmake", - "-GXcode", - "-DAPPLE_FRAMEWORK=ON", - "-DCMAKE_INSTALL_PREFIX=install", - "-DCMAKE_BUILD_TYPE=Release", - "-DBUILD_SHARED_LIBS=ON", - "-DCMAKE_MACOSX_BUNDLE=ON", - "-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO", - ] - else: - args = [ - "cmake", - "-GXcode", - "-DAPPLE_FRAMEWORK=ON", - "-DCMAKE_INSTALL_PREFIX=install", - "-DCMAKE_BUILD_TYPE=Release", - ] + args = [ + "cmake", + "-GXcode", + "-DAPPLE_FRAMEWORK=ON", + "-DCMAKE_INSTALL_PREFIX=install", + "-DCMAKE_BUILD_TYPE=Release", + ] + ([ + "-DBUILD_SHARED_LIBS=ON", + "-DCMAKE_MACOSX_BUNDLE=ON", + "-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO", + ] if self.dynamic else []) if len(self.exclude) > 0: args += ["-DBUILD_opencv_world=OFF"] - args += ("-DBUILD_opencv_%s=OFF" % m for m in self.exclude) + args += ["-DBUILD_opencv_%s=OFF" % m for m in self.exclude] return args def getBuildCommand(self, archs, target): + buildcmd = [ + "xcodebuild", + ] + if self.dynamic: - buildcmd = [ - "xcodebuild", + buildcmd += [ "IPHONEOS_DEPLOYMENT_TARGET=8.0", "ONLY_ACTIVE_ARCH=NO", ] @@ -160,25 +155,19 @@ class Builder: for arch in archs: buildcmd.append("-arch") buildcmd.append(arch.lower()) - + else: + arch = ";".join(archs) buildcmd += [ + "IPHONEOS_DEPLOYMENT_TARGET=6.0", + "ARCHS=%s" % arch, + ] + + buildcmd += [ "-sdk", target.lower(), "-configuration", "Release", "-parallelizeTargets", "-jobs", "4", - "-target","ALL_BUILD", - ] - else: - arch = ";".join(archs) - buildcmd = [ - "xcodebuild", - "IPHONEOS_DEPLOYMENT_TARGET=6.0", - "ARCHS=%s" % arch, - "-sdk", target.lower(), - "-configuration", "Release", - "-parallelizeTargets", - "-jobs", "4" - ] + ] + (["-target","ALL_BUILD"] if self.dynamic else []) return buildcmd From f1c8e04268c046fdf82fb2a9534dd6ee9b5436b1 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 21 Feb 2017 12:50:40 +0300 Subject: [PATCH 401/409] ios framework: don't disable opencv_world in dynamic framework --- platforms/ios/build_framework.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/ios/build_framework.py b/platforms/ios/build_framework.py index 5b08f69bfe..a09c2fa916 100644 --- a/platforms/ios/build_framework.py +++ b/platforms/ios/build_framework.py @@ -135,7 +135,7 @@ class Builder: ] if self.dynamic else []) if len(self.exclude) > 0: - args += ["-DBUILD_opencv_world=OFF"] + args += ["-DBUILD_opencv_world=OFF"] if not self.dynamic else [] args += ["-DBUILD_opencv_%s=OFF" % m for m in self.exclude] return args From eaba0e63581e9e4809f3fffcf2f1a8bd4c5342a5 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 21 Feb 2017 12:52:06 +0300 Subject: [PATCH 402/409] cmake: append '-framework UIKit' for videoio/imgcodecs module --- modules/imgcodecs/CMakeLists.txt | 3 +++ modules/videoio/CMakeLists.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index 8da6c176cd..546d956a98 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -92,6 +92,9 @@ if(IOS) list(APPEND imgcodecs_srcs ${CMAKE_CURRENT_LIST_DIR}/src/ios_conversions.mm) list(APPEND IMGCODECS_LIBRARIES "-framework Accelerate" "-framework CoreGraphics" "-framework QuartzCore" "-framework AssetsLibrary") endif() +if(APPLE_FRAMEWORK) + list(APPEND IMGCODECS_LIBRARIES "-framework UIKit") +endif() if(UNIX) #these variables are set by CHECK_MODULE macro diff --git a/modules/videoio/CMakeLists.txt b/modules/videoio/CMakeLists.txt index 763304adbb..cbcb8a1a63 100644 --- a/modules/videoio/CMakeLists.txt +++ b/modules/videoio/CMakeLists.txt @@ -210,7 +210,7 @@ if(IOS) ${CMAKE_CURRENT_LIST_DIR}/src/cap_ios_video_camera.mm) list(APPEND VIDEOIO_LIBRARIES "-framework Accelerate" "-framework AVFoundation" "-framework CoreGraphics" "-framework CoreImage" "-framework CoreMedia" "-framework CoreVideo" "-framework QuartzCore" "-framework AssetsLibrary") - if(APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + if(APPLE_FRAMEWORK) list(APPEND VIDEOIO_LIBRARIES "-framework UIKit") endif() endif() From d7240d1a28b9f09da5769b49ea8a4059a99d9e2e Mon Sep 17 00:00:00 2001 From: berak Date: Tue, 21 Feb 2017 14:27:12 +0100 Subject: [PATCH 403/409] fix leaking handle in cap_dshow.cpp --- modules/videoio/src/cap_dshow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/videoio/src/cap_dshow.cpp b/modules/videoio/src/cap_dshow.cpp index 8360e4318b..200edc71c1 100644 --- a/modules/videoio/src/cap_dshow.cpp +++ b/modules/videoio/src/cap_dshow.cpp @@ -877,14 +877,12 @@ void videoDevice::NukeDownstream(IBaseFilter *pBF){ void videoDevice::destroyGraph(){ HRESULT hr = 0; - //int FuncRetval=0; - //int NumFilters=0; int i = 0; while (hr == NOERROR) { IEnumFilters * pEnum = 0; - ULONG cFetched; + ULONG cFetched = 0; // We must get the enumerator again every time because removing a filter from the graph // invalidates the enumerator. We always get only the first filter from each enumerator. @@ -917,9 +915,11 @@ void videoDevice::destroyGraph(){ pFilter->Release(); pFilter = NULL; } - else break; pEnum->Release(); pEnum = NULL; + + if (cFetched == 0) + break; i++; } From 88a338a3eba4f6f383e251b8ce576ff8237e44dd Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 21 Feb 2017 19:23:37 +0300 Subject: [PATCH 404/409] cmake: disable pkg-config for Apple frameworks --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 71b4b36087..83d567685e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -519,7 +519,9 @@ endif(WIN32 AND NOT MINGW) # CHECK FOR SYSTEM LIBRARIES, OPTIONS, ETC.. # ---------------------------------------------------------------------------- if(UNIX) - find_package(PkgConfig QUIET) + if(NOT APPLE_FRAMEWORK) + find_package(PkgConfig QUIET) + endif() include(CheckFunctionExists) include(CheckIncludeFile) From 9fbce6c8c869f1303b6db084afe9ce5b809b1771 Mon Sep 17 00:00:00 2001 From: Wenju He Date: Wed, 22 Feb 2017 08:44:10 +0800 Subject: [PATCH 405/409] fix multiband_blend.cu build error in issue opencv_contrib#1012 --- modules/stitching/src/cuda/multiband_blend.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/stitching/src/cuda/multiband_blend.cu b/modules/stitching/src/cuda/multiband_blend.cu index daa0005ec0..27eb92044b 100644 --- a/modules/stitching/src/cuda/multiband_blend.cu +++ b/modules/stitching/src/cuda/multiband_blend.cu @@ -90,7 +90,7 @@ namespace cv { namespace cuda { namespace device if (x < width && y < height) { - static const float WEIGHT_EPS = 1e-5f; + const float WEIGHT_EPS = 1e-5f; const short3 v = ((short3*)src.ptr(y))[x]; float w = weight.ptr(y)[x]; ((short3*)src.ptr(y))[x] = make_short3(static_cast(v.x / (w + WEIGHT_EPS)), From 14451f3f062d3c05b62f03b433423752c5fb9315 Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Wed, 22 Feb 2017 14:00:22 +0300 Subject: [PATCH 406/409] core: fix adjustROI behavior on indexes overflow --- modules/core/src/matrix.cpp | 9 +++++++-- modules/core/src/umatrix.cpp | 9 +++++++-- modules/core/test/test_operations.cpp | 14 ++++++++++++++ modules/core/test/test_umat.cpp | 14 ++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/modules/core/src/matrix.cpp b/modules/core/src/matrix.cpp index c542a9ac70..b5fc690ed9 100644 --- a/modules/core/src/matrix.cpp +++ b/modules/core/src/matrix.cpp @@ -971,8 +971,13 @@ Mat& Mat::adjustROI( int dtop, int dbottom, int dleft, int dright ) Size wholeSize; Point ofs; size_t esz = elemSize(); locateROI( wholeSize, ofs ); - int row1 = std::max(ofs.y - dtop, 0), row2 = std::min(ofs.y + rows + dbottom, wholeSize.height); - int col1 = std::max(ofs.x - dleft, 0), col2 = std::min(ofs.x + cols + dright, wholeSize.width); + int row1 = std::min(std::max(ofs.y - dtop, 0), wholeSize.height), row2 = std::max(0, std::min(ofs.y + rows + dbottom, wholeSize.height)); + int col1 = std::min(std::max(ofs.x - dleft, 0), wholeSize.width), col2 = std::max(0, std::min(ofs.x + cols + dright, wholeSize.width)); + if(row1 > row2) + std::swap(row1, row2); + if(col1 > col2) + std::swap(col1, col2); + data += (row1 - ofs.y)*step + (col1 - ofs.x)*esz; rows = row2 - row1; cols = col2 - col1; size.p[0] = rows; size.p[1] = cols; diff --git a/modules/core/src/umatrix.cpp b/modules/core/src/umatrix.cpp index dd5b9a0c2c..6991b40d04 100644 --- a/modules/core/src/umatrix.cpp +++ b/modules/core/src/umatrix.cpp @@ -599,8 +599,13 @@ UMat& UMat::adjustROI( int dtop, int dbottom, int dleft, int dright ) Size wholeSize; Point ofs; size_t esz = elemSize(); locateROI( wholeSize, ofs ); - int row1 = std::max(ofs.y - dtop, 0), row2 = std::min(ofs.y + rows + dbottom, wholeSize.height); - int col1 = std::max(ofs.x - dleft, 0), col2 = std::min(ofs.x + cols + dright, wholeSize.width); + int row1 = std::min(std::max(ofs.y - dtop, 0), wholeSize.height), row2 = std::max(0, std::min(ofs.y + rows + dbottom, wholeSize.height)); + int col1 = std::min(std::max(ofs.x - dleft, 0), wholeSize.width), col2 = std::max(0, std::min(ofs.x + cols + dright, wholeSize.width)); + if(row1 > row2) + std::swap(row1, row2); + if(col1 > col2) + std::swap(col1, col2); + offset += (row1 - ofs.y)*step + (col1 - ofs.x)*esz; rows = row2 - row1; cols = col2 - col1; size.p[0] = rows; size.p[1] = cols; diff --git a/modules/core/test/test_operations.cpp b/modules/core/test/test_operations.cpp index 543fb31ac7..2526ef8a10 100644 --- a/modules/core/test/test_operations.cpp +++ b/modules/core/test/test_operations.cpp @@ -1240,3 +1240,17 @@ protected: }; TEST(Core_SparseMat, iterations) { CV_SparseMatTest test; test.safe_run(); } + +TEST(MatTestRoi, adjustRoiOverflow) +{ + Mat m(15, 10, CV_32S); + Mat roi(m, cv::Range(2, 10), cv::Range(3,6)); + int rowsInROI = roi.rows; + roi.adjustROI(1, 0, 0, 0); + + ASSERT_EQ(roi.rows, rowsInROI + 1); + + roi.adjustROI(-m.rows, -m.rows, 0, 0); + + ASSERT_EQ(roi.rows, m.rows); +} diff --git a/modules/core/test/test_umat.cpp b/modules/core/test/test_umat.cpp index 9e9835f350..d7a3e6de1f 100644 --- a/modules/core/test/test_umat.cpp +++ b/modules/core/test/test_umat.cpp @@ -496,6 +496,20 @@ TEST_P(UMatTestRoi, adjustRoi) INSTANTIATE_TEST_CASE_P(UMat, UMatTestRoi, Combine(OCL_ALL_DEPTHS, OCL_ALL_CHANNELS, UMAT_TEST_SIZES )); +TEST(UMatTestRoi, adjustRoiOverflow) +{ + UMat m(15, 10, CV_32S); + UMat roi(m, cv::Range(2, 10), cv::Range(3,6)); + int rowsInROI = roi.rows; + roi.adjustROI(1, 0, 0, 0); + + ASSERT_EQ(roi.rows, rowsInROI + 1); + + roi.adjustROI(-m.rows, -m.rows, 0, 0); + + ASSERT_EQ(roi.rows, m.rows); +} + /////////////////////////////////////////////////////////////// Size //////////////////////////////////////////////////////////////////// PARAM_TEST_CASE(UMatTestSizeOperations, int, int, Size, bool) From 595437bdd18dc6122be78908e55acea374f768a6 Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov Date: Tue, 21 Feb 2017 17:40:53 +0300 Subject: [PATCH 407/409] hal: replace round() with cvRound() --- modules/core/src/hal_internal.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/core/src/hal_internal.cpp b/modules/core/src/hal_internal.cpp index 345ca42dc6..d98cd303c4 100644 --- a/modules/core/src/hal_internal.cpp +++ b/modules/core/src/hal_internal.cpp @@ -290,7 +290,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste else if (typeid(fptype) == typeid(double)) dgels_(mode, &m, &n, &k, (double*)tmpA, &ldtmpA, (double*)b, &m, (double*)&work1, &lwork, info); - lwork = (int)round(work1); //optimal buffer size + lwork = cvRound(work1); //optimal buffer size std::vector workBufMemHolder(lwork + 1); fptype* buffer = &workBufMemHolder.front(); @@ -311,7 +311,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste else if (typeid(fptype) == typeid(double)) dgels_(mode, &m, &n, &k, (double*)tmpA, &ldtmpA, (double*)tmpB, &m, (double*)&work1, &lwork, info); - lwork = (int)round(work1); //optimal buffer size + lwork = cvRound(work1); //optimal buffer size std::vector workBufMemHolder(lwork + 1); fptype* buffer = &workBufMemHolder.front(); @@ -330,7 +330,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste else if (typeid(fptype) == typeid(double)) dgeqrf_(&m, &n, (double*)tmpA, &ldtmpA, (double*)dst, (double*)&work1, &lwork, info); - lwork = (int)round(work1); //optimal buffer size + lwork = cvRound(work1); //optimal buffer size std::vector workBufMemHolder(lwork + 1); fptype* buffer = &workBufMemHolder.front(); From aec59aba340efc0119f011f1c9bc89389157e791 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Thu, 23 Feb 2017 09:11:12 +0900 Subject: [PATCH 408/409] suppress warnings - brush up the implementation --- modules/imgproc/perf/opencl/perf_blend.cpp | 2 +- modules/imgproc/src/blend.cpp | 65 +++++++++++----------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/modules/imgproc/perf/opencl/perf_blend.cpp b/modules/imgproc/perf/opencl/perf_blend.cpp index 4feda65144..661aa06324 100644 --- a/modules/imgproc/perf/opencl/perf_blend.cpp +++ b/modules/imgproc/perf/opencl/perf_blend.cpp @@ -56,7 +56,7 @@ namespace ocl { typedef Size_MatType BlendLinearFixture; -OCL_PERF_TEST_P(BlendLinearFixture, BlendLinear, ::testing::Combine(OCL_TEST_SIZES, OCL_PERF_ENUM(CV_32FC1, CV_32FC3, CV_32FC4, CV_8UC1, CV_8UC3, CV_8UC4))) +OCL_PERF_TEST_P(BlendLinearFixture, BlendLinear, ::testing::Combine(OCL_TEST_SIZES, OCL_TEST_TYPES_134)) { Size_MatType_t params = GetParam(); const Size srcSize = get<0>(params); diff --git a/modules/imgproc/src/blend.cpp b/modules/imgproc/src/blend.cpp index bcb5c80f4a..16ed8c25c7 100644 --- a/modules/imgproc/src/blend.cpp +++ b/modules/imgproc/src/blend.cpp @@ -98,16 +98,16 @@ static inline void load_expand_u8tof32(const uchar* ptr, v_float32x4& dst0, v_fl v_uint8x16 a = v_load((ptr)); expand_u8tof32(a, dst0, dst1, dst2, dst3); } +int blendLinearSimd128(const uchar* src1, const uchar* src2, const float* weights1, const float* weights2, uchar* dst, int x, int width, int cn); +int blendLinearSimd128(const float* src1, const float* src2, const float* weights1, const float* weights2, float* dst, int x, int width, int cn); int blendLinearSimd128(const uchar* src1, const uchar* src2, const float* weights1, const float* weights2, uchar* dst, int x, int width, int cn) { - const v_float32x4 v_eps = v_setall_f32(1e-5f); - int weight_offset = 0; int step = v_uint8x16::nlanes * cn; - int weight_step = v_uint8x16::nlanes*cn; + int weight_step = v_uint8x16::nlanes; switch(cn) { case 1: - for( ; x <= width - step; x += step, weight_offset += weight_step) + for(int weight_offset = 0 ; x <= width - step; x += step, weight_offset += weight_step) { v_float32x4 v_src10, v_src11, v_src12, v_src13; v_float32x4 v_src20, v_src21, v_src22, v_src23; @@ -123,7 +123,7 @@ int blendLinearSimd128(const uchar* src1, const uchar* src2, const float* weight } break; case 2: - for( ; x <= width - step; x += step, weight_offset += weight_step) + for(int weight_offset = 0 ; x <= width - step; x += step, weight_offset += weight_step) { v_uint8x16 v_src10, v_src11, v_src20, v_src21; v_load_deinterleave(src1 + x, v_src10, v_src11); @@ -150,7 +150,7 @@ int blendLinearSimd128(const uchar* src1, const uchar* src2, const float* weight } break; case 3: - for( ; x <= width - step; x += step, weight_offset += weight_step) + for(int weight_offset = 0 ; x <= width - step; x += step, weight_offset += weight_step) { v_uint8x16 v_src10, v_src11, v_src12, v_src20, v_src21, v_src22; v_load_deinterleave(src1 + x, v_src10, v_src11, v_src12); @@ -190,32 +190,31 @@ int blendLinearSimd128(const uchar* src1, const uchar* src2, const float* weight v_uint8x16 v_dst0 = pack_f32tou8(v_src100, v_src101, v_src102, v_src103); v_uint8x16 v_dst1 = pack_f32tou8(v_src110, v_src111, v_src112, v_src113); v_uint8x16 v_dst2 = pack_f32tou8(v_src120, v_src121, v_src122, v_src123); - v_store_interleave(dst + x, v_dst0, v_dst1, v_dst2); } break; case 4: step = v_uint8x16::nlanes; weight_step = v_float32x4::nlanes; - for( ; x <= width - step; x += step, weight_offset += weight_step) + for(int weight_offset = 0 ; x <= width - step; x += step, weight_offset += weight_step) { v_float32x4 v_src10, v_src11, v_src12, v_src13, v_src14, v_src15, v_src16, v_src17; v_float32x4 v_src20, v_src21, v_src22, v_src23, v_src24, v_src25, v_src26, v_src27; load_expand_u8tof32(src1 + x, v_src10, v_src11, v_src12, v_src13); load_expand_u8tof32(src2 + x, v_src20, v_src21, v_src22, v_src23); + v_transpose4x4(v_src10, v_src11, v_src12, v_src13, v_src14, v_src15, v_src16, v_src17); v_transpose4x4(v_src20, v_src21, v_src22, v_src23, v_src24, v_src25, v_src26, v_src27); v_float32x4 v_w1 = v_load(weights1 + weight_offset); v_float32x4 v_w2 = v_load(weights2 + weight_offset); - v_float32x4 v_denom = v_w1 + v_w2 + v_eps; - v_src10 = (v_src14 * v_w1 + v_src24 * v_w2) / v_denom; - v_src11 = (v_src15 * v_w1 + v_src25 * v_w2) / v_denom; - v_src12 = (v_src16 * v_w1 + v_src26 * v_w2) / v_denom; - v_src13 = (v_src17 * v_w1 + v_src27 * v_w2) / v_denom; + v_src10 = blend(v_src14, v_src24, v_w1, v_w2); + v_src11 = blend(v_src15, v_src25, v_w1, v_w2); + v_src12 = blend(v_src16, v_src26, v_w1, v_w2); + v_src13 = blend(v_src17, v_src27, v_w1, v_w2); + v_float32x4 v_dst0, v_dst1, v_dst2, v_dst3; v_transpose4x4(v_src10, v_src11, v_src12, v_src13, v_dst0, v_dst1, v_dst2, v_dst3); - store_pack_f32tou8(dst + x, v_dst0, v_dst1, v_dst2, v_dst3); } break; @@ -227,68 +226,66 @@ int blendLinearSimd128(const uchar* src1, const uchar* src2, const float* weight int blendLinearSimd128(const float* src1, const float* src2, const float* weights1, const float* weights2, float* dst, int x, int width, int cn) { - const v_float32x4 v_eps = v_setall_f32(1e-5f); - int weight_offset = 0; int step = v_float32x4::nlanes*cn; switch(cn) { case 1: - for( ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) + for(int weight_offset = 0 ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) { v_float32x4 v_src1 = v_load(src1 + x); v_float32x4 v_src2 = v_load(src2 + x); v_float32x4 v_w1 = v_load(weights1 + weight_offset); v_float32x4 v_w2 = v_load(weights2 + weight_offset); - v_float32x4 v_denom = v_w1 + v_w2 + v_eps; - v_float32x4 v_dst = (v_src1 * v_w1 + v_src2 * v_w2) / v_denom; + + v_float32x4 v_dst = blend(v_src1, v_src2, v_w1, v_w2); v_store(dst + x, v_dst); } break; case 2: - for( ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) + for(int weight_offset = 0 ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) { v_float32x4 v_src10, v_src11, v_src20, v_src21; v_load_deinterleave(src1 + x, v_src10, v_src11); v_load_deinterleave(src2 + x, v_src20, v_src21); v_float32x4 v_w1 = v_load(weights1 + weight_offset); v_float32x4 v_w2 = v_load(weights2 + weight_offset); - v_float32x4 v_denom = v_w1 + v_w2 + v_eps; - v_float32x4 v_dst0 = (v_src10 * v_w1 + v_src20 * v_w2) / v_denom; - v_float32x4 v_dst1 = (v_src11 * v_w1 + v_src21 * v_w2) / v_denom; + + v_float32x4 v_dst0 = blend(v_src10, v_src20, v_w1, v_w2); + v_float32x4 v_dst1 = blend(v_src11, v_src21, v_w1, v_w2); v_store_interleave(dst + x, v_dst0, v_dst1); } break; case 3: - for( ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) + for(int weight_offset = 0 ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) { v_float32x4 v_src10, v_src11, v_src12, v_src20, v_src21, v_src22; v_load_deinterleave(src1 + x, v_src10, v_src11, v_src12); v_load_deinterleave(src2 + x, v_src20, v_src21, v_src22); v_float32x4 v_w1 = v_load(weights1 + weight_offset); v_float32x4 v_w2 = v_load(weights2 + weight_offset); - v_float32x4 v_denom = v_w1 + v_w2 + v_eps; - v_float32x4 v_dst0 = (v_src10 * v_w1 + v_src20 * v_w2) / v_denom; - v_float32x4 v_dst1 = (v_src11 * v_w1 + v_src21 * v_w2) / v_denom; - v_float32x4 v_dst2 = (v_src12 * v_w1 + v_src22 * v_w2) / v_denom; + + v_float32x4 v_dst0 = blend(v_src10, v_src20, v_w1, v_w2); + v_float32x4 v_dst1 = blend(v_src11, v_src21, v_w1, v_w2); + v_float32x4 v_dst2 = blend(v_src12, v_src22, v_w1, v_w2); v_store_interleave(dst + x, v_dst0, v_dst1, v_dst2); } break; case 4: - for( ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) + for(int weight_offset = 0 ; x <= width - step; x += step, weight_offset += v_float32x4::nlanes) { v_float32x4 v_src10, v_src11, v_src12, v_src13, v_src20, v_src21, v_src22, v_src23; v_load_deinterleave(src1 + x, v_src10, v_src11, v_src12, v_src13); v_load_deinterleave(src2 + x, v_src20, v_src21, v_src22, v_src23); v_float32x4 v_w1 = v_load(weights1 + weight_offset); v_float32x4 v_w2 = v_load(weights2 + weight_offset); - v_float32x4 v_denom = v_w1 + v_w2 + v_eps; - v_float32x4 v_dst0 = (v_src10 * v_w1 + v_src20 * v_w2) / v_denom; - v_float32x4 v_dst1 = (v_src11 * v_w1 + v_src21 * v_w2) / v_denom; - v_float32x4 v_dst2 = (v_src12 * v_w1 + v_src22 * v_w2) / v_denom; - v_float32x4 v_dst3 = (v_src13 * v_w1 + v_src23 * v_w2) / v_denom; + + v_float32x4 v_dst0 = blend(v_src10, v_src20, v_w1, v_w2); + v_float32x4 v_dst1 = blend(v_src11, v_src21, v_w1, v_w2); + v_float32x4 v_dst2 = blend(v_src12, v_src22, v_w1, v_w2); + v_float32x4 v_dst3 = blend(v_src13, v_src23, v_w1, v_w2); v_store_interleave(dst + x, v_dst0, v_dst1, v_dst2, v_dst3); } From 822c67fdee49dc07f9074ab817fcde998eb03ed4 Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Fri, 24 Feb 2017 01:31:32 +0900 Subject: [PATCH 409/409] remove non ASCII character from comment --- modules/imgproc/src/connectedcomponents.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/src/connectedcomponents.cpp b/modules/imgproc/src/connectedcomponents.cpp index bf53704144..9ffcf719f7 100644 --- a/modules/imgproc/src/connectedcomponents.cpp +++ b/modules/imgproc/src/connectedcomponents.cpp @@ -336,7 +336,7 @@ namespace cv{ }//End function LabelingWu operator() };//End struct LabelingWu - // Based on Optimized Block-based Connected Components Labeling with Decision Trees, Costantino Grana et al + // Based on "Optimized Block-based Connected Components Labeling with Decision Trees", Costantino Grana et al // Only for 8-connectivity template struct LabelingGrana{

    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 +using namespace std; using namespace cv; -double alpha; /**< Simple contrast control */ -int beta; /**< Simple brightness control */ - /** * @function main * @brief Main function */ int main( int, char** argv ) { - /// Read image given by user - Mat image = imread( argv[1] ); - Mat new_image = Mat::zeros( image.size(), image.type() ); + //! [basic-linear-transform-parameters] + double alpha = 1.0; /*< Simple contrast control */ + int beta = 0; /*< Simple brightness control */ + //! [basic-linear-transform-parameters] - /// Initialize values - std::cout<<" Basic Linear Transforms "<>alpha; - std::cout<<"* Enter the beta value [0-100]: "; std::cin>>beta; + /// Read image given by user + //! [basic-linear-transform-load] + Mat image = imread( argv[1] ); + //! [basic-linear-transform-load] + //! [basic-linear-transform-output] + Mat new_image = Mat::zeros( image.size(), image.type() ); + //! [basic-linear-transform-output] + /// Initialize values + cout << " Basic Linear Transforms " << endl; + cout << "-------------------------" << endl; + cout << "* Enter the alpha value [1.0-3.0]: "; cin >> alpha; + cout << "* Enter the beta value [0-100]: "; cin >> beta; - /// Do the operation new_image(i,j) = alpha*image(i,j) + beta - /// Instead of these 'for' loops we could have used simply: - /// image.convertTo(new_image, -1, alpha, beta); - /// but we wanted to show you how to access the pixels :) - for( int y = 0; y < image.rows; y++ ) - { for( int x = 0; x < image.cols; x++ ) - { for( int c = 0; c < 3; c++ ) - { - new_image.at(y,x)[c] = saturate_cast( alpha*( image.at(y,x)[c] ) + beta ); - } - } - } + /// Do the operation new_image(i,j) = alpha*image(i,j) + beta + /// Instead of these 'for' loops we could have used simply: + /// image.convertTo(new_image, -1, alpha, beta); + /// but we wanted to show you how to access the pixels :) + //! [basic-linear-transform-operation] + for( int y = 0; y < image.rows; y++ ) { + for( int x = 0; x < image.cols; x++ ) { + for( int c = 0; c < 3; c++ ) { + new_image.at(y,x)[c] = + saturate_cast( alpha*( image.at(y,x)[c] ) + beta ); + } + } + } + //! [basic-linear-transform-operation] - /// Create Windows - namedWindow("Original Image", 1); - namedWindow("New Image", 1); + //! [basic-linear-transform-display] + /// Create Windows + namedWindow("Original Image", WINDOW_AUTOSIZE); + namedWindow("New Image", WINDOW_AUTOSIZE); - /// Show stuff - imshow("Original Image", image); - imshow("New Image", new_image); + /// Show stuff + imshow("Original Image", image); + imshow("New Image", new_image); - - /// Wait until user press some key - waitKey(); - return 0; + /// Wait until user press some key + waitKey(); + //! [basic-linear-transform-display] + return 0; } diff --git a/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp b/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp new file mode 100644 index 0000000000..7fc28f03d1 --- /dev/null +++ b/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp @@ -0,0 +1,91 @@ +#include +#include "opencv2/imgcodecs.hpp" +#include "opencv2/highgui.hpp" + +using namespace std; +using namespace cv; + +namespace +{ +/** Global Variables */ +int alpha = 100; +int beta = 100; +int gamma_cor = 100; +Mat img_original, img_corrected, img_gamma_corrected; + +void basicLinearTransform(const Mat &img, const double alpha_, const int beta_) +{ + Mat res; + img.convertTo(res, -1, alpha_, beta_); + + hconcat(img, res, img_corrected); +} + +void gammaCorrection(const Mat &img, const double gamma_) +{ + CV_Assert(gamma_ >= 0); + //![changing-contrast-brightness-gamma-correction] + Mat lookUpTable(1, 256, CV_8U); + uchar* p = lookUpTable.ptr(); + for( int i = 0; i < 256; ++i) + p[i] = saturate_cast(pow(i / 255.0, gamma_) * 255.0); + + Mat res = img.clone(); + LUT(img, lookUpTable, res); + //![changing-contrast-brightness-gamma-correction] + + hconcat(img, res, img_gamma_corrected); +} + +void on_linear_transform_alpha_trackbar(int, void *) +{ + double alpha_value = alpha / 100.0; + int beta_value = beta - 100; + basicLinearTransform(img_original, alpha_value, beta_value); +} + +void on_linear_transform_beta_trackbar(int, void *) +{ + double alpha_value = alpha / 100.0; + int beta_value = beta - 100; + basicLinearTransform(img_original, alpha_value, beta_value); +} + +void on_gamma_correction_trackbar(int, void *) +{ + double gamma_value = gamma_cor / 100.0; + gammaCorrection(img_original, gamma_value); +} +} + +int main( int, char** argv ) +{ + img_original = imread( argv[1] ); + img_corrected = Mat(img_original.rows, img_original.cols*2, img_original.type()); + img_gamma_corrected = Mat(img_original.rows, img_original.cols*2, img_original.type()); + + hconcat(img_original, img_original, img_corrected); + hconcat(img_original, img_original, img_gamma_corrected); + + namedWindow("Brightness and contrast adjustments", WINDOW_AUTOSIZE); + namedWindow("Gamma correction", WINDOW_AUTOSIZE); + + createTrackbar("Alpha gain (contrast)", "Brightness and contrast adjustments", &alpha, 500, on_linear_transform_alpha_trackbar); + createTrackbar("Beta bias (brightness)", "Brightness and contrast adjustments", &beta, 200, on_linear_transform_beta_trackbar); + createTrackbar("Gamma correction", "Gamma correction", &gamma_cor, 200, on_gamma_correction_trackbar); + + while (true) + { + imshow("Brightness and contrast adjustments", img_corrected); + imshow("Gamma correction", img_gamma_corrected); + + int c = waitKey(30); + if (c == 27) + break; + } + + imwrite("linear_transform_correction.png", img_corrected); + imwrite("gamma_correction.png", img_gamma_corrected); + + return 0; +} From f1aae52daf66b85e80e446aadc51ec44e935124d Mon Sep 17 00:00:00 2001 From: Tomoaki Teshima Date: Tue, 18 Oct 2016 10:23:47 +0900 Subject: [PATCH 297/409] clean up unused #if --- modules/calib3d/src/compat_ptsetreg.cpp | 4 -- modules/calib3d/src/fisheye.cpp | 24 ----------- modules/calib3d/src/triangulate.cpp | 46 ---------------------- modules/calib3d/test/test_chesscorners.cpp | 10 ----- 4 files changed, 84 deletions(-) diff --git a/modules/calib3d/src/compat_ptsetreg.cpp b/modules/calib3d/src/compat_ptsetreg.cpp index 774129e421..6e67000b3b 100644 --- a/modules/calib3d/src/compat_ptsetreg.cpp +++ b/modules/calib3d/src/compat_ptsetreg.cpp @@ -313,11 +313,7 @@ void CvLevMarq::step() if( !err ) completeSymm( _JtJN, completeSymmFlag ); -#if 1 _JtJN.diag() *= 1. + lambda; -#else - _JtJN.diag() += lambda; -#endif solve(_JtJN, _JtErr, nonzero_param, solveMethod); int j = 0; diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 32dc15c3f2..2b65d6b329 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -542,19 +542,6 @@ void cv::fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, Input pptr[6] = Vec2d(0, h); pptr[7] = Vec2d(0, h/2); -#if 0 - const int N = 10; - cv::Mat points(1, N * 4, CV_64FC2); - Vec2d* pptr = points.ptr(); - for(int i = 0, k = 0; i < 10; ++i) - { - pptr[k++] = Vec2d(w/2, 0) - Vec2d(w/8, 0) + Vec2d(w/4/N*i, 0); - pptr[k++] = Vec2d(w/2, h-1) - Vec2d(w/8, h-1) + Vec2d(w/4/N*i, h-1); - pptr[k++] = Vec2d(0, h/2) - Vec2d(0, h/8) + Vec2d(0, h/4/N*i); - pptr[k++] = Vec2d(w-1, h/2) - Vec2d(w-1, h/8) + Vec2d(w-1, h/4/N*i); - } -#endif - fisheye::undistortPoints(points, points, K, D, R); cv::Scalar center_mass = mean(points); cv::Vec2d cn(center_mass.val); @@ -580,17 +567,6 @@ void cv::fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, Input maxx = std::max(maxx, std::abs(pptr[i][0]-cn[0])); } -#if 0 - double minx = -DBL_MAX, miny = -DBL_MAX, maxx = DBL_MAX, maxy = DBL_MAX; - for(size_t i = 0; i < points.total(); ++i) - { - if (i % 4 == 0) miny = std::max(miny, pptr[i][1]); - if (i % 4 == 1) maxy = std::min(maxy, pptr[i][1]); - if (i % 4 == 2) minx = std::max(minx, pptr[i][0]); - if (i % 4 == 3) maxx = std::min(maxx, pptr[i][0]); - } -#endif - double f1 = w * 0.5/(minx); double f2 = w * 0.5/(maxx); double f3 = h * 0.5 * aspect_ratio/(miny); diff --git a/modules/calib3d/src/triangulate.cpp b/modules/calib3d/src/triangulate.cpp index 33c7fd774d..5bdf10e4b8 100644 --- a/modules/calib3d/src/triangulate.cpp +++ b/modules/calib3d/src/triangulate.cpp @@ -114,52 +114,6 @@ cvTriangulatePoints(CvMat* projMatr1, CvMat* projMatr2, CvMat* projPoints1, CvMa cvmSet(points4D,2,i,matrV(3,2));/* Z */ cvmSet(points4D,3,i,matrV(3,3));/* W */ } - -#if 0 - double err = 0; - /* Points was reconstructed. Try to reproject points */ - /* We can compute reprojection error if need */ - { - int i; - CvMat point3D; - double point3D_dat[4]; - point3D = cvMat(4,1,CV_64F,point3D_dat); - - CvMat point2D; - double point2D_dat[3]; - point2D = cvMat(3,1,CV_64F,point2D_dat); - - for( i = 0; i < numPoints; i++ ) - { - double W = cvmGet(points4D,3,i); - - point3D_dat[0] = cvmGet(points4D,0,i)/W; - point3D_dat[1] = cvmGet(points4D,1,i)/W; - point3D_dat[2] = cvmGet(points4D,2,i)/W; - point3D_dat[3] = 1; - - /* !!! Project this point for each camera */ - for( int currCamera = 0; currCamera < 2; currCamera++ ) - { - cvMatMul(projMatrs[currCamera], &point3D, &point2D); - - float x,y; - float xr,yr,wr; - x = (float)cvmGet(projPoints[currCamera],0,i); - y = (float)cvmGet(projPoints[currCamera],1,i); - - wr = (float)point2D_dat[2]; - xr = (float)(point2D_dat[0]/wr); - yr = (float)(point2D_dat[1]/wr); - - float deltaX,deltaY; - deltaX = (float)fabs(x-xr); - deltaY = (float)fabs(y-yr); - err += deltaX*deltaX + deltaY*deltaY; - } - } - } -#endif } diff --git a/modules/calib3d/test/test_chesscorners.cpp b/modules/calib3d/test/test_chesscorners.cpp index 781eec2ffb..64a2d69c37 100644 --- a/modules/calib3d/test/test_chesscorners.cpp +++ b/modules/calib3d/test/test_chesscorners.cpp @@ -268,14 +268,6 @@ void CV_ChessboardDetectorTest::run_batch( const string& filename ) #ifndef WRITE_POINTS double err = calcError(v, expected); -#if 0 - if( err > rough_success_error_level ) - { - ts.printf( cvtest::TS::LOG, "bad accuracy of corner guesses\n" ); - ts.set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY ); - continue; - } -#endif max_rough_error = MAX( max_rough_error, err ); #endif if( pattern == CHESSBOARD ) @@ -287,14 +279,12 @@ void CV_ChessboardDetectorTest::run_batch( const string& filename ) err = calcError(v, expected); sum_error += err; count++; -#if 1 if( err > precise_success_error_level ) { ts->printf( cvtest::TS::LOG, "Image %s: bad accuracy of adjusted corners %f\n", img_file.c_str(), err ); ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY ); return; } -#endif ts->printf(cvtest::TS::LOG, "Error on %s is %f\n", img_file.c_str(), err); max_precise_error = MAX( max_precise_error, err ); #endif From 46fa9a6bd4be75dd5af6c2295b8f940675327ee1 Mon Sep 17 00:00:00 2001 From: StevenPuttemans Date: Fri, 6 Jan 2017 11:19:47 +0100 Subject: [PATCH 298/409] fix some issues with skipping files and old redundant code --- apps/annotation/opencv_annotation.cpp | 48 +++++++++------------------ 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/apps/annotation/opencv_annotation.cpp b/apps/annotation/opencv_annotation.cpp index febe9fc95d..5d1eedeccd 100644 --- a/apps/annotation/opencv_annotation.cpp +++ b/apps/annotation/opencv_annotation.cpp @@ -59,12 +59,7 @@ Adapted by: Puttemans Steven - April 2016 - Vectorize the process to enable bett #include #include - -#if defined(_WIN32) - #include -#else - #include -#endif +#include using namespace std; using namespace cv; @@ -249,34 +244,20 @@ int main( int argc, const char** argv ) int resizeFactor = parser.get("resizeFactor"); int const maxWindowHeight = parser.get("maxWindowHeight") > 0 ? parser.get("maxWindowHeight") : -1; - // Check if the folder actually exists - // If -1 is returned then the folder actually exists, and thus you can continue - // In all other cases there was a folder creation and thus the folder did not exist - #if defined(_WIN32) - if(_mkdir(image_folder.c_str()) != -1){ - // Generate an error message - cerr << "The image folder given does not exist. Please check again!" << endl; - // Remove the created folder again, to ensure a second run with same code fails again - _rmdir(image_folder.c_str()); - return 0; - } - #else - if(mkdir(image_folder.c_str(), 0777) != -1){ - // Generate an error message - cerr << "The image folder given does not exist. Please check again!" << endl; - // Remove the created folder again, to ensure a second run with same code fails again - remove(image_folder.c_str()); - return 0; - } - #endif - // Start by processing the data // Return the image filenames inside the image folder - vector< vector > annotations; + map< String, vector > annotations; vector filenames; String folder(image_folder); glob(folder, filenames); + // Add key tips on how to use the software when running it + cout << "* mark rectangles with the left mouse button," << endl; + cout << "* press 'c' to accept a selection," << endl; + cout << "* press 'd' to delete the latest selection," << endl; + cout << "* press 'n' to proceed with next image," << endl; + cout << "* press 'esc' to stop." << endl; + // Loop through each image stored in the images folder // Create and temporarily store the annotations // At the end write everything to the annotations file @@ -306,7 +287,7 @@ int main( int argc, const char** argv ) current_annotations[j].height = current_annotations[j].height * resizeFactor; } } - annotations.push_back(current_annotations); + annotations[filenames[i]] = current_annotations; // Check if the ESC key was hit, then exit earlier then expected if(stop){ @@ -323,10 +304,11 @@ int main( int argc, const char** argv ) } // Store the annotations, write to the output file - for(int i = 0; i < (int)annotations.size(); i++){ - output << filenames[i] << " " << annotations[i].size(); - for(int j=0; j < (int)annotations[i].size(); j++){ - Rect temp = annotations[i][j]; + for(map >::iterator it = annotations.begin(); it != annotations.end(); it++){ + vector &anno = it->second; + output << it->first << " " << anno.size(); + for(size_t j=0; j < anno.size(); j++){ + Rect temp = anno[j]; output << " " << temp.x << " " << temp.y << " " << temp.width << " " << temp.height; } output << endl; From e6f27240d5e5fc49f9d079a34abaccf2bee72654 Mon Sep 17 00:00:00 2001 From: LaurentBerger Date: Sun, 8 Jan 2017 15:24:29 +0100 Subject: [PATCH 299/409] dd a note in findcontours doc --- modules/imgproc/include/opencv2/imgproc.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 95e5a1592b..243d72b32e 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -3744,6 +3744,7 @@ CV_EXPORTS_W int connectedComponentsWithStats(InputArray image, OutputArray labe The function retrieves contours from the binary image using the algorithm @cite Suzuki85 . The contours are a useful tool for shape analysis and object detection and recognition. See squares.cpp in the OpenCV sample directory. +@note Since opencv 3.2 source image is not modified by this function. @param image Source, an 8-bit single-channel image. Non-zero pixels are treated as 1's. Zero pixels remain 0's, so the image is treated as binary . You can use cv::compare, cv::inRange, cv::threshold , From b3c77e664d853e425f8966fbd1f3be993b22df99 Mon Sep 17 00:00:00 2001 From: atinfinity Date: Mon, 9 Jan 2017 19:51:33 +0900 Subject: [PATCH 300/409] added to check the support of AVX2 --- modules/core/src/stat.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index 5cdae20871..e1fddb220d 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -4253,6 +4253,7 @@ int normHamming(const uchar* a, int n) result += vgetq_lane_s32 (vreinterpretq_s32_u64(bitSet2),2); } #elif CV_AVX2 + if (USE_AVX2) { __m256i _r0 = _mm256_setzero_si256(); __m256i _0 = _mm256_setzero_si256(); @@ -4303,6 +4304,7 @@ int normHamming(const uchar* a, const uchar* b, int n) result += vgetq_lane_s32 (vreinterpretq_s32_u64(bitSet2),2); } #elif CV_AVX2 + if (USE_AVX2) { __m256i _r0 = _mm256_setzero_si256(); __m256i _0 = _mm256_setzero_si256(); From e16e141c38ed53a5f9157e6768f9891f4ebf803a Mon Sep 17 00:00:00 2001 From: catree Date: Tue, 3 Jan 2017 22:28:19 +0100 Subject: [PATCH 301/409] Add OpenCV parallel_for_ tutorial. --- .../how_to_use_OpenCV_parallel_for_.markdown | 183 ++++++++++++++++++ ...nCV_parallel_for_640px-Mandelset_hires.png | Bin 0 -> 16752 bytes ..._to_use_OpenCV_parallel_for_Mandelbrot.png | Bin 0 -> 63249 bytes ...parallel_for_sqrt_scale_transformation.png | Bin 0 -> 33815 bytes .../core/table_of_content_core.markdown | 7 + .../how_to_use_OpenCV_parallel_for_.cpp | 122 ++++++++++++ 6 files changed, 312 insertions(+) create mode 100644 doc/tutorials/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.markdown create mode 100644 doc/tutorials/core/how_to_use_OpenCV_parallel_for_/images/how_to_use_OpenCV_parallel_for_640px-Mandelset_hires.png create mode 100644 doc/tutorials/core/how_to_use_OpenCV_parallel_for_/images/how_to_use_OpenCV_parallel_for_Mandelbrot.png create mode 100644 doc/tutorials/core/how_to_use_OpenCV_parallel_for_/images/how_to_use_OpenCV_parallel_for_sqrt_scale_transformation.png create mode 100644 samples/cpp/tutorial_code/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.cpp diff --git a/doc/tutorials/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.markdown b/doc/tutorials/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.markdown new file mode 100644 index 0000000000..f2a511fc21 --- /dev/null +++ b/doc/tutorials/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.markdown @@ -0,0 +1,183 @@ +How to use the OpenCV parallel_for_ to parallelize your code {#tutorial_how_to_use_OpenCV_parallel_for_} +================================================================== + +Goal +---- + +The goal of this tutorial is to show you how to use the OpenCV `parallel_for_` framework to easily +parallelize your code. To illustrate the concept, we will write a program to draw a Mandelbrot set +exploiting almost all the CPU load available. +The full tutorial code is [here](https://github.com/opencv/opencv/blob/master/samples/cpp/tutorial_code/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.cpp). +If you want more information about multithreading, you will have to refer to a reference book or course as this tutorial is intended +to remain simple. + +Precondition +---- + +The first precondition is to have OpenCV built with a parallel framework. +In OpenCV 3.2, the following parallel frameworks are available in that order: +1. Intel Threading Building Blocks (3rdparty library, should be explicitly enabled) +2. C= Parallel C/C++ Programming Language Extension (3rdparty library, should be explicitly enabled) +3. OpenMP (integrated to compiler, should be explicitly enabled) +4. APPLE GCD (system wide, used automatically (APPLE only)) +5. Windows RT concurrency (system wide, used automatically (Windows RT only)) +6. Windows concurrency (part of runtime, used automatically (Windows only - MSVC++ >= 10)) +7. Pthreads (if available) + +As you can see, several parallel frameworks can be used in the OpenCV library. Some parallel libraries +are third party libraries and have to be explictly built and enabled in CMake (e.g. TBB, C=), others are +automatically available with the platform (e.g. APPLE GCD) but chances are that you should be enable to +have access to a parallel framework either directly or by enabling the option in CMake and rebuild the library. + +The second (weak) precondition is more related to the task you want to achieve as not all computations +are suitable / can be adatapted to be run in a parallel way. To remain simple, tasks that can be splitted +into multiple elementary operations with no memory dependency (no possible race condition) are easily +parallelizable. Computer vision processing are often easily parallelizable as most of the time the processing of +one pixel does not depend to the state of other pixels. + +Simple example: drawing a Mandelbrot set +---- + +We will use the example of drawing a Mandelbrot set to show how from a regular sequential code you can easily adapt +the code to parallize the computation. + +Theory +----------- + +The Mandelbrot set definition has been named in tribute to the mathematician Benoit Mandelbrot by the mathematician +Adrien Douady. It has been famous outside of the mathematics field as the image representation is an example of a +class of fractals, a mathematical set that exhibits a repeating pattern displayed at every scale (even more, a +Mandelbrot set is self-similar as the whole shape can be repeatedly seen at different scale). For a more in-depth +introduction, you can look at the corresponding [Wikipedia article](https://en.wikipedia.org/wiki/Mandelbrot_set). +Here, we will just introduce the formula to draw the Mandelbrot set (from the mentioned Wikipedia article). + +> The Mandelbrot set is the set of values of \f$ c \f$ in the complex plane for which the orbit of 0 under iteration +> of the quadratic map +> \f[\begin{cases} z_0 = 0 \\ z_{n+1} = z_n^2 + c \end{cases}\f] +> remains bounded. +> That is, a complex number \f$ c \f$ is part of the Mandelbrot set if, when starting with \f$ z_0 = 0 \f$ and applying +> the iteration repeatedly, the absolute value of \f$ z_n \f$ remains bounded however large \f$ n \f$ gets. +> This can also be represented as +> \f[\limsup_{n\to\infty}|z_{n+1}|\leqslant2\f] + +Pseudocode +----------- + +A simple algorithm to generate a representation of the Mandelbrot set is called the +["escape time algorithm"](https://en.wikipedia.org/wiki/Mandelbrot_set#Escape_time_algorithm). +For each pixel in the rendered image, we test using the recurrence relation if the complex number is bounded or not +under a maximum number of iterations. Pixels that do not belong to the Mandelbrot set will escape quickly whereas +we assume that the pixel is in the set after a fixed maximum number of iterations. A high value of iterations will +produce a more detailed image but the computation time will increase accordingly. We use the number of iterations +needed to "escape" to depict the pixel value in the image. + +``` +For each pixel (Px, Py) on the screen, do: +{ + x0 = scaled x coordinate of pixel (scaled to lie in the Mandelbrot X scale (-2, 1)) + y0 = scaled y coordinate of pixel (scaled to lie in the Mandelbrot Y scale (-1, 1)) + x = 0.0 + y = 0.0 + iteration = 0 + max_iteration = 1000 + while (x*x + y*y < 2*2 AND iteration < max_iteration) { + xtemp = x*x - y*y + x0 + y = 2*x*y + y0 + x = xtemp + iteration = iteration + 1 + } + color = palette[iteration] + plot(Px, Py, color) +} +``` + +To relate between the pseudocode and the theory, we have: +* \f$ z = x + iy \f$ +* \f$ z^2 = x^2 + i2xy - y^2 \f$ +* \f$ c = x_0 + iy_0 \f$ + +![](images/how_to_use_OpenCV_parallel_for_640px-Mandelset_hires.png) + +On this figure, we recall that the real part of a complex number is on the x-axis and the imaginary part on the y-axis. +You can see that the whole shape can be repeatedly visible if we zoom at particular locations. + +Implementation +----------- + +Escape time algorithm implementation +-------------------------- + +@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-escape-time-algorithm + +Here, we used the [`std::complex`](http://en.cppreference.com/w/cpp/numeric/complex) template class to represent a +complex number. This function performs the test to check if the pixel is in set or not and returns the "escaped" iteration. + +Sequential Mandelbrot implementation +-------------------------- + +@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-sequential + +In this implementation, we sequentially iterate over the pixels in the rendered image to perform the test to check if the +pixel is likely to belong to the Mandelbrot set or not. + +Another thing to do is to transform the pixel coordinate into the Mandelbrot set space with: + +@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-transformation + +Finally, to assign the grayscale value to the pixels, we use the following rule: +* a pixel is black if it reaches the maximum number of iterations (pixel is assumed to be in the Mandelbrot set), +* otherwise we assign a grayscale value depending on the escaped iteration and scaled to fit the grayscale range. + +@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-grayscale-value + +Using a linear scale transformation is not enough to perceive the grayscale variation. To overcome this, we will boost +the perception by using a square root scale transformation (borrowed from Jeremy D. Frens in his +[blog post](http://www.programming-during-recess.net/2016/06/26/color-schemes-for-mandelbrot-sets/)): +\f$ f \left( x \right) = \sqrt{\frac{x}{\text{maxIter}}} \times 255 \f$ + +![](images/how_to_use_OpenCV_parallel_for_sqrt_scale_transformation.png) + +The green curve corresponds to a simple linear scale transformation, the blue one to a square root scale transformation +and you can observe how the lowest values will be boosted when looking at the slope at these positions. + +Parallel Mandelbrot implementation +-------------------------- + +When looking at the sequential implementation, we can notice that each pixel is computed independently. To optimize the +computation, we can perform multiple pixel calculations in parallel, by exploiting the multi-core architecture of modern +processor. To achieve this easily, we will use the OpenCV @ref cv::parallel_for_ framework. + +@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-parallel + +The first thing is to declare a custom class that inherits from @ref cv::ParallelLoopBody and to override the +`virtual void operator ()(const cv::Range& range) const`. + +The range in the `operator ()` represents the subset of pixels that will be treated by an individual thread. +This splitting is done automatically to distribuate equally the computation load. We have to convert the pixel index coordinate +to a 2D `[row, col]` coordinate. Also note that we have to keep a reference on the mat image to be able to modify in-place +the image. + +The parallel execution is called with: + +@snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-parallel-call + +Here, the range represents the total number of operations to be executed, so the total number of pixels in the image. +To set the number of threads, you can use: @ref cv::setNumThreads. You can also specify the number of splitting using the +nstripes parameter in @ref cv::parallel_for_. For instance, if your processor has 4 threads, setting `cv::setNumThreads(2)` +or setting `nstripes=2` should be the same as by default it will use all the processor threads available but will split the +workload only on two threads. + +Results +----------- + +You can find the full tutorial code [here](https://github.com/opencv/opencv/blob/master/samples/cpp/tutorial_code/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.cpp). +The performance of the parallel implementation depends of the type of CPU you have. For instance, on 4 cores / 8 threads +CPU, you can expect a speed-up of around 6.9X. There are many factors to explain why we do not achieve a speed-up of almost 8X. +Main reasons should be mostly due to: +* the overhead to create and manage the threads, +* background processes running in parallel, +* the difference between 4 hardware cores with 2 logical threads for each core and 8 hardware cores. + +The resulting image produced by the tutorial code (you can modify the code to use more iterations and assign a pixel color +depending on the escaped iteration and using a color palette to get more aesthetic images): +![Mandelbrot set with xMin=-2.1, xMax=0.6, yMin=-1.2, yMax=1.2, maxIterations=500](images/how_to_use_OpenCV_parallel_for_Mandelbrot.png) diff --git a/doc/tutorials/core/how_to_use_OpenCV_parallel_for_/images/how_to_use_OpenCV_parallel_for_640px-Mandelset_hires.png b/doc/tutorials/core/how_to_use_OpenCV_parallel_for_/images/how_to_use_OpenCV_parallel_for_640px-Mandelset_hires.png new file mode 100644 index 0000000000000000000000000000000000000000..2b63916d77ad6dc62239f764e59221894f20e136 GIT binary patch literal 16752 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