diff --git a/.appveyor.yml b/.appveyor.yml index f0c62c275..aeb6dee08 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -108,5 +108,5 @@ test_script: #- sh: ctest -j4 on_success: - - cmd: 7z a OpenMVS_x64.7z "C:\projects\openmvs\bin\bin\x64\%Configuration%\*.exe" "C:\projects\openmvs\bin\bin\x64\%Configuration%\*.dll" + - cmd: 7z a OpenMVS_x64.7z "C:\projects\openmvs\bin\bin\vc15\x64\%Configuration%\*.exe" "C:\projects\openmvs\bin\bin\vc15\x64\%Configuration%\*.dll" - cmd: appveyor PushArtifact OpenMVS_x64.7z diff --git a/CMakeLists.txt b/CMakeLists.txt index ad6583a14..6d9677e10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,6 @@ ConfigLibrary() # List configuration options SET(OpenMVS_USE_NONFREE ON CACHE BOOL "Build non-free (patented) functionality") -SET(OpenMVS_USE_EXIV2 OFF CACHE BOOL "Link and use EXIV2 library") SET(OpenMVS_USE_CERES OFF CACHE BOOL "Enable CERES optimization library") SET(OpenMVS_USE_FAST_FLOAT2INT ON CACHE BOOL "Use an optimized code to convert real numbers to int") SET(OpenMVS_USE_FAST_INVSQRT OFF CACHE BOOL "Use an optimized code to compute the inverse square root (slower in fact on modern compilers)") diff --git a/apps/DensifyPointCloud/DensifyPointCloud.cpp b/apps/DensifyPointCloud/DensifyPointCloud.cpp index bcace9c5e..b96cfae46 100644 --- a/apps/DensifyPointCloud/DensifyPointCloud.cpp +++ b/apps/DensifyPointCloud/DensifyPointCloud.cpp @@ -49,6 +49,7 @@ String strOutputFileName; String strMeshFileName; String strDenseConfigFileName; float fSampleMesh; +int thFilterPointCloud; int nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -85,22 +86,27 @@ bool Initialize(size_t argc, LPCTSTR* argv) // group of options allowed both on command line and in config file unsigned nResolutionLevel; + unsigned nMaxResolution; unsigned nMinResolution; unsigned nNumViews; unsigned nMinViewsFuse; + unsigned nOptimize; unsigned nEstimateColors; unsigned nEstimateNormals; boost::program_options::options_description config("Densify options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the dense point-cloud") - ("resolution-level", boost::program_options::value(&nResolutionLevel)->default_value(1), "how many times to scale down the images before point cloud computation") - ("min-resolution", boost::program_options::value(&nMinResolution)->default_value(640), "do not scale images lower than this resolution") - ("number-views", boost::program_options::value(&nNumViews)->default_value(4), "number of views used for depth-map estimation (0 - all neighbor views available)") - ("number-views-fuse", boost::program_options::value(&nMinViewsFuse)->default_value(3), "minimum number of images that agrees with an estimate during fusion in order to consider it inlier") - ("estimate-colors", boost::program_options::value(&nEstimateColors)->default_value(1), "estimate the colors for the dense point-cloud") - ("estimate-normals", boost::program_options::value(&nEstimateNormals)->default_value(0), "estimate the normals for the dense point-cloud") - ("sample-mesh", boost::program_options::value(&OPT::fSampleMesh)->default_value(0.f), "uniformly samples points on a mesh (0 - disabled, <0 - number of points, >0 - sample density per square unit)") + ("resolution-level", boost::program_options::value(&nResolutionLevel)->default_value(1), "how many times to scale down the images before point cloud computation") + ("max-resolution", boost::program_options::value(&nMaxResolution)->default_value(3200), "do not scale images higher than this resolution") + ("min-resolution", boost::program_options::value(&nMinResolution)->default_value(640), "do not scale images lower than this resolution") + ("number-views", boost::program_options::value(&nNumViews)->default_value(5), "number of views used for depth-map estimation (0 - all neighbor views available)") + ("number-views-fuse", boost::program_options::value(&nMinViewsFuse)->default_value(3), "minimum number of images that agrees with an estimate during fusion in order to consider it inlier") + ("optimize", boost::program_options::value(&nOptimize)->default_value(7), "filter used after depth-map estimation (0 - disabled, 1 - remove speckles, 2 - fill gaps, 4 - cross-adjust)") + ("estimate-colors", boost::program_options::value(&nEstimateColors)->default_value(2), "estimate the colors for the dense point-cloud") + ("estimate-normals", boost::program_options::value(&nEstimateNormals)->default_value(0), "estimate the normals for the dense point-cloud") + ("sample-mesh", boost::program_options::value(&OPT::fSampleMesh)->default_value(0.f), "uniformly samples points on a mesh (0 - disabled, <0 - number of points, >0 - sample density per square unit)") + ("filter-point-cloud", boost::program_options::value(&OPT::thFilterPointCloud)->default_value(0), "filter dense point-cloud based on visibility (0 - disabled)") ; // hidden options, allowed both on command line and @@ -161,13 +167,17 @@ bool Initialize(size_t argc, LPCTSTR* argv) OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_dense.mvs"); // init dense options + if (!Util::isFullPath(OPT::strDenseConfigFileName)) + OPT::strDenseConfigFileName = MAKE_PATH(OPT::strDenseConfigFileName); OPTDENSE::init(); const bool bValidConfig(OPTDENSE::oConfig.Load(OPT::strDenseConfigFileName)); OPTDENSE::update(); OPTDENSE::nResolutionLevel = nResolutionLevel; + OPTDENSE::nMaxResolution = nMaxResolution; OPTDENSE::nMinResolution = nMinResolution; OPTDENSE::nNumViews = nNumViews; OPTDENSE::nMinViewsFuse = nMinViewsFuse; + OPTDENSE::nOptimize = nOptimize; OPTDENSE::nEstimateColors = nEstimateColors; OPTDENSE::nEstimateNormals = nEstimateNormals; if (!bValidConfig) @@ -184,6 +194,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) // start memory dumper MiniDumper::Create(APPNAME, WORKING_FOLDER); #endif + + Util::Init(); return true; } @@ -233,6 +245,15 @@ int main(int argc, LPCTSTR* argv) VERBOSE("error: empty initial point-cloud"); return EXIT_FAILURE; } + if (OPT::thFilterPointCloud < 0) { + // filter point-cloud based on camera-point visibility intersections + scene.PointCloudFilter(OPT::thFilterPointCloud); + const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T("_filtered")); + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); + scene.pointcloud.Save(baseFileName+_T(".ply")); + Finalize(); + return EXIT_SUCCESS; + } if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS) { TD_TIMER_START(); if (!scene.DenseReconstruction()) diff --git a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp index 052004a2b..651b44c98 100644 --- a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp +++ b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp @@ -85,11 +85,11 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "produce this help message") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") - ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") - ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -105,7 +105,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input COLMAP folder containing cameras, images and points files OR input MVS project file") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the MVS project") ("image-folder", boost::program_options::value(&OPT::strImageFolder)->default_value(COLMAP_IMAGES_FOLDER), "folder to the undistorted images") - ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(true), "normalize intrinsics while exporting to MVS format") + ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(true), "normalize intrinsics while exporting to MVS format") ; boost::program_options::options_description cmdline_options; @@ -185,6 +185,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) // start memory dumper MiniDumper::Create(APPNAME, WORKING_FOLDER); #endif + + Util::Init(); return true; } @@ -318,7 +320,6 @@ struct Image { return true; } bool Write(std::ostream& out) const { - ASSERT(!projs.empty()); out << ID+1 << _T(" ") << q.w() << _T(" ") << q.x() << _T(" ") << q.y() << _T(" ") << q.z() << _T(" ") << t(0) << _T(" ") << t(1) << _T(" ") << t(2) << _T(" ") @@ -434,7 +435,7 @@ bool ImportScene(const String& strFolder, Interface& scene) camera.C = Interface::Pos3d(0,0,0); if (OPT::bNormalizeIntrinsics) { // normalize camera intrinsics - const double fScale(1.0/Camera::GetNormalizationScale(colmapCamera.width, colmapCamera.height)); + const REAL fScale(REAL(1)/Camera::GetNormalizationScale(colmapCamera.width, colmapCamera.height)); camera.K(0,0) *= fScale; camera.K(1,1) *= fScale; camera.K(0,2) *= fScale; @@ -555,6 +556,7 @@ bool ExportScene(const String& strFolder, const Interface& scene) // write camera list CLISTDEF0IDX(KMatrix,uint32_t) Ks; + CLISTDEF0IDX(COLMAP::Camera,uint32_t) cams; { const String filenameCameras(strFolder+COLMAP_CAMERAS); LOG_OUT() << "Writing cameras: " << filenameCameras << std::endl; @@ -614,12 +616,17 @@ bool ExportScene(const String& strFolder, const Interface& scene) K(1,1) = cam.params[1]; K(0,2) = cam.params[2]; K(1,2) = cam.params[3]; + cams.emplace_back(cam); } } // create images list COLMAP::Images images; CameraArr cameras; + float maxNumPointsSparse(0); + const float avgViewsPerPoint(3.f); + const uint32_t avgResolutionSmallView(640*480), avgResolutionLargeView(6000*4000); + const uint32_t avgPointsPerSmallView(3000), avgPointsPerLargeView(12000); { images.resize(scene.images.size()); cameras.resize(scene.images.size()); @@ -641,41 +648,125 @@ bool ExportScene(const String& strFolder, const Interface& scene) camera.R = pose.R; camera.C = pose.C; camera.ComposeP(); + const COLMAP::Camera& cam = cams[image.platformID]; + const uint32_t resolutionView(cam.width*cam.height); + const float linearFactor(float(avgResolutionLargeView-resolutionView)/(avgResolutionLargeView-avgResolutionSmallView)); + maxNumPointsSparse += (avgPointsPerSmallView+(avgPointsPerLargeView-avgPointsPerSmallView)*linearFactor)/avgViewsPerPoint; } } - // write points list - { - const String filenamePoints(strFolder+COLMAP_POINTS); - LOG_OUT() << "Writing points: " << filenamePoints << std::endl; - std::ofstream file(filenamePoints); - if (!file.good()) { - VERBOSE("error: unable to open file '%s'", filenamePoints.c_str()); - return false; + // auto-select dense or sparse mode based on number of points + const bool bSparsePointCloud(scene.vertices.size() < (size_t)maxNumPointsSparse); + if (bSparsePointCloud) { + // write points list + { + const String filenamePoints(strFolder+COLMAP_POINTS); + LOG_OUT() << "Writing points: " << filenamePoints << std::endl; + std::ofstream file(filenamePoints); + if (!file.good()) { + VERBOSE("error: unable to open file '%s'", filenamePoints.c_str()); + return false; + } + file << _T("# 3D point list with one line of data per point:") << std::endl; + file << _T("# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)") << std::endl; + for (uint32_t ID=0; ID<(uint32_t)scene.vertices.size(); ++ID) { + const Interface::Vertex& vertex = scene.vertices[ID]; + COLMAP::Point point; + point.ID = ID; + point.p = vertex.X; + for (const Interface::Vertex::View& view: vertex.views) { + COLMAP::Image& img = images[view.imageID]; + COLMAP::Point::Track track; + track.idImage = view.imageID; + track.idProj = (uint32_t)img.projs.size(); + point.tracks.push_back(track); + COLMAP::Image::Proj proj; + proj.idPoint = ID; + const Point3 X(vertex.X); + ProjectVertex_3x4_3_2(cameras[view.imageID].P.val, X.ptr(), proj.p.data()); + img.projs.push_back(proj); + } + point.c = scene.verticesColor.empty() ? Interface::Col3(255,255,255) : scene.verticesColor[ID].c; + point.e = 0; + if (!point.Write(file)) + return false; + } } - file << _T("# 3D point list with one line of data per point:") << std::endl; - file << _T("# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)") << std::endl; - for (uint32_t ID=0; ID<(uint32_t)scene.vertices.size(); ++ID) { - const Interface::Vertex& vertex = scene.vertices[ID]; - COLMAP::Point point; - point.ID = ID; - point.p = vertex.X; - for (const Interface::Vertex::View& view: vertex.views) { - COLMAP::Image& img = images[view.imageID]; - COLMAP::Point::Track track; - track.idImage = view.imageID; - track.idProj = (uint32_t)img.projs.size(); - point.tracks.push_back(track); - COLMAP::Image::Proj proj; - proj.idPoint = ID; - const Point3 X(vertex.X); - ProjectVertex_3x4_3_2(cameras[view.imageID].P.val, X.ptr(), proj.p.data()); - img.projs.push_back(proj); + + Util::ensureFolder(strFolder+COLMAP_STEREO_FOLDER); + + // write fusion list + { + const String filenameFusion(strFolder+COLMAP_FUSION); + LOG_OUT() << "Writing fusion configuration: " << filenameFusion << std::endl; + std::ofstream file(filenameFusion); + if (!file.good()) { + VERBOSE("error: unable to open file '%s'", filenameFusion.c_str()); + return false; } - point.c = scene.verticesColor.empty() ? Interface::Col3(255,255,255) : scene.verticesColor[ID].c; - point.e = 0; - if (!point.Write(file)) + for (const COLMAP::Image& img: images) { + if (img.projs.empty()) + continue; + file << img.name << std::endl; + if (file.fail()) + return false; + } + } + + // write patch-match list + { + const String filenameFusion(strFolder+COLMAP_PATCHMATCH); + LOG_OUT() << "Writing patch-match configuration: " << filenameFusion << std::endl; + std::ofstream file(filenameFusion); + if (!file.good()) { + VERBOSE("error: unable to open file '%s'", filenameFusion.c_str()); return false; + } + for (const COLMAP::Image& img: images) { + if (img.projs.empty()) + continue; + file << img.name << std::endl; + if (file.fail()) + return false; + file << _T("__auto__, 20") << std::endl; + if (file.fail()) + return false; + } + } + + Util::ensureFolder(strFolder+COLMAP_STEREO_CONSISTENCYGRAPHS_FOLDER); + Util::ensureFolder(strFolder+COLMAP_STEREO_DEPTHMAPS_FOLDER); + Util::ensureFolder(strFolder+COLMAP_STEREO_NORMALMAPS_FOLDER); + } else { + // export dense point-cloud + const String filenameDensePoints(strFolder+COLMAP_DENSE_POINTS); + const String filenameDenseVisPoints(strFolder+COLMAP_DENSE_POINTS_VISIBILITY); + LOG_OUT() << "Writing points: " << filenameDensePoints << " and " << filenameDenseVisPoints << std::endl; + File file(filenameDenseVisPoints, File::WRITE, File::CREATE | File::TRUNCATE); + if (!file.isOpen()) { + VERBOSE("error: unable to write file '%s'", filenameDenseVisPoints.c_str()); + return false; + } + const uint64_t numPoints(scene.vertices.size()); + file.write(&numPoints, sizeof(uint64_t)); + PointCloud pointcloud; + for (size_t i=0; i(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") - ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") - ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -157,6 +158,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) // start memory dumper MiniDumper::Create(APPNAME, WORKING_FOLDER); #endif + + Util::Init(); return true; } @@ -248,7 +251,6 @@ void UndistortImage(const Camera& camera, const REAL& k1, const Image8U3 imgIn, typedef Sampler::Cubic Sampler; const Sampler sampler; Point2f pt; - Pixel32F clr; for (int v=0; v(sampler, pt); - col.r = CLAMP(ROUND2INT(clr.r), 0, 255); - col.g = CLAMP(ROUND2INT(clr.g), 0, 255); - col.b = CLAMP(ROUND2INT(clr.b), 0, 255); + col = imgIn.sample(sampler, pt).cast(); } else { // set to black col = Pixel8U::BLACK; @@ -274,16 +273,9 @@ void UndistortImage(const Camera& camera, const REAL& k1, const Image8U3 imgIn, } } // namespace MVS -int main(int argc, LPCTSTR* argv) -{ - #ifdef _DEBUGINFO - // set _crtBreakAlloc index to stop in at allocation - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); - #endif - - if (!Initialize(argc, argv)) - return EXIT_FAILURE; +int ImportSceneVSFM() +{ TD_TIMER_START(); // read VisualSFM input data @@ -295,7 +287,7 @@ int main(int argc, LPCTSTR* argv) std::vector names; std::vector ptc; if (!PBA::LoadModelFile(MAKE_PATH_SAFE(OPT::strInputFileName), cameras, vertices, measurements, correspondingPoint, correspondingView, names, ptc)) - return EXIT_FAILURE; + return false; // convert data from VisualSFM to OpenMVS MVS::Scene scene(OPT::nMaxThreads); @@ -309,7 +301,7 @@ int main(int argc, LPCTSTR* argv) image.name = MAKE_PATH_FULL(WORKING_FOLDER_FULL, image.name); if (!image.ReloadImage(0, false)) { LOG("error: can not read image %s", image.name.c_str()); - return EXIT_FAILURE; + return false; } // set camera image.platformID = scene.platforms.GetSize(); @@ -322,10 +314,10 @@ int main(int argc, LPCTSTR* argv) camera.C = CMatrix::ZERO; // normalize camera intrinsics const REAL fScale(REAL(1)/MVS::Camera::GetNormalizationScale(image.width, image.height)); - camera.K(0, 0) *= fScale; - camera.K(1, 1) *= fScale; - camera.K(0, 2) *= fScale; - camera.K(1, 2) *= fScale; + camera.K(0,0) *= fScale; + camera.K(1,1) *= fScale; + camera.K(0,2) *= fScale; + camera.K(1,2) *= fScale; // set pose image.poseID = platform.poses.GetSize(); MVS::Platform::Pose& pose = platform.poses.AddEmpty(); @@ -374,7 +366,7 @@ int main(int argc, LPCTSTR* argv) #pragma omp flush (bAbort) continue; #else - return EXIT_FAILURE; + return false; #endif } MVS::UndistortImage(imageData.camera, cameraNVM.GetNormalizedMeasurementDistortion(), imageData.image, imageData.image); @@ -386,21 +378,143 @@ int main(int argc, LPCTSTR* argv) #pragma omp flush (bAbort) continue; #else - return EXIT_FAILURE; + return false; #endif } imageData.ReleaseImage(); } #ifdef _USE_OPENMP if (bAbort) - return EXIT_SUCCESS; + return false; #endif progress.close(); + VERBOSE("Input data imported: %u cameras, %u poses, %u images, %u vertices (%s)", cameras.size(), cameras.size(), cameras.size(), vertices.size(), TD_TIMER_GET_FMT().c_str()); + // write OpenMVS input data - scene.SaveInterface(MAKE_PATH_SAFE(OPT::strOutputFileName)); + return scene.SaveInterface(MAKE_PATH_SAFE(OPT::strOutputFileName)); +} - VERBOSE("Input data imported: %u cameras, %u poses, %u images, %u vertices (%s)", cameras.size(), cameras.size(), cameras.size(), vertices.size(), TD_TIMER_GET_FMT().c_str()); + +template +void _ImageListParseP(const LPSTR* argv, TMatrix& P) +{ + // read projection matrix + P(0,0) = String::FromString(argv[0]); + P(0,1) = String::FromString(argv[1]); + P(0,2) = String::FromString(argv[2]); + P(0,3) = String::FromString(argv[3]); + P(1,0) = String::FromString(argv[4]); + P(1,1) = String::FromString(argv[5]); + P(1,2) = String::FromString(argv[6]); + P(1,3) = String::FromString(argv[7]); + P(2,0) = String::FromString(argv[8]); + P(2,1) = String::FromString(argv[9]); + P(2,2) = String::FromString(argv[10]); + P(2,3) = String::FromString(argv[11]); +} + +int ImportSceneCMPMVS() +{ + TD_TIMER_START(); + + MVS::Scene scene(OPT::nMaxThreads); + + // read CmpMVS input data as a list of images and their projection matrices + std::ifstream iFilein(MAKE_PATH_SAFE(OPT::strInputFileName)); + if (!iFilein.is_open()) + return false; + while (iFilein.good()) { + String strImageName; + std::getline(iFilein, strImageName); + if (strImageName.empty()) + continue; + if (!File::access(MAKE_PATH_SAFE(strImageName))) + return false; + const String strImageNameP(Util::getFileFullName(strImageName)+"_P.txt"); + std::ifstream iFileP(MAKE_PATH_SAFE(strImageNameP)); + if (!iFileP.is_open()) + return false; + String strP; int numLines(0); + while (iFileP.good()) { + String line; + std::getline(iFileP, line); + if (strImageName.empty()) + break; + if (strP.empty()) + strP = line; + else + strP += _T(' ') + line; + ++numLines; + } + if (numLines != 3) + return false; + PMatrix P; + size_t argc; + CAutoPtrArr argv(Util::CommandLineToArgvA(strP, argc)); + if (argc != 12) + return false; + _ImageListParseP(argv, P); + KMatrix K; RMatrix R; CMatrix C; + MVS::DecomposeProjectionMatrix(P, K, R, C); + // set image + MVS::Image& image = scene.images.AddEmpty(); + image.name = strImageName; + Util::ensureUnifySlash(image.name); + image.name = MAKE_PATH_FULL(WORKING_FOLDER_FULL, image.name); + if (!image.ReloadImage(0, false)) { + LOG("error: can not read image %s", image.name.c_str()); + return false; + } + // set camera + image.platformID = scene.platforms.GetSize(); + MVS::Platform& platform = scene.platforms.AddEmpty(); + MVS::Platform::Camera& camera = platform.cameras.AddEmpty(); + image.cameraID = 0; + camera.K = K; + camera.R = RMatrix::IDENTITY; + camera.C = CMatrix::ZERO; + // normalize camera intrinsics + const REAL fScale(REAL(1)/MVS::Camera::GetNormalizationScale(image.width, image.height)); + camera.K(0, 0) *= fScale; + camera.K(1, 1) *= fScale; + camera.K(0, 2) *= fScale; + camera.K(1, 2) *= fScale; + // set pose + image.poseID = platform.poses.GetSize(); + MVS::Platform::Pose& pose = platform.poses.AddEmpty(); + pose.R = R; + pose.C = C; + image.UpdateCamera(scene.platforms); + ++scene.nCalibratedImages; + } + + VERBOSE("Input data imported: %u images (%s)", scene.images.size(), TD_TIMER_GET_FMT().c_str()); + + // write OpenMVS input data + return scene.SaveInterface(MAKE_PATH_SAFE(OPT::strOutputFileName)); +} + + +int main(int argc, LPCTSTR* argv) +{ + #ifdef _DEBUGINFO + // set _crtBreakAlloc index to stop in at allocation + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); + #endif + + if (!Initialize(argc, argv)) + return EXIT_FAILURE; + + const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); + if (strInputFileNameExt == VSFM_EXT) { + if (!ImportSceneVSFM()) + return EXIT_FAILURE; + } else + if (strInputFileNameExt == CMPMVS_EXT) { + if (!ImportSceneCMPMVS()) + return EXIT_FAILURE; + } Finalize(); return EXIT_SUCCESS; diff --git a/apps/ReconstructMesh/ReconstructMesh.cpp b/apps/ReconstructMesh/ReconstructMesh.cpp index 14d325e01..8651715e0 100644 --- a/apps/ReconstructMesh/ReconstructMesh.cpp +++ b/apps/ReconstructMesh/ReconstructMesh.cpp @@ -189,6 +189,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) // start memory dumper MiniDumper::Create(APPNAME, WORKING_FOLDER); #endif + + Util::Init(); return true; } diff --git a/apps/RefineMesh/RefineMesh.cpp b/apps/RefineMesh/RefineMesh.cpp index a8c65136d..1cbd6e227 100644 --- a/apps/RefineMesh/RefineMesh.cpp +++ b/apps/RefineMesh/RefineMesh.cpp @@ -195,6 +195,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) // start memory dumper MiniDumper::Create(APPNAME, WORKING_FOLDER); #endif + + Util::Init(); return true; } diff --git a/apps/TextureMesh/TextureMesh.cpp b/apps/TextureMesh/TextureMesh.cpp index ee99b89b0..9e610a8ba 100644 --- a/apps/TextureMesh/TextureMesh.cpp +++ b/apps/TextureMesh/TextureMesh.cpp @@ -179,6 +179,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) // start memory dumper MiniDumper::Create(APPNAME, WORKING_FOLDER); #endif + + Util::Init(); return true; } diff --git a/apps/Viewer/Scene.cpp b/apps/Viewer/Scene.cpp index 703ce2625..8e63dbf9a 100644 --- a/apps/Viewer/Scene.cpp +++ b/apps/Viewer/Scene.cpp @@ -42,68 +42,35 @@ using namespace VIEWER; // S T R U C T S /////////////////////////////////////////////////// -template -struct LeafAvgSize { - typedef typename OCTREE::IDX_TYPE IDX; - const IDX maxNumItems; - double radius; - size_t count; - LeafAvgSize(IDX cellNumItems) : maxNumItems(cellNumItems/2), radius(0), count(0) {} - void operator () (const typename OCTREE::CELL_TYPE& cell, typename OCTREE::Type r) { - if (cell.GetNumItems() > maxNumItems) { - radius += r; - count++; - } - } - operator float () const { - return (float)(radius / (double)count); - } -}; +struct IndexDist { + IDX idx; + REAL dist; -template -struct TIntersectRay { - typedef SCENE Scene; - typedef OCTREE Octree; - typedef typename OCTREE::IDX_TYPE IDX; - - struct IndexDist { - IDX idx; - REAL dist; + inline IndexDist() : dist(REAL(FLT_MAX)) {} + inline bool IsValid() const { return dist < REAL(FLT_MAX); } +}; - inline IndexDist() : dist(REAL(FLT_MAX)) {} - inline bool IsValid() const { return dist < REAL(FLT_MAX); } - }; +struct IntersectRayPoints { + typedef MVS::PointCloud Scene; + typedef VIEWER::Scene::OctreePoints Octree; + typedef typename Octree::IDX_TYPE IDX; + typedef TCone Cone3; + typedef TConeIntersect Cone3Intersect; const Scene& scene; - const Octree& octree; - const Ray3& ray; + const Cone3 cone; + const Cone3Intersect coneIntersect; + const unsigned minViews; IndexDist pick; - TIntersectRay(const Scene& _scene, const Octree& _octree, const Ray3& _ray) - : - scene(_scene), - octree(_octree), - ray(_ray) + IntersectRayPoints(const Octree& octree, const Ray3& _ray, const Scene& _scene, unsigned _minViews) + : scene(_scene), cone(_ray, D2R(REAL(0.5))), coneIntersect(cone), minViews(_minViews) { + octree.Collect(*this, *this); } inline bool Intersects(const typename Octree::POINT_TYPE& center, typename Octree::Type radius) const { - return ray.Intersects(AABB3f(center, radius)); - } -}; - -struct IntersectRayPoints : public TIntersectRay { - using typename TIntersectRay::Scene; - using typename TIntersectRay::Octree; - using typename TIntersectRay::IDX; - - const REAL pointSize; - const unsigned minViews; - - IntersectRayPoints(const Scene& _scene, const Octree& _octree, REAL _pointSize, unsigned _minViews, const Ray3& _ray) - : TIntersectRay(_scene, _octree, _ray), pointSize(_pointSize), minViews(_minViews) - { - octree.Collect(*this, *this); + return coneIntersect(Sphere3(center.cast(), REAL(radius)*SQRT_3)); } void operator () (const IDX* idices, IDX size) { @@ -114,7 +81,7 @@ struct IntersectRayPoints : public TIntersectRay(X), pointSize), dist)) { + if (coneIntersect.Classify(Cast(X), dist) == VISIBLE) { ASSERT(dist >= 0); if (pick.dist > dist) { pick.dist = dist; @@ -125,17 +92,25 @@ struct IntersectRayPoints : public TIntersectRay { - using typename TIntersectRay::Scene; - using typename TIntersectRay::Octree; - using typename TIntersectRay::IDX; +struct IntersectRayMesh { + typedef MVS::Mesh Scene; + typedef VIEWER::Scene::OctreeMesh Octree; + typedef typename Octree::IDX_TYPE IDX; - IntersectRayMesh(const Scene& _scene, const Octree& _octree, const Ray3& _ray) - : TIntersectRay(_scene, _octree, _ray) + const Scene& scene; + const Ray3& ray; + IndexDist pick; + + IntersectRayMesh(const Octree& octree, const Ray3& _ray, const Scene& _scene) + : scene(_scene), ray(_ray) { octree.Collect(*this, *this); } + inline bool Intersects(const typename Octree::POINT_TYPE& center, typename Octree::Type radius) const { + return ray.Intersects(AABB3f(center, radius)); + } + void operator () (const IDX* idices, IDX size) { // store all intersected faces only once typedef std::unordered_set FaceSet; @@ -204,18 +179,12 @@ class EVTComputeOctree : public Event MVS::Scene& scene = pScene->scene; if (!scene.mesh.IsEmpty()) { Scene::OctreeMesh octMesh(scene.mesh.vertices); - LeafAvgSize size(128); - octMesh.ParseCells(size); scene.mesh.ListIncidenteFaces(); pScene->octMesh.Swap(octMesh); - pScene->avgScale = size; } else if (!scene.pointcloud.IsEmpty()) { Scene::OctreePoints octPoints(scene.pointcloud.points); - LeafAvgSize size(256); - octPoints.ParseCells(size); pScene->octPoints.Swap(octPoints); - pScene->avgScale = size; } return true; } @@ -265,7 +234,6 @@ void Scene::Empty() images.Release(); scene.Release(); sceneName.clear(); - avgScale = 0; } void Scene::Release() { @@ -654,7 +622,7 @@ void Scene::CastRay(const Ray3& ray, int action) } else if (!octMesh.IsEmpty()) { // find ray intersection with the mesh - const IntersectRayMesh intRay(scene.mesh, octMesh, ray); + const IntersectRayMesh intRay(octMesh, ray, scene.mesh); if (intRay.pick.IsValid()) { const MVS::Mesh::Face& face = scene.mesh.faces[(MVS::Mesh::FIndex)intRay.pick.idx]; window.selectionPoints[0] = scene.mesh.vertices[face[0]]; @@ -675,7 +643,7 @@ void Scene::CastRay(const Ray3& ray, int action) } else if (!octPoints.IsEmpty()) { // find ray intersection with the points - const IntersectRayPoints intRay(scene.pointcloud, octPoints, avgScale*0.1f, window.minViews, ray); + const IntersectRayPoints intRay(octPoints, ray, scene.pointcloud, window.minViews); if (intRay.pick.IsValid()) { window.selectionPoints[0] = window.selectionPoints[3] = scene.pointcloud.points[intRay.pick.idx]; window.selectionType = Window::SEL_POINT; diff --git a/apps/Viewer/Scene.h b/apps/Viewer/Scene.h index 043495273..0d31fa076 100644 --- a/apps/Viewer/Scene.h +++ b/apps/Viewer/Scene.h @@ -62,7 +62,6 @@ class Scene OctreePoints octPoints; OctreeMesh octMesh; - float avgScale; GLuint listPointCloud; GLuint listMesh; @@ -81,7 +80,7 @@ class Scene void ReleaseMesh(); inline bool IsValid() const { return window.IsValid(); } inline bool IsOpen() const { return IsValid() && !scene.IsEmpty(); } - inline bool IsOctreeValid() const { return avgScale > 0; } + inline bool IsOctreeValid() const { return !octPoints.IsEmpty() || !octMesh.IsEmpty(); } bool Init(int width, int height, LPCTSTR windowName, LPCTSTR fileName=NULL, LPCTSTR meshFileName=NULL); bool Open(LPCTSTR fileName, LPCTSTR meshFileName=NULL); diff --git a/apps/Viewer/Viewer.cpp b/apps/Viewer/Viewer.cpp index 028675a6a..2470e3abf 100644 --- a/apps/Viewer/Viewer.cpp +++ b/apps/Viewer/Viewer.cpp @@ -181,6 +181,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) // start memory dumper MiniDumper::Create(APPNAME, WORKING_FOLDER); #endif + + Util::Init(); return true; } diff --git a/apps/Viewer/Window.cpp b/apps/Viewer/Window.cpp index f39784f61..6a9ada4f1 100644 --- a/apps/Viewer/Window.cpp +++ b/apps/Viewer/Window.cpp @@ -292,7 +292,7 @@ void Window::MouseButton(int button, int action, int /*mods*/) // 4d World Coordinates const Mat4 invV(V.inverse()); ASSERT(ISEQUAL(invV(3,3),1.0)); - Eigen::Vector3d start(invV.topRightCorner<3,1>()); + const Eigen::Vector3d start(invV.topRightCorner<3,1>()); const Eigen::Vector4d ray_wor(invV*ray_eye); const Eigen::Vector3d dir(ray_wor.topRows<3>().normalized()); clbkRayScene(Ray3d(start, dir), action); diff --git a/build/Modules/FindCGAL.cmake b/build/Modules/FindCGAL.cmake index 325d36d49..fcdc7b4fd 100644 --- a/build/Modules/FindCGAL.cmake +++ b/build/Modules/FindCGAL.cmake @@ -64,7 +64,16 @@ set(CGAL_VERSION "") if(EXISTS "${CGAL_DIR}" AND NOT "${CGAL_DIR}" STREQUAL "") if(EXISTS "${CGAL_DIR}/CGALConfig.cmake") include("${CGAL_DIR}/CGALConfig.cmake") - set(CGAL_LIBS ${CGAL_LIBS} ${CGAL_LIBRARIES} ${CGAL_LIBRARY} ${CGAL_Core_LIBRARY} ${CGAL_ImageIO_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${CGAL_Core_3RD_PARTY_LIBRARIES} ${CGAL_ImageIO_3RD_PARTY_LIBRARIES} ${MPFR_LIBRARIES} ${GMP_LIBRARIES} ${ZLIB_LIBRARIES}) + set(CGAL_TARGET_LIBS "") + if(TARGET CGAL::CGAL) + get_target_property(CGAL_TARGET_LIBS CGAL::CGAL INTERFACE_LINK_LIBRARIES) + elseif(TARGET CGAL) + get_target_property(CGAL_TARGET_LIBS CGAL INTERFACE_LINK_LIBRARIES) + endif() + if(NOT CGAL_TARGET_LIBS) + set(CGAL_TARGET_LIBS "") + endif() + set(CGAL_LIBS ${CGAL_LIBS} ${CGAL_LIBRARIES} ${CGAL_LIBRARY} ${CGAL_TARGET_LIBS} ${CGAL_Core_LIBRARY} ${CGAL_ImageIO_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${CGAL_Core_3RD_PARTY_LIBRARIES} ${CGAL_ImageIO_3RD_PARTY_LIBRARIES} ${MPFR_LIBRARIES} ${GMP_LIBRARIES} ${ZLIB_LIBRARIES}) set(CGAL_VERSION "${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}") else() set(CGAL_INCLUDE_DIRS "${CGAL_DIR}/include" "${CGAL_DIR}/auxiliary/gmp/include") diff --git a/build/Templates/ConfigLocal.h.in b/build/Templates/ConfigLocal.h.in index d81e950ab..5fbc77b78 100644 --- a/build/Templates/ConfigLocal.h.in +++ b/build/Templates/ConfigLocal.h.in @@ -34,9 +34,6 @@ // TIFF codec #cmakedefine _USE_TIFF -// EXIV2 parser -#cmakedefine _USE_EXIV2 - // OpenGL support #cmakedefine _USE_OPENGL diff --git a/build/Utils.cmake b/build/Utils.cmake index 6c33c1f74..d24b8453b 100644 --- a/build/Utils.cmake +++ b/build/Utils.cmake @@ -174,7 +174,11 @@ macro(ComposePackageLibSuffix) set(PACKAGE_LIB_SUFFIX_DBG "") set(PACKAGE_LIB_SUFFIX_REL "") if(MSVC) - if("${MSVC_VERSION}" STREQUAL "1900") + if("${MSVC_VERSION}" STREQUAL "1921") + set(PACKAGE_LIB_SUFFIX "/vc16") + elseif("${MSVC_VERSION}" STREQUAL "1916") + set(PACKAGE_LIB_SUFFIX "/vc15") + elseif("${MSVC_VERSION}" STREQUAL "1900") set(PACKAGE_LIB_SUFFIX "/vc14") elseif("${MSVC_VERSION}" STREQUAL "1800") set(PACKAGE_LIB_SUFFIX "/vc12") @@ -421,8 +425,32 @@ macro(optimize_default_compiler_settings) set(BUILD_EXTRA_EXE_LINKER_FLAGS_RELEASE "") set(BUILD_EXTRA_EXE_LINKER_FLAGS_DEBUG "") - # enable C++11 support - set(CMAKE_CXX_STANDARD 11) + # try to enable C++14/C++11 support + if(${CMAKE_VERSION} VERSION_LESS "3.8.2") + check_cxx_compiler_flag(--std=c++14 SUPPORTS_STD_CXX14) + check_cxx_compiler_flag(--std=c++11 SUPPORTS_STD_CXX11) + if(SUPPORTS_STD_CXX14) + set(CMAKE_CXX_STANDARD 14) + elseif(SUPPORTS_STD_CXX11) + set(CMAKE_CXX_STANDARD 11) + endif() + else() + foreach(i ${CMAKE_CXX_COMPILE_FEATURES}) + if("${i}" STREQUAL "cxx_std_14") + set(CMAKE_CXX_STANDARD 14) + break() + elseif("${i}" STREQUAL "cxx_std_11") + set(CMAKE_CXX_STANDARD 11) + endif() + endforeach() + endif() + if(CLANG AND (CMAKE_CXX_STANDARD EQUAL 11 OR CMAKE_CXX_STANDARD EQUAL 14)) + set(CMAKE_EXE_LINKER_FLAGS "-stdlib=libc++") + add_extra_compiler_option(-stdlib=libc++) + endif() + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + message("Compiling with C++${CMAKE_CXX_STANDARD}") if(MINGW) # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838 diff --git a/libs/Common/Config.h b/libs/Common/Config.h index 60783fe46..779b2bb3e 100644 --- a/libs/Common/Config.h +++ b/libs/Common/Config.h @@ -194,6 +194,10 @@ # define FORCEINLINE inline #endif +#ifndef _SUPPORT_CPP11 +# define constexpr inline +#endif + #define SAFE_DELETE(p) { if (p!=NULL) { delete (p); (p)=NULL; } } #define SAFE_DELETE_ARR(p) { if (p!=NULL) { delete [] (p); (p)=NULL; } } #define SAFE_FREE(p) { if (p!=NULL) { free(p); (p)=NULL; } } diff --git a/libs/Common/List.h b/libs/Common/List.h index e94672fd1..794b0a2a4 100644 --- a/libs/Common/List.h +++ b/libs/Common/List.h @@ -70,8 +70,9 @@ #define CLISTDEF0(TYPE) SEACAVE::cList< TYPE, const TYPE&, 0 > #define CLISTDEF2(TYPE) SEACAVE::cList< TYPE, const TYPE&, 2 > -#define CLISTDEFIDX(TYPE,IDXTYPE) SEACAVE::cList< TYPE, const TYPE&, 1, 16, IDXTYPE > #define CLISTDEF0IDX(TYPE,IDXTYPE) SEACAVE::cList< TYPE, const TYPE&, 0, 16, IDXTYPE > +#define CLISTDEFIDX(TYPE,IDXTYPE) SEACAVE::cList< TYPE, const TYPE&, 1, 16, IDXTYPE > +#define CLISTDEF2IDX(TYPE,IDXTYPE) SEACAVE::cList< TYPE, const TYPE&, 2, 16, IDXTYPE > namespace SEACAVE { @@ -109,7 +110,6 @@ class cList typedef ARG_TYPE ArgType; typedef IDX_TYPE IDX; typedef int (STCALL *TFncCompare)(const void* elem, const void* key); //returns 0 if equal, otherwise <0 or >0 respectively - typedef bool (STCALL *TFncCompareBool)(ARG_TYPE elem, ARG_TYPE key); //returns true if the two elements are strict in weak ordering // construct an empty list inline cList() : _size(0), _vectorSize(0), _vector(NULL) @@ -627,40 +627,57 @@ class cList return std::accumulate(Begin(), End(), TYPE(0)) / _size; } + inline TYPE& PartialSort(IDX index) + { + TYPE* const nth(Begin()+index); + std::partial_sort(Begin(), nth, End()); + return *nth; + } + inline void Sort() { std::sort(Begin(), End()); } - inline void Sort(TFncCompareBool xCompare) + template + inline void Sort(const Functor& functor) { - std::sort(Begin(), End(), xCompare); + std::sort(Begin(), End(), functor); } inline bool IsSorted() const { + #ifdef _SUPPORT_CPP11 + return std::is_sorted(Begin(), End()); + #else if (_size < 2) return true; IDX i = _size-1; do { ARG_TYPE elem1 = _vector[i]; ARG_TYPE elem0 = _vector[--i]; - if (!(elem0 < elem1 || elem0 == elem1)) + if (elem1 < elem0) return false; } while (i > 0); return true; + #endif } - inline bool IsSorted(TFncCompareBool xCompare) const + template + inline bool IsSorted(const Functor& functor) const { + #ifdef _SUPPORT_CPP11 + return std::is_sorted(Begin(), End(), functor); + #else if (_size < 2) return true; IDX i = _size-1; do { ARG_TYPE elem1 = _vector[i]; ARG_TYPE elem0 = _vector[--i]; - if (!xCompare(elem0, elem1)) + if (functor(elem1, elem0)) return false; } while (i > 0); return true; + #endif } inline std::pair InsertSortUnique(ARG_TYPE elem) @@ -1273,7 +1290,7 @@ class cList TYPE* _vector; public: - static const IDX NO_INDEX; + enum : IDX { NO_INDEX = DECLARE_NO_INDEX(IDX) }; #if _USE_VECTORINTERFACE != 0 public: @@ -1284,6 +1301,9 @@ class cList typedef const value_type& const_reference; typedef std::vector VectorType; inline cList(const VectorType& rList) { CopyOf(&rList[0], rList.size()); } + #ifdef _SUPPORT_CPP11 + inline cList(std::initializer_list l) : _size(0), _vectorSize((IDX)l.size()), _vector(NULL) { ASSERT(l.size() -const typename cList::IDX -cList::NO_INDEX(DECLARE_NO_INDEX(IDX)); /*----------------------------------------------------------------*/ template @@ -1346,7 +1363,6 @@ inline bool ValidIDX(const IDX_TYPE& idx) { // some test functions // run cListTest(99999); #if 0 -static bool SortIntTest(int a, int b) { return a arrR; cList arr0; @@ -1362,7 +1378,7 @@ inline bool cListTestIter(unsigned elems) { arrC.Insert(e); } std::sort(arrR.begin(), arrR.end()); - arrC.Sort(SortIntTest); + arrC.Sort([](int a, int b) { return a(); +} +FORCEINLINE double randomd() { + return RANDOM(); +} +FORCEINLINE long double randomld() { + return RANDOM(); +} +STATIC_ASSERT(RAND_MAX < 2147483648); // integer randomRange assumes this is capped +template +FORCEINLINE T randomRange(T nMin, T nMax) { + ASSERT(nMin <= nMax && nMax-nMin+1 < 8589934596); // not to overflow a uint64_t + return nMin + T((uint64_t(nMax-nMin)*RAND()+RAND_MAX/2)/RAND_MAX); +} +template<> +FORCEINLINE float randomRange(float fMin, float fMax) { + return fMin + (fMax - fMin) * random(); +} +template<> +FORCEINLINE double randomRange(double fMin, double fMax) { + return fMin + (fMax - fMin) * randomd(); +} +template<> +FORCEINLINE long double randomRange(long double fMin, long double fMax) { + return fMin + (fMax - fMin) * randomld(); +} +template +FORCEINLINE T randomMeanRange(T mean, T delta/*=(max-min)/2*/) { + ASSERT(delta >= 0 && delta*2+1 < 8589934596); // not to overflow a uint64_t + return (mean + T((uint64_t(delta)*2*RAND()+RAND_MAX/2)/RAND_MAX)) - delta; +} +template<> +FORCEINLINE float randomMeanRange(float mean, float delta/*=(max-min)/2*/) { + return mean + delta * (2.f * random() - 1.f); +} +template<> +FORCEINLINE double randomMeanRange(double mean, double delta/*=(max-min)/2*/) { + return mean + delta * (2.0 * randomd() - 1.0); +} +template<> +FORCEINLINE long double randomMeanRange(long double mean, long double delta/*=(max-min)/2*/) { + return mean + delta * (2.0L * randomld() - 1.0L); +} +// gaussian random number generation +template +FORCEINLINE T gaussian(T val, T sigma) { + return EXP(-SQUARE(val/sigma)/2)/(SQRT(T(M_PI*2))*sigma); +} +template +FORCEINLINE T randomGaussian(T mean, T sigma) { + T x, y, r2; + do { + x = T(-1) + T(2) * RANDOM(); + y = T(-1) + T(2) * RANDOM(); + r2 = x * x + y * y; + } while (r2 > T(1) || r2 == T(0)); + return mean + sigma * y * SQRT(T(-2) * LOGN(r2) / r2); +} +template +FORCEINLINE T randomGaussian(T sigma) { + return randomGaussian(T(0), sigma); +} +FORCEINLINE float randomGaussian() { + return randomGaussian(0.f, 1.f); +} +FORCEINLINE double randomGaussiand() { + return randomGaussian(0.0, 1.0); +} +FORCEINLINE long double randomGaussianld() { + return randomGaussian(0.0L, 1.0L); +} +/*----------------------------------------------------------------*/ + + +// Encapsulates state for random number generation +// based on C++11 random number generator functionality +struct Random : std::mt19937 { + typedef std::mt19937 generator_type; + + Random() : generator_type(std::random_device()()) {} + Random(result_type seed) : generator_type(seed) {} + + // integer randomRange assumes this is capped + STATIC_ASSERT(max() < 4294967296); + + // returns a uniform random number in the range [0, 1] + template + FORCEINLINE typename std::enable_if::value, T>::type random() { + return (T)random()/(T)max(); + } + // returns a uniform random number in the range [0, max()] + template + FORCEINLINE typename std::enable_if::value, T>::type random() { + return (T)operator()(); + } + + // returns a uniform random number in the range [nMin, nMax] + template + FORCEINLINE typename std::enable_if::value, T>::type randomRange(T nMin, T nMax) { + return nMin + (nMax-nMin) * random(); + } + template + FORCEINLINE typename std::enable_if::value, T>::type randomRange(T nMin, T nMax) { + ASSERT(nMin <= nMax && nMax-nMin+1 < 4294967297); // not to overflow a uint64_t + return nMin + (T)(((uint64_t)(nMax-nMin) * random() + max()/2)/max()); + } + + // returns a uniform random number in the range [mean-delta, mean+delta] + template + FORCEINLINE typename std::enable_if::value, T>::type randomMeanRange(T mean, T delta/*=(max-min)/2*/) { + return mean + delta * (T(2) * random() - T(1)); + } + template + FORCEINLINE typename std::enable_if::value, T>::type randomMeanRange(T mean, T delta/*=(max-min)/2*/) { + ASSERT(delta >= 0 && delta*T(2)+1 < 4294967297); // not to overflow a uint64_t + return mean + (T)(((uint64_t)delta*2 * random() + max()/2)/max()) - delta; + } + + // returns a uniform random number in the range [nMin, nMax] using std implementation + template + FORCEINLINE typename std::enable_if::value, T>::type randomUniform(T nMin, T nMax) { + return std::uniform_real_distribution(nMin, nMax)(*this); + } + template + FORCEINLINE typename std::enable_if::value, T>::type randomUniform(T nMin, T nMax) { + return std::uniform_int_distribution(nMin, nMax)(*this); + } + + // returns a gaussian random number using std implementation + template + FORCEINLINE T randomGaussian(T mean, T stddev) { + return std::normal_distribution(mean, stddev)(*this); + } +}; +/*----------------------------------------------------------------*/ + +} // namespace SEACAVE + +#endif // __SEACAVE_RANDOM_H__ diff --git a/libs/Common/Ray.h b/libs/Common/Ray.h index 802778e52..fd1989e6a 100644 --- a/libs/Common/Ray.h +++ b/libs/Common/Ray.h @@ -30,7 +30,7 @@ class TTriangle typedef Eigen::Matrix POINT; typedef SEACAVE::TAABB AABB; typedef SEACAVE::TPlane PLANE; - enum { numScalar = (2*DIMS) }; + enum { numScalar = (3*DIMS) }; POINT a, b, c; // triangle vertices @@ -129,6 +129,97 @@ class TRay /*----------------------------------------------------------------*/ +// Basic cylinder class +template +class TCylinder +{ + STATIC_ASSERT(DIMS > 1 && DIMS <= 3); + +public: + typedef Eigen::Matrix VECTOR; + typedef Eigen::Matrix POINT; + typedef SEACAVE::TRay RAY; + typedef SEACAVE::TSphere SPHERE; + enum { numScalar = (2*DIMS+2) }; + + RAY ray; // central ray defining starting the point and direction + TYPE radius; // cylinder radius + TYPE minHeight; // cylinder heights satisfying: + TYPE maxHeight; // std::numeric_limits::lowest() <= minHeight <= 0 < maxHeight <= std::numeric_limits::max() + + //--------------------------------------- + + inline TCylinder() {} + + inline TCylinder(const RAY& _ray, TYPE _radius, TYPE _minHeight=TYPE(0), TYPE _maxHeight=std::numeric_limits::max()) + : ray(_ray), radius(_radius), minHeight(_minHeight), maxHeight(_maxHeight) { ASSERT(TYPE(0) >= minHeight && minHeight < maxHeight); } + + inline bool IsFinite() const { return maxHeight < std::numeric_limits::max() && minHeight > std::numeric_limits::lowest(); } + inline TYPE GetLength() const { return maxHeight - minHeight; } + + bool Intersects(const SPHERE&) const; + + inline GCLASS Classify(const POINT&, TYPE& t) const; +}; // class TCylinder +/*----------------------------------------------------------------*/ + + +// Basic cone class +template +class TCone +{ + STATIC_ASSERT(DIMS > 1 && DIMS <= 3); + +public: + typedef Eigen::Matrix VECTOR; + typedef Eigen::Matrix POINT; + typedef SEACAVE::TRay RAY; + typedef SEACAVE::TSphere SPHERE; + enum { numScalar = (2*DIMS+3) }; + + RAY ray; // ray origin is the cone vertex and ray direction is the cone axis direction + TYPE angle; // cone angle, in radians, must be inside [0, pi/2] + TYPE minHeight; // heights satisfying: + TYPE maxHeight; // 0 <= minHeight < maxHeight <= std::numeric_limits::max() + + //--------------------------------------- + + inline TCone() {} + inline TCone(const RAY& _ray, TYPE _angle, TYPE _minHeight=TYPE(0), TYPE _maxHeight=std::numeric_limits::max()) + : ray(_ray), angle(_angle), minHeight(_minHeight), maxHeight(_maxHeight) { ASSERT(TYPE(0) <= minHeight && minHeight < maxHeight); } +}; // class TCone + +// Structure used to compute the intersection between a cone and the given sphere/point +template +class TConeIntersect +{ + STATIC_ASSERT(DIMS > 1 && DIMS <= 3); + +public: + typedef Eigen::Matrix VECTOR; + typedef Eigen::Matrix POINT; + typedef SEACAVE::TCone CONE; + typedef SEACAVE::TSphere SPHERE; + + const CONE& cone; + + // cache angle derivatives, to avoid calling trigonometric functions in geometric queries + const TYPE cosAngle, sinAngle, sinAngleSq, cosAngleSq, invSinAngle; + + //--------------------------------------- + + inline TConeIntersect(const CONE& _cone) + : cone(_cone), cosAngle(COS(cone.angle)), sinAngle(SIN(cone.angle)), + sinAngleSq(SQUARE(sinAngle)), cosAngleSq(SQUARE(cosAngle)), + invSinAngle(TYPE(1)/sinAngle) {} + + bool operator()(const SPHERE&) const; + + GCLASS Classify(const POINT&, TYPE& t) const; +}; // class TConeIntersect +/*----------------------------------------------------------------*/ + + #include "Ray.inl" /*----------------------------------------------------------------*/ diff --git a/libs/Common/Ray.inl b/libs/Common/Ray.inl index cfc2908f1..05b1271d6 100644 --- a/libs/Common/Ray.inl +++ b/libs/Common/Ray.inl @@ -896,3 +896,108 @@ TYPE TRay::Distance(const POINT& pt) const return SQRT(DistanceSq(pt)); } // Distance(POINT) /*----------------------------------------------------------------*/ + + +// S T R U C T S /////////////////////////////////////////////////// + +template +bool TCylinder::Intersects(const SPHERE& sphere) const +{ + struct Check { + static bool Intersection(const RAY& ray, const SPHERE& sphere, const VECTOR& CmV, TYPE t, TYPE radius, TYPE height) { + const POINT O(ray.m_pOrig + ray.m_vDir * height); + const VECTOR a(sphere.center - O); + if (a.squaredNorm() <= SQUARE(sphere.radius)) + return true; // ray origin inside the sphere + const POINT D((CmV - ray.m_vDir * t).normalized()); + const TYPE d = a.dot(D); + if (d < TYPE(0)) + return false; // intersection behind the ray origin + const TYPE dSq((a - D*d).squaredNorm()); + const TYPE srSq = SQUARE(sphere.radius); + if (dSq <= srSq) { + const TYPE r = d - SQRT(srSq-dSq); + ASSERT(r >= TYPE(0)); + return r <= radius; // intersection before the ray end + } + return false; + } + }; + const VECTOR CmV(sphere.center - ray.m_pOrig); + const TYPE t(ray.m_vDir.dot(CmV)); + // sphere projects behind the cylinder origin + if (t < minHeight) { + if (t+sphere.radius < minHeight) + return false; + return Check::Intersection(ray, sphere, CmV, t, radius, minHeight); + } + // sphere projects after the cylinder end + if (t > maxHeight) { + if (t-sphere.radius > maxHeight) + return false; + return Check::Intersection(ray, sphere, CmV, t, radius, maxHeight); + } + const TYPE lenSq((CmV - ray.m_vDir * t).squaredNorm()); + return lenSq <= SQUARE(sphere.radius + radius); +} // Intersects +/*----------------------------------------------------------------*/ + +// Classify point to cylinder. +template +GCLASS TCylinder::Classify(const POINT& p, TYPE& t) const +{ + ASSERT(ISEQUAL(ray.m_vDir.norm(), TYPE(1))); + const VECTOR D(p - ray.m_pOrig); + t = ray.m_vDir.dot(D); + if (t < minHeight) return BACK; + if (t > maxHeight) return FRONT; + const TYPE rSq((D - ray.m_vDir*t).squaredNorm()); + const TYPE radiusSq(SQUARE(radius)); + if (rSq > radiusSq) return CULLED; + if (rSq < radiusSq) return VISIBLE; + return PLANAR; +} // Classify +/*----------------------------------------------------------------*/ + + +// S T R U C T S /////////////////////////////////////////////////// + +template +bool TConeIntersect::operator()(const SPHERE& sphere) const +{ + const VECTOR CmV(sphere.center - cone.ray.m_pOrig); + const VECTOR D(CmV + cone.ray.m_vDir * (sphere.radius*invSinAngle)); + TYPE e = D.dot(cone.ray.m_vDir); + if (e <= TYPE(0) || e*e < D.squaredNorm()*cosAngleSq) + return false; + e = CmV.dot(cone.ray.m_vDir); + if (e-sphere.radius > cone.maxHeight) + return false; + if (e < cone.minHeight) { + const TYPE lenSq = CmV.squaredNorm(); + if (e*e >= lenSq*sinAngleSq) + return lenSq <= SQUARE(sphere.radius); + } + return true; +} // Intersect +/*----------------------------------------------------------------*/ + +// Classify point to cone. +template +GCLASS TConeIntersect::Classify(const POINT& p, TYPE& t) const +{ + ASSERT(ISEQUAL(cone.ray.m_vDir.norm(), TYPE(1))); + const VECTOR D(p - cone.ray.m_pOrig); + t = cone.ray.m_vDir.dot(D); + if (ISZERO(t)) + return PLANAR; + if (t < cone.minHeight) return BACK; + if (t > cone.maxHeight) return FRONT; + ASSERT(!ISZERO(D.norm())); + const TYPE tSq(SQUARE(t)); + const TYPE dSq(cosAngleSq*D.squaredNorm()); + if (tSq < dSq) return CULLED; + if (tSq > dSq) return VISIBLE; + return PLANAR; +} // Classify +/*----------------------------------------------------------------*/ diff --git a/libs/Common/Rotation.h b/libs/Common/Rotation.h index 7959e7ed6..87761e56e 100644 --- a/libs/Common/Rotation.h +++ b/libs/Common/Rotation.h @@ -290,13 +290,15 @@ class TRMatrixBase : public TMatrix /** @brief Initialization from parametrized rotation (axis-angle) */ inline TRMatrixBase(const Vec& rot); - /** @brief Initialization from rotation axis w and angle phi (in rad) - using Rodrigues' formula */ + /** @brief Initialization from rotation axis w and angle phi (in rad) using Rodrigues' formula */ inline TRMatrixBase(const Vec& w, const TYPE phi); /** @brief Initialization from quaternion */ inline TRMatrixBase(const Quat& q); + /** @brief Initialization with the rotation from roll/pitch/yaw (in rad) */ + inline TRMatrixBase(TYPE roll, TYPE pitch, TYPE yaw); + inline ~TRMatrixBase(); template inline TRMatrixBase& operator = (const cv::Matx& rhs) { BaseBase::operator = (rhs); return *this; } diff --git a/libs/Common/Rotation.inl b/libs/Common/Rotation.inl index e8a82a50f..212d6c075 100644 --- a/libs/Common/Rotation.inl +++ b/libs/Common/Rotation.inl @@ -553,6 +553,12 @@ inline TRMatrixBase::TRMatrixBase(const Quat& q) SetFromQuaternion(q); } +template +inline TRMatrixBase::TRMatrixBase(TYPE roll, TYPE pitch, TYPE yaw) +{ + SetXYZ(roll, pitch, yaw); +} + template inline TRMatrixBase::~TRMatrixBase() {} @@ -561,33 +567,48 @@ inline TRMatrixBase::~TRMatrixBase() template void TRMatrixBase::SetXYZ(TYPE PhiX, TYPE PhiY, TYPE PhiZ) { - TRMatrixBase Rx(TRMatrixBase::IDENTITY); - TRMatrixBase Ry(TRMatrixBase::IDENTITY); - TRMatrixBase Rz(TRMatrixBase::IDENTITY); - - /* set Rz, Ry, Rx as rotation matrices */ - Rz(0,0) = cos(PhiZ); - Rz(0,1) = -sin(PhiZ); - Rz(1,0) = -Rz(0,1); - Rz(1,1) = Rz(0,0); - //Rz(2,2) = 1; - - Ry(0,0) = cos(PhiY); - Ry(0,2) = sin(PhiY); - //Ry(1,1) = 1; - Ry(2,0) = -Ry(0,2); //-sin(PhiY); - Ry(2,2) = Ry(0,0); //cos(PhiY); - - //Rx(0,0) = 1; - Rx(1,1) = cos(PhiX); - Rx(1,2) = -sin(PhiX); - Rx(2,1) = -Rx(1,2); //sin(PhiX); - Rx(2,2) = Rx(1,1); //cos(PhiX); - -// Mat Result=Rx*Ry*Rz; -// for (int i =0; i<9; i++) -// this->val[i]=Result.GetData()[i]; - (*this) = Rx*Ry*Rz; + #if 0 + TRMatrixBase Rx(TRMatrixBase::IDENTITY); + TRMatrixBase Ry(TRMatrixBase::IDENTITY); + TRMatrixBase Rz(TRMatrixBase::IDENTITY); + + /* set Rz, Ry, Rx as rotation matrices */ + Rz(0,0) = cos(PhiZ); + Rz(0,1) = -sin(PhiZ); + Rz(1,0) = -Rz(0,1); + Rz(1,1) = Rz(0,0); + //Rz(2,2) = 1; + + Ry(0,0) = cos(PhiY); + Ry(0,2) = sin(PhiY); + //Ry(1,1) = 1; + Ry(2,0) = -Ry(0,2); //-sin(PhiY); + Ry(2,2) = Ry(0,0); //cos(PhiY); + + //Rx(0,0) = 1; + Rx(1,1) = cos(PhiX); + Rx(1,2) = -sin(PhiX); + Rx(2,1) = -Rx(1,2); //sin(PhiX); + Rx(2,2) = Rx(1,1); //cos(PhiX); + + (*this) = Rx*Ry*Rz; + #else + const TYPE sin_x = sin(PhiX); + const TYPE sin_y = sin(PhiY); + const TYPE sin_z = sin(PhiZ); + const TYPE cos_x = cos(PhiX); + const TYPE cos_y = cos(PhiY); + const TYPE cos_z = cos(PhiZ); + val[0] = cos_y * cos_z; + val[1] = -cos_y * sin_z; + val[2] = sin_y; + val[3] = cos_x * sin_z + cos_z * sin_x * sin_y; + val[4] = cos_x * cos_z - sin_x * sin_y * sin_z; + val[5] = -cos_y * sin_x; + val[6] = sin_x * sin_z - cos_x * cos_z * sin_y; + val[7] = cos_z * sin_x + cos_x * sin_y * sin_z; + val[8] = cos_x * cos_y; + #endif } @@ -692,21 +713,21 @@ void TRMatrixBase::Set(const Vec& wa, TYPE phi) } const TYPE wnorm(norm(wa)); - if (wnorm < TYPE(1e-7)) { + if (wnorm < std::numeric_limits::epsilon()) { CPC_ERROR("Vector "< #include +#include #include #include #include @@ -337,7 +338,7 @@ inline T MAXF3(const T& x1, const T& x2, const T& x3) { #define RAND std::rand #endif template -FORCEINLINE T RANDOM() { return (T(1)/RAND_MAX)*RAND(); } +FORCEINLINE T RANDOM() { return T(RAND())/RAND_MAX; } template union TAliasCast @@ -598,15 +599,15 @@ namespace SEACAVE { // F U N C T I O N S /////////////////////////////////////////////// template -inline T& NEGATE(T& a) { +constexpr T& NEGATE(T& a) { return (a = -a); } template -inline T SQUARE(const T& a) { +constexpr T SQUARE(const T& a) { return (a * a); } template -inline T CUBE(const T& a) { +constexpr T CUBE(const T& a) { return (a * a * a); } template @@ -626,7 +627,7 @@ inline T LOG10(const T& a) { return T(log10(a)); } template -inline T powi(T base, int exp) { +constexpr T powi(T base, int exp) { T result(1); while (exp) { if (exp & 1) @@ -636,7 +637,7 @@ inline T powi(T base, int exp) { } return result; } -inline int log2i(int val) { +constexpr int log2i(int val) { int ret = -1; while (val > 0) { val >>= 1; @@ -654,14 +655,14 @@ inline T arithmeticSeries(T n, T a1=1, T d=1) { return (n*(a1*2+(n-1)*d))/2; } template -inline T factorial(T n) { +constexpr T factorial(T n) { T ret = 1; while (n > 1) ret *= n--; return ret; } template -inline T combinations(const T& n, const T& k) { +constexpr T combinations(const T& n, const T& k) { ASSERT(n >= k); #if 1 T num = n; @@ -675,6 +676,24 @@ inline T combinations(const T& n, const T& k) { #endif } +// adapted from https://github.com/whackashoe/fastapprox.git +// (set bSafe to true if the values might be smaller than -126) +template +inline float FPOW2(float p) { + if (bSafe && p < -126.f) { + return 0.f; + } else { + ASSERT(p >= -126.f); + CastF2I v; + v.i = static_cast((1 << 23) * (p + 126.94269504f)); + return v.f; + } +} +template +inline float FEXP(float v) { + return FPOW2(1.44269504f * v); +} + // Inverse of the square root // Compute a fast 1 / sqrtf(v) approximation inline float RSQRT(float v) { @@ -839,66 +858,6 @@ FORCEINLINE int Round2Int(double x) { /*----------------------------------------------------------------*/ -// Random number generation -// uniform random number generation -FORCEINLINE float random() { - return RANDOM(); -} -FORCEINLINE double randomd() { - return RANDOM(); -} -template -FORCEINLINE T randomRange(T nMin, T nMax) { - return nMin + ((nMax - nMin) * RAND())/RAND_MAX; -} -template<> -FORCEINLINE float randomRange(float fMin, float fMax) { - return fMin + (fMax - fMin) * random(); -} -template<> -FORCEINLINE double randomRange(double fMin, double fMax) { - return fMin + (fMax - fMin) * randomd(); -} -template -FORCEINLINE T randomMeanRange(T mean, T delta/*=(max-min)/2*/) { - return mean-delta + (delta*T(2) * RAND())/RAND_MAX; -} -template<> -FORCEINLINE float randomMeanRange(float mean, float delta/*=(max-min)/2*/) { - return mean + delta * (2.f * random() - 1.f); -} -template<> -FORCEINLINE double randomMeanRange(double mean, double delta/*=(max-min)/2*/) { - return mean + delta * (2.0 * randomd() - 1.0); -} -// gaussian random number generation -template -FORCEINLINE T gaussian(T val, T sigma) { - return EXP(-SQUARE(val/sigma)/2)/(SQRT(T(M_PI*2))*sigma); -} -template -FORCEINLINE T randomGaussian(T mean, T sigma) { - T x, y, r2; - do { - x = T(-1) + T(2) * RANDOM(); - y = T(-1) + T(2) * RANDOM(); - r2 = x * x + y * y; - } while (r2 > T(1) || r2 == T(0)); - return mean + sigma * y * SQRT(T(-2) * LOGN(r2) / r2); -} -template -FORCEINLINE T randomGaussian(T sigma) { - return randomGaussian(T(0), sigma); -} -FORCEINLINE float randomGaussian() { - return randomGaussian(0.f, 1.f); -} -FORCEINLINE double randomGaussiand() { - return randomGaussian(0.0, 1.0); -} -/*----------------------------------------------------------------*/ - - // INTERPOLATION // Linear interpolation @@ -1173,6 +1132,7 @@ inline _Tp SAFEDIVIDE(_Tp x, _Tp y) { return (y==_Tp(0) ? INVZERO(y) : x/ } // namespace SEACAVE +#include "Random.h" #include "HalfFloat.h" @@ -1521,8 +1481,12 @@ class TDMatrix : public cv::Mat_ return pt.width>=0 && pt.height>=0 && pt.width - inline bool isInside(const cv::Point_& pt) const { - return int(pt.x)>=0 && int(pt.y)>=0 && int(pt.x)::value,bool>::type isInside(const cv::Point_& pt) const { + return pt.x>=0 && pt.y>=0 && pt.x + inline typename std::enable_if::value,bool>::type isInside(const cv::Point_& pt) const { + return pt.x>=T(0) && pt.y>=T(0) && pt.x<=T(Base::size().width) && pt.y<=T(Base::size().height); } /// Is this coordinate inside the 2D matrix, and not too close to the edges? @@ -1531,12 +1495,20 @@ class TDMatrix : public cv::Mat_ return pt.width>=border && pt.height>=border && pt.width - inline bool isInsideWithBorder(const cv::Point_& pt, int border) const { - return int(pt.x)>=border && int(pt.y)>=border && int(pt.x)::value,bool>::type isInsideWithBorder(const cv::Point_& pt, int border) const { + return pt.x>=border && pt.y>=border && pt.x + inline typename std::enable_if::value,bool>::type isInsideWithBorder(const cv::Point_& pt, int border) const { + return pt.x>=T(border) && pt.y>=T(border) && pt.x<=T(Base::size().width-(border+1)) && pt.y<=T(Base::size().height-(border+1)); } template - inline bool isInsideWithBorder(const cv::Point_& pt) const { - return int(pt.x)>=border && int(pt.y)>=border && int(pt.x)::value,bool>::type isInsideWithBorder(const cv::Point_& pt) const { + return pt.x>=border && pt.y>=border && pt.x + inline typename std::enable_if::value,bool>::type isInsideWithBorder(const cv::Point_& pt) const { + return pt.x>=T(border) && pt.y>=T(border) && pt.x<=T(Base::size().width-(border+1)) && pt.y<=T(Base::size().height-(border+1)); } /// Remove the given element from the vector @@ -1810,6 +1782,14 @@ struct TPixel { inline void set(const ALT* clr) { c[0] = TYPE(clr[0]); c[1] = TYPE(clr[1]); c[2] = TYPE(clr[2]); } inline void get(ALT& _r, ALT& _g, ALT& _b) const { _r = ALT(r); _g = ALT(g); _b = ALT(b); } inline void get(ALT* clr) const { clr[0] = ALT(c[0]); clr[1] = ALT(c[1]); clr[2] = ALT(c[2]); } + template inline TPixel::value || !std::is_same::value,T>::type> cast() const { return TPixel(T(r), T(g), T(b)); } + template inline TPixel::value && std::is_same::value,T>::type> cast() const { + return TPixel( + (uint8_t)CLAMP(ROUND2INT(r), 0, 255), + (uint8_t)CLAMP(ROUND2INT(g), 0, 255), + (uint8_t)CLAMP(ROUND2INT(b), 0, 255) + ); + } // set/get as vector inline const TYPE& operator[](size_t i) const { ASSERT(i<3); return c[i]; } inline TYPE& operator[](size_t i) { ASSERT(i<3); return c[i]; } @@ -1921,6 +1901,15 @@ struct TColor { inline void set(const ALT* clr) { c[0] = TYPE(clr[0]); c[1] = TYPE(clr[1]); c[2] = TYPE(clr[2]); c[3] = TYPE(clr[3]); } inline void get(ALT& _r, ALT& _g, ALT& _b, ALT& _a) const { _r = ALT(r); _g = ALT(g); _b = ALT(b); _a = ALT(a); } inline void get(ALT* clr) const { clr[0] = ALT(c[0]); clr[1] = ALT(c[1]); clr[2] = ALT(c[2]); clr[3] = ALT(c[3]); } + template inline TColor::value || !std::is_same::value,T>::type> cast() const { return TColor(T(r), T(g), T(b), T(a)); } + template inline TColor::value && std::is_same::value,T>::type> cast() const { + return TColor( + (uint8_t)CLAMP(ROUND2INT(r), 0, 255), + (uint8_t)CLAMP(ROUND2INT(g), 0, 255), + (uint8_t)CLAMP(ROUND2INT(b), 0, 255), + (uint8_t)CLAMP(ROUND2INT(a), 0, 255) + ); + } // set/get as vector inline const TYPE& operator[](size_t i) const { ASSERT(i<4); return c[i]; } inline TYPE& operator[](size_t i) { ASSERT(i<4); return c[i]; } @@ -2027,7 +2016,7 @@ class TImage : public TDMatrix INTERTYPE sample(const SAMPLER& sampler, const TPoint2& pt) const; template - void toGray(TImage& out, int code, bool bNormalize=false) const; + void toGray(TImage& out, int code, bool bNormalize=false, bool bSRGB=false) const; unsigned computeMaxResolution(unsigned& level, unsigned minImageSize=320, unsigned maxImageSize=INT_MAX) const; static unsigned computeMaxResolution(unsigned width, unsigned height, unsigned& level, unsigned minImageSize=320, unsigned maxImageSize=INT_MAX); diff --git a/libs/Common/Types.inl b/libs/Common/Types.inl index 0dc277ffb..eb1582583 100644 --- a/libs/Common/Types.inl +++ b/libs/Common/Types.inl @@ -1524,6 +1524,79 @@ DEFINE_GENERIC_CVDATATYPE(SEACAVE::Image32F4, uint8_t) namespace SEACAVE { +namespace CONVERT { + +// convert sRGB to/from linear value +// (see http://en.wikipedia.org/wiki/SRGB) +template +constexpr T sRGB2RGB(T x) { + return x <= T(0.04045) ? x * (T(1)/T(12.92)) : POW((x + T(0.055)) * (T(1)/T(1.055)), T(2.4)); +} +template +constexpr T RGB2sRGB(T x) { + return x <= T(0.0031308) ? T(12.92) * x : T(1.055) * POW(x, T(1)/T(2.4)) - T(0.055); +} +static const CAutoPtrArr g_ptrsRGB82RGBf([]() { + float* const buffer = new float[256]; + for (int i=0; i<256; ++i) + buffer[i] = sRGB2RGB(float(i)/255.f); + return buffer; +}()); + +// color conversion helper structures +template +struct NormRGB_t { + const TO v; + inline NormRGB_t(TI _v) : v(TO(_v)*(TO(1)/TO(255))) {} + inline operator TO () const { return v; } +}; +template +struct RGBUnNorm_t { + const TO v; + inline RGBUnNorm_t(TI _v) : v(TO(_v)*TO(255)) {} + inline operator TO () const { return v; } +}; +template +struct RoundF2U_t { + const TO v; + inline RoundF2U_t(TI _v) : v(ROUND2INT(_v)) {} + inline operator TO () const { return v; } +}; +template +struct sRGB2RGB_t { + const TO v; + inline sRGB2RGB_t(TI _v) : v(sRGB2RGB(TO(_v))) {} + inline operator TO () const { return v; } +}; +template +struct NormsRGB2RGB_t { + const TO v; + inline NormsRGB2RGB_t(TI _v) : v(sRGB2RGB(TO(_v)*(TO(1)/TO(255)))) {} + inline operator TO () const { return v; } +}; +template <> +struct NormsRGB2RGB_t { + const float v; + inline NormsRGB2RGB_t(uint8_t _v) : v(g_ptrsRGB82RGBf[_v]) {} + inline operator float () const { return v; } +}; +template +struct NormsRGB2RGBUnNorm_t { + const TO v; + inline NormsRGB2RGBUnNorm_t(TI _v) : v(sRGB2RGB(TO(_v)*(TO(1)/TO(255)))*TO(255)) {} + inline operator TO () const { return v; } +}; +template <> +struct NormsRGB2RGBUnNorm_t { + const float v; + inline NormsRGB2RGBUnNorm_t(uint8_t _v) : v(g_ptrsRGB82RGBf[_v]*255.f) {} + inline operator float () const { return v; } +}; + +} // namespace CONVERT +/*----------------------------------------------------------------*/ + + // C L A S S ////////////////////////////////////////////////////// template @@ -1556,6 +1629,21 @@ template inline TPoint3 Cast(const TPoint3& pt) { return pt; } +// Pixel +template +inline TPixel Cast(const TPixel& pt) { + return pt; +} +// Color +template +inline TColor Cast(const TColor& pt) { + return pt; +} +// Matrix +template +inline TMatrix Cast(const TMatrix& v) { + return v; +} /*----------------------------------------------------------------*/ @@ -2035,23 +2123,21 @@ INTERTYPE TImage::sample(const SAMPLER& sampler, const TPoint2 template -void TImage::toGray(TImage& out, int code, bool bNormalize) const +void TImage::toGray(TImage& out, int code, bool bNormalize, bool bSRGB) const { #if 1 ASSERT(code==cv::COLOR_RGB2GRAY || code==cv::COLOR_RGBA2GRAY || code==cv::COLOR_BGR2GRAY || code==cv::COLOR_BGRA2GRAY); static const T coeffsRGB[] = {T(0.299), T(0.587), T(0.114)}; static const T coeffsBGR[] = {T(0.114), T(0.587), T(0.299)}; - static const T coeffsRGBn[] = {T(0.299/255), T(0.587/255), T(0.114/255)}; - static const T coeffsBGRn[] = {T(0.114/255), T(0.587/255), T(0.299/255)}; const float* coeffs; switch (code) { case cv::COLOR_BGR2GRAY: case cv::COLOR_BGRA2GRAY: - coeffs = (bNormalize ? coeffsBGRn : coeffsBGR); + coeffs = coeffsBGR; break; case cv::COLOR_RGB2GRAY: case cv::COLOR_RGBA2GRAY: - coeffs = (bNormalize ? coeffsRGBn : coeffsRGB); + coeffs = coeffsRGB; break; default: ASSERT("Unsupported image format" == NULL); @@ -2065,8 +2151,26 @@ void TImage::toGray(TImage& out, int code, bool bNormalize) const T* dst = out.cv::Mat::template ptr(); T* const dstEnd = dst + out.area(); typedef typename cv::DataType::channel_type ST; - for (const ST* src=cv::Mat::template ptr(); dst!=dstEnd; src+=scn) - *dst++ = cb*T(src[0]) + cg*T(src[1]) + cr*T(src[2]); + if (bSRGB) { + if (bNormalize) { + typedef typename CONVERT::NormsRGB2RGB_t ColConv; + for (const ST* src=cv::Mat::template ptr(); dst!=dstEnd; src+=scn) + *dst++ = T(cb*ColConv(src[0]) + cg*ColConv(src[1]) + cr*ColConv(src[2])); + } else { + typedef typename CONVERT::NormsRGB2RGBUnNorm_t ColConv; + for (const ST* src=cv::Mat::template ptr(); dst!=dstEnd; src+=scn) + *dst++ = T(cb*ColConv(src[0]) + cg*ColConv(src[1]) + cr*ColConv(src[2])); + } + } else { + if (bNormalize) { + typedef typename CONVERT::NormRGB_t ColConv; + for (const ST* src=cv::Mat::template ptr(); dst!=dstEnd; src+=scn) + *dst++ = T(cb*ColConv(src[0]) + cg*ColConv(src[1]) + cr*ColConv(src[2])); + } else { + for (const ST* src=cv::Mat::template ptr(); dst!=dstEnd; src+=scn) + *dst++ = T(cb*src[0] + cg*src[1] + cr*src[2]); + } + } #else cv::Mat cimg; convertTo(cimg, cv::DataType::type); diff --git a/libs/Common/Util.cpp b/libs/Common/Util.cpp index 53df906b2..a32268cf7 100644 --- a/libs/Common/Util.cpp +++ b/libs/Common/Util.cpp @@ -433,6 +433,25 @@ String Util::GetOSInfo() /*----------------------------------------------------------------*/ +// Initialize various global variables (ex: random-number-generator state). +void Util::Init() +{ + #ifdef _RELEASE + const time_t t(Util::getTime()); + std::srand((unsigned)t); + #if CV_MAJOR_VERSION > 3 || (CV_MAJOR_VERSION == 3 && CV_MINOR_VERSION >= 4) + cv::setRNGSeed((int)t); + #endif + #else + std::srand((unsigned)0); + #if CV_MAJOR_VERSION > 3 || (CV_MAJOR_VERSION == 3 && CV_MINOR_VERSION >= 4) + cv::setRNGSeed((int)0); + #endif + #endif +} +/*----------------------------------------------------------------*/ + + /** * Set global variable for availability of SSE instructions. */ @@ -616,7 +635,7 @@ void Util::LogBuild() #else LOG(_T("Build date: ") __DATE__ _T(", ") __TIME__); #endif - LOG((_T("CPU: ") + Util::GetCPUInfo()).c_str()); + LOG(_T("CPU: %s (%u cores)"), Util::GetCPUInfo().c_str(), Thread::hardwareConcurrency()); LOG((_T("RAM: ") + Util::GetRAMInfo()).c_str()); LOG((_T("OS: ") + Util::GetOSInfo()).c_str()); if (!SIMD_ENABLED.isSet(Util::SSE)) LOG(_T("warning: no SSE compatible CPU or OS detected")); diff --git a/libs/Common/Util.h b/libs/Common/Util.h index d41b829ed..dee6aab24 100644 --- a/libs/Common/Util.h +++ b/libs/Common/Util.h @@ -631,8 +631,7 @@ class GENERAL_API Util if (wsz == NULL) return String(); #if 1 - const std::wstring ws(wsz); - return std::string(ws.cbegin(), ws.cend()); + return std::wstring_convert, wchar_t>().to_bytes(wsz); #elif 1 std::mbstate_t state = std::mbstate_t(); const size_t len(std::wcsrtombs(NULL, &wsz, 0, &state)); @@ -642,7 +641,7 @@ class GENERAL_API Util if (std::wcsrtombs(&mbstr[0], &wsz, mbstr.size(), &state) == static_cast(-1)) return String(); return String(&mbstr[0]); - #elif 1 + #else const std::wstring ws(wsz); const std::locale locale(""); typedef std::codecvt converter_type; @@ -654,10 +653,6 @@ class GENERAL_API Util if (converter.out(state, ws.data(), ws.data() + ws.length(), from_next, &to[0], &to[0] + to.size(), to_next) != converter_type::ok) return String(); return std::string(&to[0], to_next); - #else - typedef std::codecvt_utf8 convert_typeX; - std::wstring_convert converterX; - return converterX.to_bytes(wstr); #endif } @@ -749,6 +744,8 @@ class GENERAL_API Util } + static void Init(); + static String GetCPUInfo(); static String GetRAMInfo(); static String GetOSInfo(); diff --git a/libs/Common/Util.inl b/libs/Common/Util.inl index 484c5313d..f64a1704e 100644 --- a/libs/Common/Util.inl +++ b/libs/Common/Util.inl @@ -613,152 +613,36 @@ inline TPoint3 CorrectBarycentricCoordinates(TPoint2 b) { template inline void Normal2Dir(const TPoint3& d, TPoint2& p) { ASSERT(ISEQUAL(norm(d), T(1))); - #if 0 - // empirically tested - p.y = TR(atan2(sqrt(d.x*d.x + d.y*d.y), d.z)); p.x = TR(atan2(d.y, d.x)); - #elif 0 - // empirically tested - if (d.x == T(0) && d.y == T(0)) { - ASSERT(ISEQUAL(ABS(d.z), T(1))); - p.y = TR(M_PI_2); - p.x = TR(M_PI_2); - } else { - p.y = TR(atan2(d.z, sqrt(d.x*d.x + d.y*d.y))); - p.x = TR(atan2(d.y, d.x)); - } - #else - // as in PMVS - const T b(asin(CLAMP(d.y, T(-1), T(1)))); - p.y = TR(b); - const T cosb(cos(b)); - if (cosb == T(0)) { - p.x = TR(0); - } else { - const T scl(T(1) / cosb); - const T sina( d.x * scl); - const T cosa(-d.z * scl); - p.x = TR(acos(CLAMP(cosa, T(-1), T(1)))); - if (sina < T(0)) - p.x = -p.x; - } - #endif + p.y = TR(acos(d.z)); } template inline void Dir2Normal(const TPoint2& p, TPoint3& d) { - #if 0 - // empirically tested const T siny(sin(p.y)); d.x = TR(cos(p.x)*siny); d.y = TR(sin(p.x)*siny); d.z = TR(cos(p.y)); - #elif 0 - // empirically tested - const T cosy(cos(p.y)); - d.x = TR(cos(p.x)*cosy); - d.y = TR(sin(p.x)*cosy); - d.z = TR(sin(p.y)); - #else - // as in PMVS - const T cosy(cos(p.y)); - d.x = TR( sin(p.x)*cosy); - d.y = TR( sin(p.y)); - d.z = TR(-cos(p.x)*cosy); - #endif ASSERT(ISEQUAL(norm(d), TR(1))); } // Encodes/decodes a 3D vector in two parameters for the direction and one parameter for the scale -#if 0 -template -inline void Vector2DirScale(const T vect[3], TR dir[2], TR* scale) -{ - const T x2y2(SQUARE(vect[0])+SQUARE(vect[1])); - const T scl(sqrt(x2y2+SQUARE(vect[2]))); - scale[0] = TR(scl); - const T nrm(T(1) / scl); - dir[1] = TR(atan2(sqrt(x2y2)*nrm, vect[2]*nrm)); - dir[0] = TR(atan2(vect[1]*nrm, vect[0]*nrm)); -} -template -inline void DirScale2Vector(const T dir[2], const T* scale, TR vect[3]) -{ - const T cosy(cos(dir[1])*scale[0]); - vect[0] = TR(cos(dir[0])*cosy); - vect[1] = TR(sin(dir[0])*cosy); - vect[2] = TR(sin(dir[1])*scale[0]); -} -#elif 0 -template -inline void Vector2DirScale(const T vect[3], TR dir[2], TR* scale) -{ - const T x2y2(SQUARE(vect[0])+SQUARE(vect[1])); - const T scl(sqrt(x2y2+SQUARE(vect[2]))); - scale[0] = TR(scl); - const T nrm(T(1) / scl); - if (vect[0] == T(0) && vect[1] == T(0)) { - ASSERT(ISEQUAL(ABS(vect[2]*nrm), T(1))); - dir[1] = TR(M_PI_2); - dir[0] = TR(M_PI_2); - } else { - dir[1] = TR(atan2(vect[2]*nrm, sqrt(x2y2)*nrm)); - dir[0] = TR(atan2(vect[1]*nrm, vect[0]*nrm)); - } -} -template -inline void DirScale2Vector(const T dir[2], const T* scale, TR vect[3]) -{ - const T cosy(cos(dir[1])*scale[0]); - vect[0] = TR(cos(dir[0])*cosy); - vect[1] = TR(sin(dir[0])*cosy); - vect[2] = TR(sin(dir[1])*scale[0]); -} -#else template inline void Vector2DirScale(const T vect[3], TR dir[2], TR* scale) { const T scl(sqrt(SQUARE(vect[0])+SQUARE(vect[1])+SQUARE(vect[2]))); + ASSERT(!ISZERO(scl)); scale[0] = TR(scl); - const T b(asin(CLAMP(vect[1]/scale[0], T(-1), T(1)))); - dir[1] = TR(b); - const T cosb(cos(b)); - if (cosb == T(0)) { - dir[0] = TR(0); - } else { - const T invscl(T(1) / (scl*cosb)); - const T sina( vect[0] * invscl); - const T cosa(-vect[2] * invscl); - dir[0] = TR(acos(CLAMP(cosa, T(-1), T(1)))); - if (sina < T(0)) - dir[0] = -dir[0]; - } + dir[0] = TR(atan2(vect[1], vect[0])); + dir[1] = TR(acos(vect[2] / scl)); } template inline void DirScale2Vector(const T dir[2], const T* scale, TR vect[3]) { - const T cos_dir1(cos(dir[1])*scale[0]); - vect[0] = TR( sin(dir[0]) * cos_dir1); - vect[1] = TR( sin(dir[1]) * scale[0]); - vect[2] = TR(-cos(dir[0]) * cos_dir1); -} -#endif -#if TD_VERBOSE == TD_VERBOSE_DEBUG -inline void TestDir() { - typedef REAL Real; - for (int i=0; i<10000; ++i) { - TPoint3 d( - SEACAVE::randomMeanRange(0.0, 1.0), - SEACAVE::randomMeanRange(0.0, 1.0), - SEACAVE::randomMeanRange(0.0, 1.0) - ); - normalize(d); - TPoint2 p; - Normal2Dir(d, p); - TPoint3 _d; - Dir2Normal(p, _d); - ASSERT(ISEQUAL(d, _d)); - } + ASSERT(!ISZERO(*scale)); + const T siny(*scale*sin(dir[1])); + vect[0] = TR(cos(dir[0])*siny); + vect[1] = TR(sin(dir[0])*siny); + vect[2] = TR(*scale*cos(dir[1])); } -#endif /*----------------------------------------------------------------*/ @@ -772,6 +656,7 @@ inline T MaxDepthDifference(T d, T threshold) { } template inline T DepthSimilarity(T d0, T d1) { + ASSERT(d0 > 0 && d1 > 0); #if 0 return ABS(d0-d1)*T(2)/(d0+d1); #else diff --git a/libs/IO/CMakeLists.txt b/libs/IO/CMakeLists.txt index a069fc3d2..a354376be 100644 --- a/libs/IO/CMakeLists.txt +++ b/libs/IO/CMakeLists.txt @@ -23,16 +23,6 @@ if(TIFF_FOUND) else() SET(TIFF_LIBRARIES "") endif() -if(OpenMVS_USE_EXIV2) - FIND_PACKAGE(EXIV2 QUIET) - if(EXIV2_FOUND) - INCLUDE_DIRECTORIES(${EXIV2_INCLUDE_DIRS}) - LIST(APPEND OpenMVS_DEFINITIONS -D_USE_EXIV2) - SET(_USE_EXIV2 TRUE CACHE INTERNAL "") - else() - SET(EXIV2_LIBS "") - endif() -endif() # List sources files FILE(GLOB PCH_C "Common.cpp") diff --git a/libs/IO/Common.h b/libs/IO/Common.h index dee7db38b..e43aaac44 100644 --- a/libs/IO/Common.h +++ b/libs/IO/Common.h @@ -36,9 +36,6 @@ #ifdef _USE_TIFF #define _IMAGE_TIFF // add TIFF support #endif -#ifdef _USE_EXIV2 -#define _IMAGE_EXIF // complete EXIF info support based on Exiv2 -#endif #include "ImageSCI.h" #ifdef _IMAGE_BMP @@ -59,13 +56,8 @@ #ifdef _IMAGE_TIFF #include "ImageTIFF.h" #endif -#ifdef _IMAGE_EXIF -#include "ImageEXIF.h" -#endif -#include "EXIF.h" #include "PLY.h" #include "OBJ.h" -#include "TinyXML2.h" /*----------------------------------------------------------------*/ #endif // __IO_COMMON_H__ diff --git a/libs/IO/EXIF.cpp b/libs/IO/EXIF.cpp deleted file mode 100644 index a17af248b..000000000 --- a/libs/IO/EXIF.cpp +++ /dev/null @@ -1,656 +0,0 @@ -/************************************************************************** - exif.cpp -- A simple ISO C++ library to parse basic EXIF - information from a JPEG file. - - Copyright (c) 2010-2013 Mayank Lahiri - mlahiri@gmail.com - All rights reserved (BSD License). - - See exif.h for version history. - - 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. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 FREEBSD PROJECT 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. -*/ -#include "Common.h" -#include "EXIF.h" - -using std::string; - -namespace { - // IF Entry - struct IFEntry { - // Raw fields - unsigned short tag; - unsigned short format; - unsigned data; - unsigned length; - - // Parsed fields - string val_string; - unsigned short val_16; - unsigned val_32; - double val_rational; - unsigned char val_byte; - }; - - // Helper functions - unsigned parse32(const unsigned char *buf, bool intel) { - if (intel) - return ((unsigned)buf[3]<<24) | - ((unsigned)buf[2]<<16) | - ((unsigned)buf[1]<<8) | - buf[0]; - - return ((unsigned)buf[0]<<24) | - ((unsigned)buf[1]<<16) | - ((unsigned)buf[2]<<8) | - buf[3]; - } - - unsigned short parse16(const unsigned char *buf, bool intel) { - if (intel) - return ((unsigned) buf[1]<<8) | buf[0]; - return ((unsigned) buf[0]<<8) | buf[1]; - } - - string parseEXIFString(const unsigned char *buf, - const unsigned num_components, - const unsigned data, - const unsigned base, - const unsigned len, - bool intel) { - string value; - if (num_components <= 4){ - value.resize(num_components); - char j = intel ? 0 : 24; - char j_m = intel ? -8 : 8; - for(unsigned i=0; i> j & 0xff; - if (value[num_components-1] == '\0') - value.resize(num_components-1); - } else { - if (base+data+num_components <= len) - value.assign( (const char*)(buf+base+data), num_components-1 ); - } - return value; - } - - double parseEXIFRational(const unsigned char *buf, bool intel) { - double numerator = 0; - double denominator = 1; - - numerator = (double) parse32(buf, intel); - denominator= (double) parse32(buf+4, intel); - if(denominator < 1e-20) - return 0; - return numerator/denominator; - } - - IFEntry parseIFEntry(const unsigned char *buf, - const unsigned offs, - const bool alignIntel, - const unsigned base, - const unsigned len) { - IFEntry result; - - // Each directory entry is composed of: - // 2 bytes: tag number (data field) - // 2 bytes: data format - // 4 bytes: number of components - // 4 bytes: data value or offset to data value - result.tag = parse16(buf + offs, alignIntel); - result.format = parse16(buf + offs + 2, alignIntel); - result.length = parse32(buf + offs + 4, alignIntel); - result.data = parse32(buf + offs + 8, alignIntel); - - // Parse value in specified format - switch (result.format) { - case 1: - result.val_byte = (unsigned char) *(buf + offs + 8); - break; - case 2: - result.val_string = parseEXIFString(buf, result.length, result.data, base, len, alignIntel); - break; - case 3: - result.val_16 = parse16((const unsigned char *) buf + offs + 8, alignIntel); - break; - case 4: - result.val_32 = result.data; - break; - case 5: - if (base + result.data + 8 <= len) - result.val_rational = parseEXIFRational(buf + base + result.data, alignIntel); - break; - case 7: - case 9: - case 10: - break; - default: - result.tag = 0xFF; - } - return result; - } -} - -// -// Locates the EXIF segment and parses it using parseFromEXIFSegment -// -int EXIFInfo::parseFrom(const unsigned char *buf, unsigned len) { - enum JPEG_MARKERS { - START = 0xFF, - SOI = 0xD8, - EOI = 0xD9, - SOS = 0xDA, - COM = 0xFE, - JFIF = 0xE0, - EXIF = 0xE1, - IPTC = 0xED, - }; - // Sanity check: all JPEG files start with 0xFFD8 and end with 0xFFD9 - // This check also ensures that the user has supplied a correct value for len. - if (!buf || len < 16) - return PARSE_EXIF_ERROR_NO_EXIF; - if (buf[0] != START || buf[1] != SOI) - return PARSE_EXIF_ERROR_NO_JPEG; - // not always valid, sometimes 0xFF is added for padding - //if (buf[len-2] != 0xFF || buf[len-1] != 0xD9) - // return PARSE_EXIF_ERROR_NO_JPEG; - - // Scan for EXIF header (bytes 0xFF 0xE1) and do a sanity check by - // looking for bytes "Exif\0\0". The marker length data is in Motorola - // byte order, which results in the 'false' parameter to parse16(). - // The marker has to contain at least the TIFF header, otherwise the - // EXIF data is corrupt. So the minimum length specified here has to be: - // 2 bytes: section size - // 6 bytes: "Exif\0\0" string - // 2 bytes: TIFF header (either "II" or "MM" string) - // 2 bytes: TIFF magic (short 0x2a00 in Motorola byte order) - // 4 bytes: Offset to first IFD - // ========= - // 16 bytes - for (unsigned pos=2; pos len) - return PARSE_EXIF_ERROR_NO_JPEG; - // skip the section - pos += section_length; - } - } - } - return PARSE_EXIF_ERROR_NO_EXIF; -} - -int EXIFInfo::parseFrom(const string &data) { - return parseFrom((const unsigned char *)data.data(), (unsigned)data.length()); -} - -// -// Main parsing function for an EXIF segment. -// -// PARAM: 'buf' start of the EXIF TIFF, which must be the bytes "Exif\0\0". -// PARAM: 'len' length of buffer -// -int EXIFInfo::parseFromEXIFSegment(const unsigned char *buf, unsigned len) { - clear(); - bool alignIntel = true; // byte alignment (defined in EXIF header) - unsigned offs = 0; // current offset into buffer - if (!buf || len < 6) - return PARSE_EXIF_ERROR_NO_EXIF; - - if (!std::equal(buf, buf+6, "Exif\0\0")) - return PARSE_EXIF_ERROR_NO_EXIF; - offs += 6; - - // Now parsing the TIFF header. The first two bytes are either "II" or - // "MM" for Intel or Motorola byte alignment. Sanity check by parsing - // the unsigned short that follows, making sure it equals 0x2a. The - // last 4 bytes are an offset into the first IFD, which are added to - // the global offset counter. For this block, we expect the following - // minimum size: - // 2 bytes: 'II' or 'MM' - // 2 bytes: 0x002a - // 4 bytes: offset to first IDF - // ----------------------------- - // 8 bytes - if (offs + 8 > len) - return PARSE_EXIF_ERROR_CORRUPT; - unsigned tiff_header_start = offs; - if (buf[offs] == 'I' && buf[offs+1] == 'I') - alignIntel = true; - else { - if(buf[offs] == 'M' && buf[offs+1] == 'M') - alignIntel = false; - else - return PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN; - } - this->ByteAlign = alignIntel; - offs += 2; - if (0x2a != parse16(buf+offs, alignIntel)) - return PARSE_EXIF_ERROR_CORRUPT; - offs += 2; - unsigned first_ifd_offset = parse32(buf + offs, alignIntel); - offs += first_ifd_offset - 4; - if (offs >= len) - return PARSE_EXIF_ERROR_CORRUPT; - - // Now parsing the first Image File Directory (IFD0, for the main image). - // An IFD consists of a variable number of 12-byte directory entries. The - // first two bytes of the IFD section contain the number of directory - // entries in the section. The last 4 bytes of the IFD contain an offset - // to the next IFD, which means this IFD must contain exactly 6 + 12 * num - // bytes of data. - if (offs + 2 > len) - return PARSE_EXIF_ERROR_CORRUPT; - int num_entries = parse16(buf + offs, alignIntel); - if (offs + 6 + 12 * num_entries > len) - return PARSE_EXIF_ERROR_CORRUPT; - offs += 2; - unsigned exif_sub_ifd_offset = len; - unsigned gps_sub_ifd_offset = len; - while (--num_entries >= 0) { - IFEntry result = parseIFEntry(buf, offs, alignIntel, tiff_header_start, len); - offs += 12; - switch(result.tag) { - case 0x102: - // Bits per sample - if (result.format == 3) - this->BitsPerSample = result.val_16; - break; - - case 0x10E: - // Image description - if (result.format == 2) - this->ImageDescription = result.val_string; - break; - - case 0x10F: - // Digicam make - if (result.format == 2) - this->Make = result.val_string; - break; - - case 0x110: - // Digicam model - if (result.format == 2) - this->Model = result.val_string; - break; - - case 0x112: - // Orientation of image - if (result.format == 3) - this->Orientation = result.val_16; - break; - - case 0x131: - // Software used for image - if (result.format == 2) - this->Software = result.val_string; - break; - - case 0x132: - // EXIF/TIFF date/time of image modification - if (result.format == 2) - this->DateTime = result.val_string; - break; - - case 0x8298: - // Copyright information - if (result.format == 2) - this->Copyright = result.val_string; - break; - - case 0x8825: - // GPS IFS offset - gps_sub_ifd_offset = tiff_header_start + result.data; - break; - - case 0x8769: - // EXIF SubIFD offset - exif_sub_ifd_offset = tiff_header_start + result.data; - break; - } - } - - // Jump to the EXIF SubIFD if it exists and parse all the information - // there. Note that it's possible that the EXIF SubIFD doesn't exist. - // The EXIF SubIFD contains most of the interesting information that a - // typical user might want. - if (exif_sub_ifd_offset + 4 <= len) { - offs = exif_sub_ifd_offset; - int num_entries = parse16(buf + offs, alignIntel); - if (offs + 6 + 12 * num_entries > len) - return PARSE_EXIF_ERROR_CORRUPT; - offs += 2; - while (--num_entries >= 0) { - IFEntry result = parseIFEntry(buf, offs, alignIntel, tiff_header_start, len); - switch(result.tag) { - case 0x829a: - // Exposure time in seconds - if (result.format == 5) - this->ExposureTime = result.val_rational; - break; - - case 0x829d: - // FNumber - if (result.format == 5) - this->FNumber = result.val_rational; - break; - - case 0x8827: - // ISO Speed Rating - if (result.format == 3) - this->ISOSpeedRatings = result.val_16; - break; - - case 0x9003: - // Original date and time - if (result.format == 2) - this->DateTimeOriginal = result.val_string; - break; - - case 0x9004: - // Digitization date and time - if (result.format == 2) - this->DateTimeDigitized = result.val_string; - break; - - case 0x9201: - // Shutter speed value - if (result.format == 5) - this->ShutterSpeedValue = result.val_rational; - break; - - case 0x9204: - // Exposure bias value - if (result.format == 5) - this->ExposureBiasValue = result.val_rational; - break; - - case 0x9206: - // Subject distance - if (result.format == 5) - this->SubjectDistance = result.val_rational; - break; - - case 0x9209: - // Flash used - if (result.format == 3) - this->Flash = result.data ? 1 : 0; - break; - - case 0x920a: - // Focal length - if (result.format == 5) - this->FocalLength = result.val_rational; - break; - - case 0x9207: - // Metering mode - if (result.format == 3) - this->MeteringMode = result.val_16; - break; - - case 0x9291: - // Subsecond original time - if (result.format == 2) - this->SubSecTimeOriginal = result.val_string; - break; - - case 0xa002: - // EXIF Image width - if (result.format == 4) - this->ImageWidth = result.val_32; - if (result.format == 3) - this->ImageWidth = result.val_16; - break; - - case 0xa003: - // EXIF Image height - if (result.format == 4) - this->ImageHeight = result.val_32; - if (result.format == 3) - this->ImageHeight = result.val_16; - break; - - case 0xa20e: - // Focal plane X resolution - if (result.format == 5) - this->FocalPlaneXResolution = result.val_rational; - break; - - case 0xa20f: - // Focal plane Y resolution - if (result.format == 5) - this->FocalPlaneYResolution = result.val_rational; - break; - - case 0xa210: - // Focal plane resolution unit - if (result.format == 3) - this->FocalPlaneResolutionUnit = result.val_16; - break; - - case 0xa405: - // Focal length in 35mm film - if (result.format == 3) - this->FocalLengthIn35mm = result.val_16; - break; - } - offs += 12; - } - } - - // Jump to the GPS SubIFD if it exists and parse all the information - // there. Note that it's possible that the GPS SubIFD doesn't exist. - if (gps_sub_ifd_offset + 4 <= len) { - offs = gps_sub_ifd_offset; - int num_entries = parse16(buf + offs, alignIntel); - if (offs + 6 + 12 * num_entries > len) - return PARSE_EXIF_ERROR_CORRUPT; - offs += 2; - while (--num_entries >= 0) { - unsigned short tag = parse16(buf + offs, alignIntel); - unsigned short format = parse16(buf + offs + 2, alignIntel); - unsigned length = parse32(buf + offs + 4, alignIntel); - unsigned data = parse32(buf + offs + 8, alignIntel); - switch(tag) { - case 1: - // GPS north or south - this->GeoLocation.LatComponents.direction = *(buf + offs + 8); - if ('S' == this->GeoLocation.LatComponents.direction) - this->GeoLocation.Latitude = -this->GeoLocation.Latitude; - break; - - case 2: - // GPS latitude - if (format == 5 && length == 3) { - this->GeoLocation.LatComponents.degrees = - parseEXIFRational(buf + data + tiff_header_start, alignIntel); - this->GeoLocation.LatComponents.minutes = - parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel); - this->GeoLocation.LatComponents.seconds = - parseEXIFRational(buf + data + tiff_header_start + 16, alignIntel); - this->GeoLocation.Latitude = - this->GeoLocation.LatComponents.degrees + - this->GeoLocation.LatComponents.minutes / 60 + - this->GeoLocation.LatComponents.seconds / 3600; - if ('S' == this->GeoLocation.LatComponents.direction) - this->GeoLocation.Latitude = -this->GeoLocation.Latitude; - } - break; - - case 3: - // GPS east or west - this->GeoLocation.LonComponents.direction = *(buf + offs + 8); - if ('W' == this->GeoLocation.LonComponents.direction) - this->GeoLocation.Longitude = -this->GeoLocation.Longitude; - break; - - case 4: - // GPS longitude - if (format == 5 && length == 3) { - this->GeoLocation.LonComponents.degrees = - parseEXIFRational(buf + data + tiff_header_start, alignIntel); - this->GeoLocation.LonComponents.minutes = - parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel); - this->GeoLocation.LonComponents.seconds = - parseEXIFRational(buf + data + tiff_header_start + 16, alignIntel); - this->GeoLocation.Longitude = - this->GeoLocation.LonComponents.degrees + - this->GeoLocation.LonComponents.minutes / 60 + - this->GeoLocation.LonComponents.seconds / 3600; - if ('W' == this->GeoLocation.LonComponents.direction) - this->GeoLocation.Longitude = -this->GeoLocation.Longitude; - } - break; - - case 5: - // GPS altitude reference (below or above sea level) - this->GeoLocation.AltitudeRef = *(buf + offs + 8); - if (1 == this->GeoLocation.AltitudeRef) - this->GeoLocation.Altitude = -this->GeoLocation.Altitude; - break; - - case 6: - // GPS altitude reference - if (format == 5) { - this->GeoLocation.Altitude = - parseEXIFRational(buf + data + tiff_header_start, alignIntel); - if (1 == this->GeoLocation.AltitudeRef) - this->GeoLocation.Altitude = -this->GeoLocation.Altitude; - } - break; - - case 7: - // GPS timestamp - if (format == 5 && length == 3) { - std::ostringstream os; - os.setf(std::ios_base::scientific, std::ios_base::floatfield); - os.precision(8); - os << parseEXIFRational(buf + data + tiff_header_start, alignIntel); - os << parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel); - os << parseEXIFRational(buf + data + tiff_header_start + 16, alignIntel); - this->GeoLocation.GPSTimeStamp = os.str(); - } - break; - - case 29: - // GPS datestamp - if (format == 2) - this->GeoLocation.GPSDateStamp = parseEXIFString(buf, length, data, tiff_header_start, len, alignIntel); - break; - - case 30: - // GPS differential - if (format == 3) - this->GeoLocation.GPSDifferential = parse16(buf + data + tiff_header_start, alignIntel); - break; - } - offs += 12; - } - } - - return PARSE_EXIF_SUCCESS; -} - -void EXIFInfo::clear() { - // Strings - ImageDescription = ""; - Make = ""; - Model = ""; - Software = ""; - DateTime = ""; - DateTimeOriginal = ""; - DateTimeDigitized = ""; - SubSecTimeOriginal= ""; - Copyright = ""; - - // Shorts / unsigned / double - ByteAlign = 0; - Orientation = 0; - - BitsPerSample = 0; - ExposureTime = 0; - FNumber = 0; - ISOSpeedRatings = 0; - ShutterSpeedValue = 0; - ExposureBiasValue = 0; - SubjectDistance = 0; - FocalLength = 0; - FocalLengthIn35mm = 0; - FocalPlaneXResolution = 0; - FocalPlaneYResolution = 0; - FocalPlaneResolutionUnit = 0; - Flash = 0; - MeteringMode = 0; - ImageWidth = 0; - ImageHeight = 0; - - // Geolocation - GeoLocation.Latitude = 0; - GeoLocation.Longitude = 0; - GeoLocation.Altitude = 0; - GeoLocation.AltitudeRef = 0; - GeoLocation.GPSDifferential = 0; - GeoLocation.GPSTimeStamp = ""; - GeoLocation.GPSDateStamp = ""; - GeoLocation.LatComponents.degrees = 0; - GeoLocation.LatComponents.minutes = 0; - GeoLocation.LatComponents.seconds = 0; - GeoLocation.LatComponents.direction = 0; - GeoLocation.LonComponents.degrees = 0; - GeoLocation.LonComponents.minutes = 0; - GeoLocation.LonComponents.seconds = 0; - GeoLocation.LonComponents.direction = 0; -} diff --git a/libs/IO/EXIF.h b/libs/IO/EXIF.h deleted file mode 100644 index d39395c5d..000000000 --- a/libs/IO/EXIF.h +++ /dev/null @@ -1,157 +0,0 @@ -/************************************************************************** - exif.h -- A simple ISO C++ library to parse basic EXIF - information from a JPEG file. - - Based on the description of the EXIF file format at: - -- http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html - -- http://www.media.mit.edu/pia/Research/deepview/exif.html - -- http://www.exif.org/Exif2-2.PDF - - Copyright (c) 2010-2013 Mayank Lahiri - mlahiri@gmail.com - All rights reserved. - - VERSION HISTORY: - ================ - - current: modified by cDc@seacave - - 2.1: Released July 2013 - -- fixed a bug where JPEGs without an EXIF SubIFD would not be parsed - -- fixed a bug in parsing GPS coordinate seconds - -- fixed makefile bug - -- added two pathological test images from Matt Galloway - http://www.galloway.me.uk/2012/01/uiimageorientation-exif-orientation-sample-images/ - -- split main parsing routine for easier integration into Firefox - - 2.0: Released February 2013 - -- complete rewrite - -- no new/delete - -- added GPS support - - 1.0: Released 2010 - - 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. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 FREEBSD PROJECT 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. -*/ -#ifndef __EXIF_H -#define __EXIF_H - -#include - -// -// Class responsible for storing and parsing EXIF information from a JPEG blob -// -class IO_API EXIFInfo { - public: - // Parsing function for an entire JPEG image buffer. - // - // PARAM 'data': A pointer to a JPEG image. - // PARAM 'length': The length of the JPEG image. - // RETURN: PARSE_EXIF_SUCCESS (0) on succes with 'result' filled out - // error code otherwise, as defined by the PARSE_EXIF_ERROR_* macros - int parseFrom(const unsigned char *data, unsigned length); - int parseFrom(const std::string &data); - - // Parsing function for an EXIF segment. This is used internally by parseFrom() - // but can be called for special cases where only the EXIF section is - // available (i.e., a blob starting with the bytes "Exif\0\0"). - int parseFromEXIFSegment(const unsigned char *buf, unsigned len); - - // Set all data members to default values. - void clear(); - - // Data fields filled out by parseFrom() - char ByteAlign; // 0 = Motorola byte alignment, 1 = Intel - std::string ImageDescription; // Image description - std::string Make; // Camera manufacturer's name - std::string Model; // Camera model - unsigned short Orientation; // Image orientation, start of data corresponds to - // 0: unspecified in EXIF data - // 1: upper left of image - // 3: lower right of image - // 6: upper right of image - // 8: lower left of image - // 9: undefined - unsigned short BitsPerSample; // Number of bits per component - std::string Software; // Software used - std::string DateTime; // File change date and time - std::string DateTimeOriginal; // Original file date and time (may not exist) - std::string DateTimeDigitized; // Digitization date and time (may not exist) - std::string SubSecTimeOriginal; // Sub-second time that original picture was taken - std::string Copyright; // File copyright information - double ExposureTime; // Exposure time in seconds - double FNumber; // F/stop - unsigned short ISOSpeedRatings; // ISO speed - double ShutterSpeedValue; // Shutter speed (reciprocal of exposure time) - double ExposureBiasValue; // Exposure bias value in EV - double SubjectDistance; // Distance to focus point in meters - double FocalLength; // Focal length of lens in millimeters - unsigned short FocalLengthIn35mm; // Focal length in 35mm film - double FocalPlaneXResolution; // Indicates the number of pixels in the image width (X) direction per FocalPlaneResolutionUnit on the camera focal plane (may not exist) - double FocalPlaneYResolution; // Indicates the number of pixels in the image width (Y) direction per FocalPlaneResolutionUnit on the camera focal plane (may not exist) - unsigned short FocalPlaneResolutionUnit;// Indicates the unit for measuring FocalPlaneXResolution and FocalPlaneYResolution (may not exist) - // 0: unspecified in EXIF data - // 1: no absolute unit of measurement - // 2: inch - // 3: centimeter - char Flash; // 0 = no flash, 1 = flash used - unsigned short MeteringMode; // Metering mode - // 1: average - // 2: center weighted average - // 3: spot - // 4: multi-spot - // 5: multi-segment - unsigned ImageWidth; // Image width reported in EXIF data - unsigned ImageHeight; // Image height reported in EXIF data - struct Geolocation_t { // GPS information embedded in file - double Latitude; // Image latitude expressed as decimal - double Longitude; // Image longitude expressed as decimal - double Altitude; // Altitude in meters, relative to sea level - char AltitudeRef; // 0 = above sea level, -1 = below sea level - unsigned short GPSDifferential; // Indicates whether differential correction is applied to the GPS receiver (may not exist) - // 0: measurement without differential correction - // 1: differential correction applied - std::string GPSTimeStamp; // Indicates the time as UTC (Coordinated Universal Time) (may not exist) - std::string GPSDateStamp; // A character string recording date and time information relative to UTC (Coordinated Universal Time) YYYY:MM:DD (may not exist) - struct Coord_t { - double degrees; - double minutes; - double seconds; - char direction; - } LatComponents, LonComponents; // Latitude, Longitude expressed in deg/min/sec - } GeoLocation; - EXIFInfo() { - clear(); - } -}; - -// Parse was successful -#define PARSE_EXIF_SUCCESS 0 -// No JPEG markers found in buffer, possibly invalid JPEG file -#define PARSE_EXIF_ERROR_NO_JPEG 1982 -// No EXIF header found in JPEG file. -#define PARSE_EXIF_ERROR_NO_EXIF 1983 -// Byte alignment specified in EXIF file was unknown (not Motorola or Intel). -#define PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN 1984 -// EXIF header was found, but data was corrupted. -#define PARSE_EXIF_ERROR_CORRUPT 1985 - -#endif diff --git a/libs/IO/ImageEXIF.cpp b/libs/IO/ImageEXIF.cpp deleted file mode 100644 index 3e7c507a9..000000000 --- a/libs/IO/ImageEXIF.cpp +++ /dev/null @@ -1,224 +0,0 @@ -//////////////////////////////////////////////////////////////////// -// ImageEXIF.cpp -// -// Copyright 2007 cDc@seacave -// Distributed under the Boost Software License, Version 1.0 -// (See http://www.boost.org/LICENSE_1_0.txt) - -#include "Common.h" - -#ifdef _IMAGE_EXIF -#include "ImageEXIF.h" - -#if !defined(EXV_HAVE_STDINT_H) && _MSC_VER < 1600 -#define EXV_HAVE_STDINT_H -#endif -#include -#include - -using namespace SEACAVE; - - -// D E F I N E S /////////////////////////////////////////////////// - - -// S T R U C T S /////////////////////////////////////////////////// - -class Exiv2IO : public Exiv2::BasicIo { -public: - Exiv2IO(ISTREAM& f) : m_stream(f) {} - virtual ~Exiv2IO() {} - virtual int open() { return 0; } - virtual int close() { return 0; } - virtual long write(const Exiv2::byte* data, long wcount) { return 0; /*m_stream.write(data, wcount);*/ } - virtual long write(Exiv2::BasicIo& src) { return 0; } - virtual int putb(Exiv2::byte data) { return 0; } - virtual Exiv2::DataBuf read(long rcount) - { - Exiv2::DataBuf buf(rcount); - buf.size_ = (long)m_stream.read(buf.pData_, buf.size_); - if (buf.size_ == STREAM_ERROR) - buf.size_ = 0; - return buf; - } - virtual long read(Exiv2::byte* buf, long rcount) - { - const size_t ret = m_stream.read(buf, rcount); - if (ret == STREAM_ERROR) - return 0; - return (long)ret; - } - virtual int getb() - { - int ret = 0; - if (STREAM_ERROR == m_stream.read(&ret, 1)) - return EOF; - return ret; - } - virtual void transfer(Exiv2::BasicIo& src) {} - #ifdef _MSC_VER - virtual int seek(uint64_t offset, Position pos) - #else - virtual int seek(long offset, Position pos) - #endif - { - bool ret; - switch (pos) { - case beg: - ret = m_stream.setPos(offset); - break; - case cur: - ret = m_stream.setPos(m_stream.getPos()+offset); - break; - case end: - ret = m_stream.setPos(m_stream.getSize()-offset); - break; - } - if (ret == true) - return 0; - return -1; - } - virtual Exiv2::byte* mmap(bool isWriteable) { return NULL; } - virtual int munmap() { return 0; } - virtual long tell() const { return (long)m_stream.getPos(); } - virtual long size() const { return (long)m_stream.getSize(); } - virtual bool isopen() const { return true; } - virtual int error() const { return 0; } - virtual bool eof() const { return (m_stream.getPos() == m_stream.getSize()); } - virtual std::string path() const { return std::string(); } - #ifdef EXV_UNICODE_PATH - virtual std::wstring wpath() const { return std::wstring(); } - #endif - virtual Exiv2IO::AutoPtr temporary() const { return Exiv2IO::AutoPtr(); } - -protected: - ISTREAM& m_stream; -}; // class Exiv2IO - -struct Exiv2Struct { - Exiv2::Image::AutoPtr pImage; -}; - - - -// S T R U C T S /////////////////////////////////////////////////// - -CImageEXIF::CImageEXIF() -{ -} // Constructor - -CImageEXIF::~CImageEXIF() -{ - //clean up - Close(); -} // Destructor -/*----------------------------------------------------------------*/ - -void CImageEXIF::Close() -{ -} -/*----------------------------------------------------------------*/ - -HRESULT CImageEXIF::ReadHeader() -{ - m_state = new Exiv2Struct; - Exiv2Struct& state = *m_state; - state.pImage = Exiv2::ImageFactory::open(Exiv2::BasicIo::AutoPtr(new Exiv2IO(*((ISTREAM*)m_pStream)))); - if (state.pImage.get() == NULL) { - m_state.Release(); - return _FAIL; - } - state.pImage->readMetadata(); - return _OK; -} // ReadHeader -/*----------------------------------------------------------------*/ - -HRESULT CImageEXIF::ReadData(void* pData, PIXELFORMAT dataFormat, Size nStride, Size lineWidth) -{ - return _FAIL; -} // ReadData -/*----------------------------------------------------------------*/ - -HRESULT CImageEXIF::WriteHeader(PIXELFORMAT imageFormat, Size width, Size height, BYTE numLevels) -{ - //TODO: to implement the EXIF encoder - return _OK; -} // WriteHeader -/*----------------------------------------------------------------*/ - -HRESULT CImageEXIF::WriteData(void* pData, PIXELFORMAT dataFormat, Size nStride, Size lineWidth) -{ - //TODO: to implement the EXIF encoder - return _OK; -} // WriteData -/*----------------------------------------------------------------*/ - -bool CImageEXIF::HasEXIF() const -{ - if (m_state == NULL) - return false; - return !m_state->pImage->exifData().empty(); -} // HasEXIF -bool CImageEXIF::HasIPTC() const -{ - if (m_state == NULL) - return false; - return !m_state->pImage->iptcData().empty(); -} // HasIPTC -bool CImageEXIF::HasXMP() const -{ - if (m_state == NULL) - return false; - return !m_state->pImage->xmpData().empty(); -} // HasXMP -/*----------------------------------------------------------------*/ - -String CImageEXIF::ReadKeyEXIF(const String& name, bool bInterpret) const -{ - const Exiv2Struct& state = *m_state; - const Exiv2::ExifData& exifData = state.pImage->exifData(); - ASSERT(!exifData.empty()); - const Exiv2::ExifKey key("Exif."+name); - Exiv2::ExifData::const_iterator it = exifData.findKey(key); - if (it == exifData.end()) - return String(); - if (bInterpret) - return it->print(); - return it->value().toString(); -} // ReadKeyEXIF -/*----------------------------------------------------------------*/ - -void CImageEXIF::DumpAll() -{ - const Exiv2Struct& state = *m_state; - // print EXIF info - const Exiv2::ExifData& exifData = state.pImage->exifData(); - if (exifData.empty()) { - LOG(LT_IMAGE, _T("Info: Image constains no EXIF data")); - } else { - Exiv2::ExifData::const_iterator end = exifData.end(); - for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) - LOG(LT_IMAGE, _T("%s %d %s %d %s"), i->key().c_str(), i->tag(), i->typeName(), i->count(), i->print().c_str()); - } - // print IPTC info - const Exiv2::IptcData& iptcData = state.pImage->iptcData(); - if (iptcData.empty()) { - LOG(LT_IMAGE, _T("Info: Image constains no IPTC data")); - } else { - Exiv2::IptcData::const_iterator end = iptcData.end(); - for (Exiv2::IptcData::const_iterator i = iptcData.begin(); i != end; ++i) - LOG(LT_IMAGE, _T("%s %d %s %d %s"), i->key().c_str(), i->tag(), i->typeName(), i->count(), i->print().c_str()); - } - // print XMP info - const Exiv2::XmpData& xmpData = state.pImage->xmpData(); - if (xmpData.empty()) { - LOG(LT_IMAGE, _T("Info: Image constains no XMP data")); - } else { - Exiv2::XmpData::const_iterator end = xmpData.end(); - for (Exiv2::XmpData::const_iterator i = xmpData.begin(); i != end; ++i) - LOG(LT_IMAGE, _T("%s %d %s %d %s"), i->key().c_str(), i->tag(), i->typeName(), i->count(), i->print().c_str()); - } -} // DumpAll -/*----------------------------------------------------------------*/ - -#endif // _IMAGE_EXIF diff --git a/libs/IO/ImageEXIF.h b/libs/IO/ImageEXIF.h deleted file mode 100644 index a4703ca6e..000000000 --- a/libs/IO/ImageEXIF.h +++ /dev/null @@ -1,54 +0,0 @@ -//////////////////////////////////////////////////////////////////// -// ImageEXIF.h -// -// Copyright 2007 cDc@seacave -// Distributed under the Boost Software License, Version 1.0 -// (See http://www.boost.org/LICENSE_1_0.txt) - -#ifndef __SEACAVE_IMAGEEXIV_H__ -#define __SEACAVE_IMAGEEXIV_H__ - - -// D E F I N E S /////////////////////////////////////////////////// - -struct Exiv2Struct; - - -// I N C L U D E S ///////////////////////////////////////////////// - -#include "Image.h" - - -namespace SEACAVE { - -// S T R U C T S /////////////////////////////////////////////////// - -class IO_API CImageEXIF : public CImage -{ -public: - CImageEXIF(); - virtual ~CImageEXIF(); - - void Close(); - - HRESULT ReadHeader(); - HRESULT ReadData(void*, PIXELFORMAT, Size nStride, Size lineWidth); - HRESULT WriteHeader(PIXELFORMAT, Size width, Size height, BYTE numLevels); - HRESULT WriteData(void*, PIXELFORMAT, Size nStride, Size lineWidth); - - bool HasEXIF() const; - bool HasIPTC() const; - bool HasXMP() const; - - String ReadKeyEXIF(const String& name, bool bInterpret=true) const; - - void DumpAll(); - -protected: - CAutoPtr m_state; -}; // class CImageEXIF -/*----------------------------------------------------------------*/ - -} // namespace SEACAVE - -#endif // __SEACAVE_IMAGEEXIV_H__ diff --git a/libs/IO/PLY.cpp b/libs/IO/PLY.cpp index 80497f4c1..a1b4d94a6 100644 --- a/libs/IO/PLY.cpp +++ b/libs/IO/PLY.cpp @@ -26,11 +26,11 @@ using namespace SEACAVE; // S T R U C T S /////////////////////////////////////////////////// -const char * PLY::type_names[] = { +const char* const PLY::type_names[] = { "invalid", "int8", "int16", "int32", "uint8", "uint16", "uint32", "float32", "float64", }; -const char * PLY::old_type_names[] = { +const char* const PLY::old_type_names[] = { "invalid", "char", "short", "int", "uchar", "ushort", "uint", "float", "double", }; @@ -66,7 +66,8 @@ Init PLY data as empty. PLY::PLY() : mfp(NULL), f(NULL), ostream(NULL), - which_elem(NULL), other_elems(NULL), current_rules(NULL), rule_list(NULL) + which_elem(NULL), other_elems(NULL), current_rules(NULL), rule_list(NULL), + write_type_names(type_names) { } @@ -998,6 +999,16 @@ void PLY::flush() } +/****************************************************************************** +Use old PLY type names during writing for backward compatibility. +******************************************************************************/ + +void PLY::set_legacy_type_names() +{ + write_type_names = old_type_names; +} + + /****************************************************************************** Get version number and file type of a PlyFile. @@ -1271,7 +1282,7 @@ void PLY::write_scalar_type(OSTREAM* fp, int code) abort_ply("error: write_scalar_type: bad data code = %d", code); /* write the code to a file */ - fp->print("%s", type_names[code]); + fp->print("%s", write_type_names[code]); } diff --git a/libs/IO/PLY.h b/libs/IO/PLY.h index b39ba3720..d6354edef 100644 --- a/libs/IO/PLY.h +++ b/libs/IO/PLY.h @@ -161,6 +161,8 @@ class IO_API PLY void flush(); void release(); + void set_legacy_type_names(); + void get_info(float *, int *); void append_comment(const char *); @@ -298,10 +300,11 @@ class IO_API PLY PlyPropRules *current_rules; /* current propagation rules */ PlyRuleList *rule_list; /* rule list from user */ std::vector vals; /* rule list from user */ + const char* const* write_type_names; /* names of scalar types to be used for writing (new types by default) */ protected: - static const char *type_names[9]; // names of scalar types - static const char *old_type_names[9]; // old names of types for backward compatibility + static const char* const type_names[9]; // names of scalar types + static const char* const old_type_names[9]; // old names of types for backward compatibility static const int ply_type_size[9]; static const RuleName rule_name_list[7]; }; diff --git a/libs/IO/TinyXML2.cpp b/libs/IO/TinyXML2.cpp deleted file mode 100644 index 272decb9a..000000000 --- a/libs/IO/TinyXML2.cpp +++ /dev/null @@ -1,2257 +0,0 @@ -/* -Original code by Lee Thomason (www.grinninglizard.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include "Common.h" -#include "TinyXML2.h" - -#ifdef ANDROID_NDK -# include -#else -# include -#endif - -static const char LINE_FEED = (char)0x0a; // all line endings are normalized to LF -static const char LF = LINE_FEED; -static const char CARRIAGE_RETURN = (char)0x0d; // CR gets filtered out -static const char CR = CARRIAGE_RETURN; -static const char SINGLE_QUOTE = '\''; -static const char DOUBLE_QUOTE = '\"'; - -// Bunch of unicode info at: -// http://www.unicode.org/faq/utf_bom.html -// ef bb bf (Microsoft "lead bytes") - designates UTF-8 - -static const unsigned char TIXML_UTF_LEAD_0 = 0xefU; -static const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; -static const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; - -namespace tinyxml2 -{ - -struct Entity { - const char* pattern; - int length; - char value; -}; - -static const int NUM_ENTITIES = 5; -static const Entity entities[NUM_ENTITIES] = { - { "quot", 4, DOUBLE_QUOTE }, - { "amp", 3, '&' }, - { "apos", 4, SINGLE_QUOTE }, - { "lt", 2, '<' }, - { "gt", 2, '>' } -}; - - -StrPair::~StrPair() -{ - Reset(); -} - - -void StrPair::Reset() -{ - if ( _flags & NEEDS_DELETE ) { - delete [] _start; - } - _flags = 0; - _start = 0; - _end = 0; -} - - -void StrPair::SetStr( const char* str, int flags ) -{ - Reset(); - size_t len = strlen( str ); - _start = new char[ len+1 ]; - memcpy( _start, str, len+1 ); - _end = _start + len; - _flags = flags | NEEDS_DELETE; -} - - -char* StrPair::ParseText( char* p, const char* endTag, int strFlags ) -{ - TIXMLASSERT( endTag && *endTag ); - - char* start = p; - char endChar = *endTag; - size_t length = strlen( endTag ); - - // Inner loop of text parsing. - while ( *p ) { - if ( *p == endChar && strncmp( p, endTag, length ) == 0 ) { - Set( start, p, strFlags ); - return p + length; - } - ++p; - } - return 0; -} - - -char* StrPair::ParseName( char* p ) -{ - if ( !p || !(*p) ) { - return 0; - } - - char* const start = p; - - while( *p && ( p == start ? XMLUtil::IsNameStartChar( *p ) : XMLUtil::IsNameChar( *p ) )) { - ++p; - } - - if ( p > start ) { - Set( start, p, 0 ); - return p; - } - return 0; -} - - -void StrPair::CollapseWhitespace() -{ - // Adjusting _start would cause undefined behavior on delete[] - TIXMLASSERT( ( _flags & NEEDS_DELETE ) == 0 ); - // Trim leading space. - _start = XMLUtil::SkipWhiteSpace( _start ); - - if ( _start && *_start ) { - char* p = _start; // the read pointer - char* q = _start; // the write pointer - - while( *p ) { - if ( XMLUtil::IsWhiteSpace( *p )) { - p = XMLUtil::SkipWhiteSpace( p ); - if ( *p == 0 ) { - break; // don't write to q; this trims the trailing space. - } - *q = ' '; - ++q; - } - *q = *p; - ++q; - ++p; - } - *q = 0; - } -} - - -const char* StrPair::GetStr() -{ - if ( _flags & NEEDS_FLUSH ) { - *_end = 0; - _flags ^= NEEDS_FLUSH; - - if ( _flags ) { - char* p = _start; // the read pointer - char* q = _start; // the write pointer - - while( p < _end ) { - if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == CR ) { - // CR-LF pair becomes LF - // CR alone becomes LF - // LF-CR becomes LF - if ( *(p+1) == LF ) { - p += 2; - } - else { - ++p; - } - *q++ = LF; - } - else if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == LF ) { - if ( *(p+1) == CR ) { - p += 2; - } - else { - ++p; - } - *q++ = LF; - } - else if ( (_flags & NEEDS_ENTITY_PROCESSING) && *p == '&' ) { - // Entities handled by tinyXML2: - // - special entities in the entity table [in/out] - // - numeric character reference [in] - // 中 or 中 - - if ( *(p+1) == '#' ) { - const int buflen = 10; - char buf[buflen] = { 0 }; - int len = 0; - p = const_cast( XMLUtil::GetCharacterRef( p, buf, &len ) ); - TIXMLASSERT( 0 <= len && len <= buflen ); - TIXMLASSERT( q + len <= p ); - memcpy( q, buf, len ); - q += len; - } - else { - int i=0; - for(; i(p); - // Check for BOM: - if ( *(pu+0) == TIXML_UTF_LEAD_0 - && *(pu+1) == TIXML_UTF_LEAD_1 - && *(pu+2) == TIXML_UTF_LEAD_2 ) { - *bom = true; - p += 3; - } - return p; -} - - -void XMLUtil::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) -{ - const unsigned long BYTE_MASK = 0xBF; - const unsigned long BYTE_MARK = 0x80; - const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; - - if (input < 0x80) { - *length = 1; - } - else if ( input < 0x800 ) { - *length = 2; - } - else if ( input < 0x10000 ) { - *length = 3; - } - else if ( input < 0x200000 ) { - *length = 4; - } - else { - *length = 0; // This code won't covert this correctly anyway. - return; - } - - output += *length; - - // Scary scary fall throughs. - switch (*length) { - case 4: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - case 3: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - case 2: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - case 1: - --output; - *output = (char)(input | FIRST_BYTE_MARK[*length]); - default: - break; - } -} - - -const char* XMLUtil::GetCharacterRef( const char* p, char* value, int* length ) -{ - // Presume an entity, and pull it out. - *length = 0; - - if ( *(p+1) == '#' && *(p+2) ) { - unsigned long ucs = 0; - ptrdiff_t delta = 0; - unsigned mult = 1; - - if ( *(p+2) == 'x' ) { - // Hexadecimal. - if ( !*(p+3) ) { - return 0; - } - - const char* q = p+3; - q = strchr( q, ';' ); - - if ( !q || !*q ) { - return 0; - } - - delta = q-p; - --q; - - while ( *q != 'x' ) { - if ( *q >= '0' && *q <= '9' ) { - ucs += mult * (*q - '0'); - } - else if ( *q >= 'a' && *q <= 'f' ) { - ucs += mult * (*q - 'a' + 10); - } - else if ( *q >= 'A' && *q <= 'F' ) { - ucs += mult * (*q - 'A' + 10 ); - } - else { - return 0; - } - mult *= 16; - --q; - } - } - else { - // Decimal. - if ( !*(p+2) ) { - return 0; - } - - const char* q = p+2; - q = strchr( q, ';' ); - - if ( !q || !*q ) { - return 0; - } - - delta = q-p; - --q; - - while ( *q != '#' ) { - if ( *q >= '0' && *q <= '9' ) { - ucs += mult * (*q - '0'); - } - else { - return 0; - } - mult *= 10; - --q; - } - } - // convert the UCS to UTF-8 - ConvertUTF32ToUTF8( ucs, value, length ); - return p + delta + 1; - } - return p+1; -} - - -void XMLUtil::ToStr( int v, char* buffer, int bufferSize ) -{ - TIXML_SNPRINTF( buffer, bufferSize, "%d", v ); -} - - -void XMLUtil::ToStr( unsigned v, char* buffer, int bufferSize ) -{ - TIXML_SNPRINTF( buffer, bufferSize, "%u", v ); -} - - -void XMLUtil::ToStr( bool v, char* buffer, int bufferSize ) -{ - TIXML_SNPRINTF( buffer, bufferSize, "%d", v ? 1 : 0 ); -} - -/* - ToStr() of a number is a very tricky topic. - https://github.com/leethomason/tinyxml2/issues/106 -*/ -void XMLUtil::ToStr( float v, char* buffer, int bufferSize ) -{ - TIXML_SNPRINTF( buffer, bufferSize, "%.8g", v ); -} - - -void XMLUtil::ToStr( double v, char* buffer, int bufferSize ) -{ - TIXML_SNPRINTF( buffer, bufferSize, "%.17g", v ); -} - - -bool XMLUtil::ToInt( const char* str, int* value ) -{ - if ( TIXML_SSCANF( str, "%d", value ) == 1 ) { - return true; - } - return false; -} - -bool XMLUtil::ToUnsigned( const char* str, unsigned *value ) -{ - if ( TIXML_SSCANF( str, "%u", value ) == 1 ) { - return true; - } - return false; -} - -bool XMLUtil::ToBool( const char* str, bool* value ) -{ - int ival = 0; - if ( ToInt( str, &ival )) { - *value = (ival==0) ? false : true; - return true; - } - if ( StringEqual( str, "true" ) ) { - *value = true; - return true; - } - else if ( StringEqual( str, "false" ) ) { - *value = false; - return true; - } - return false; -} - - -bool XMLUtil::ToFloat( const char* str, float* value ) -{ - if ( TIXML_SSCANF( str, "%f", value ) == 1 ) { - return true; - } - return false; -} - -bool XMLUtil::ToDouble( const char* str, double* value ) -{ - if ( TIXML_SSCANF( str, "%lf", value ) == 1 ) { - return true; - } - return false; -} - - -char* XMLDocument::Identify( char* p, XMLNode** node ) -{ - char* const start = p; - p = XMLUtil::SkipWhiteSpace( p ); - if( !p || !*p ) { - return p; - } - - // What is this thing? - // These strings define the matching patters: - static const char* xmlHeader = { "_memPool = &_commentPool; - p += xmlHeaderLen; - } - else if ( XMLUtil::StringEqual( p, commentHeader, commentHeaderLen ) ) { - returnNode = new (_commentPool.Alloc()) XMLComment( this ); - returnNode->_memPool = &_commentPool; - p += commentHeaderLen; - } - else if ( XMLUtil::StringEqual( p, cdataHeader, cdataHeaderLen ) ) { - XMLText* text = new (_textPool.Alloc()) XMLText( this ); - returnNode = text; - returnNode->_memPool = &_textPool; - p += cdataHeaderLen; - text->SetCData( true ); - } - else if ( XMLUtil::StringEqual( p, dtdHeader, dtdHeaderLen ) ) { - returnNode = new (_commentPool.Alloc()) XMLUnknown( this ); - returnNode->_memPool = &_commentPool; - p += dtdHeaderLen; - } - else if ( XMLUtil::StringEqual( p, elementHeader, elementHeaderLen ) ) { - returnNode = new (_elementPool.Alloc()) XMLElement( this ); - returnNode->_memPool = &_elementPool; - p += elementHeaderLen; - } - else { - returnNode = new (_textPool.Alloc()) XMLText( this ); - returnNode->_memPool = &_textPool; - p = start; // Back it up, all the text counts. - } - - *node = returnNode; - return p; -} - - -bool XMLDocument::Accept( XMLVisitor* visitor ) const -{ - if ( visitor->VisitEnter( *this ) ) { - for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { - if ( !node->Accept( visitor ) ) { - break; - } - } - } - return visitor->VisitExit( *this ); -} - - -// --------- XMLNode ----------- // - -XMLNode::XMLNode( XMLDocument* doc ) : - _document( doc ), - _parent( 0 ), - _firstChild( 0 ), _lastChild( 0 ), - _prev( 0 ), _next( 0 ), - _memPool( 0 ) -{ -} - - -XMLNode::~XMLNode() -{ - DeleteChildren(); - if ( _parent ) { - _parent->Unlink( this ); - } -} - -const char* XMLNode::Value() const -{ - return _value.GetStr(); -} - -void XMLNode::SetValue( const char* str, bool staticMem ) -{ - if ( staticMem ) { - _value.SetInternedStr( str ); - } - else { - _value.SetStr( str ); - } -} - - -void XMLNode::DeleteChildren() -{ - while( _firstChild ) { - XMLNode* node = _firstChild; - Unlink( node ); - - DeleteNode( node ); - } - _firstChild = _lastChild = 0; -} - - -void XMLNode::Unlink( XMLNode* child ) -{ - if ( child == _firstChild ) { - _firstChild = _firstChild->_next; - } - if ( child == _lastChild ) { - _lastChild = _lastChild->_prev; - } - - if ( child->_prev ) { - child->_prev->_next = child->_next; - } - if ( child->_next ) { - child->_next->_prev = child->_prev; - } - child->_parent = 0; -} - - -void XMLNode::DeleteChild( XMLNode* node ) -{ - TIXMLASSERT( node->_parent == this ); - DeleteNode( node ); -} - - -XMLNode* XMLNode::InsertEndChild( XMLNode* addThis ) -{ - if (addThis->_document != _document) - return 0; - - if (addThis->_parent) - addThis->_parent->Unlink( addThis ); - else - addThis->_memPool->SetTracked(); - - if ( _lastChild ) { - TIXMLASSERT( _firstChild ); - TIXMLASSERT( _lastChild->_next == 0 ); - _lastChild->_next = addThis; - addThis->_prev = _lastChild; - _lastChild = addThis; - - addThis->_next = 0; - } - else { - TIXMLASSERT( _firstChild == 0 ); - _firstChild = _lastChild = addThis; - - addThis->_prev = 0; - addThis->_next = 0; - } - addThis->_parent = this; - return addThis; -} - - -XMLNode* XMLNode::InsertFirstChild( XMLNode* addThis ) -{ - if (addThis->_document != _document) - return 0; - - if (addThis->_parent) - addThis->_parent->Unlink( addThis ); - else - addThis->_memPool->SetTracked(); - - if ( _firstChild ) { - TIXMLASSERT( _lastChild ); - TIXMLASSERT( _firstChild->_prev == 0 ); - - _firstChild->_prev = addThis; - addThis->_next = _firstChild; - _firstChild = addThis; - - addThis->_prev = 0; - } - else { - TIXMLASSERT( _lastChild == 0 ); - _firstChild = _lastChild = addThis; - - addThis->_prev = 0; - addThis->_next = 0; - } - addThis->_parent = this; - return addThis; -} - - -XMLNode* XMLNode::InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ) -{ - if (addThis->_document != _document) - return 0; - - TIXMLASSERT( afterThis->_parent == this ); - - if ( afterThis->_parent != this ) { - return 0; - } - - if ( afterThis->_next == 0 ) { - // The last node or the only node. - return InsertEndChild( addThis ); - } - if (addThis->_parent) - addThis->_parent->Unlink( addThis ); - else - addThis->_memPool->SetTracked(); - addThis->_prev = afterThis; - addThis->_next = afterThis->_next; - afterThis->_next->_prev = addThis; - afterThis->_next = addThis; - addThis->_parent = this; - return addThis; -} - - - - -const XMLElement* XMLNode::FirstChildElement( const char* value ) const -{ - for( XMLNode* node=_firstChild; node; node=node->_next ) { - XMLElement* element = node->ToElement(); - if ( element ) { - if ( !value || XMLUtil::StringEqual( element->Name(), value ) ) { - return element; - } - } - } - return 0; -} - - -const XMLElement* XMLNode::LastChildElement( const char* value ) const -{ - for( XMLNode* node=_lastChild; node; node=node->_prev ) { - XMLElement* element = node->ToElement(); - if ( element ) { - if ( !value || XMLUtil::StringEqual( element->Name(), value ) ) { - return element; - } - } - } - return 0; -} - - -const XMLElement* XMLNode::NextSiblingElement( const char* value ) const -{ - for( XMLNode* node=this->_next; node; node = node->_next ) { - const XMLElement* element = node->ToElement(); - if ( element - && (!value || XMLUtil::StringEqual( value, node->Value() ))) { - return element; - } - } - return 0; -} - - -const XMLElement* XMLNode::PreviousSiblingElement( const char* value ) const -{ - for( XMLNode* node=_prev; node; node = node->_prev ) { - const XMLElement* element = node->ToElement(); - if ( element - && (!value || XMLUtil::StringEqual( value, node->Value() ))) { - return element; - } - } - return 0; -} - - -char* XMLNode::ParseDeep( char* p, StrPair* parentEnd ) -{ - // This is a recursive method, but thinking about it "at the current level" - // it is a pretty simple flat list: - // - // - // - // With a special case: - // - // - // - // - // Where the closing element (/foo) *must* be the next thing after the opening - // element, and the names must match. BUT the tricky bit is that the closing - // element will be read by the child. - // - // 'endTag' is the end tag for this node, it is returned by a call to a child. - // 'parentEnd' is the end tag for the parent, which is filled in and returned. - - while( p && *p ) { - XMLNode* node = 0; - - p = _document->Identify( p, &node ); - if ( p == 0 || node == 0 ) { - break; - } - - StrPair endTag; - p = node->ParseDeep( p, &endTag ); - if ( !p ) { - DeleteNode( node ); - node = 0; - if ( !_document->Error() ) { - _document->SetError( XML_ERROR_PARSING, 0, 0 ); - } - break; - } - - XMLElement* ele = node->ToElement(); - // We read the end tag. Return it to the parent. - if ( ele && ele->ClosingType() == XMLElement::CLOSING ) { - if ( parentEnd ) { - *parentEnd = ele->_value; - } - node->_memPool->SetTracked(); // created and then immediately deleted. - DeleteNode( node ); - return p; - } - - // Handle an end tag returned to this level. - // And handle a bunch of annoying errors. - if ( ele ) { - bool mismatch = false; - if ( endTag.Empty() && ele->ClosingType() == XMLElement::OPEN ) { - mismatch = true; - } - else if ( !endTag.Empty() && ele->ClosingType() != XMLElement::OPEN ) { - mismatch = true; - } - else if ( !endTag.Empty() ) { - if ( !XMLUtil::StringEqual( endTag.GetStr(), node->Value() )) { - mismatch = true; - } - } - if ( mismatch ) { - _document->SetError( XML_ERROR_MISMATCHED_ELEMENT, node->Value(), 0 ); - p = 0; - } - } - if ( p == 0 ) { - DeleteNode( node ); - node = 0; - } - if ( node ) { - this->InsertEndChild( node ); - } - } - return 0; -} - -void XMLNode::DeleteNode( XMLNode* node ) -{ - if ( node == 0 ) { - return; - } - MemPool* pool = node->_memPool; - node->~XMLNode(); - pool->Free( node ); -} - -// --------- XMLText ---------- // -char* XMLText::ParseDeep( char* p, StrPair* ) -{ - const char* start = p; - if ( this->CData() ) { - p = _value.ParseText( p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION ); - if ( !p ) { - _document->SetError( XML_ERROR_PARSING_CDATA, start, 0 ); - } - return p; - } - else { - int flags = _document->ProcessEntities() ? StrPair::TEXT_ELEMENT : StrPair::TEXT_ELEMENT_LEAVE_ENTITIES; - if ( _document->WhitespaceMode() == COLLAPSE_WHITESPACE ) { - flags |= StrPair::COLLAPSE_WHITESPACE; - } - - p = _value.ParseText( p, "<", flags ); - if ( !p ) { - _document->SetError( XML_ERROR_PARSING_TEXT, start, 0 ); - } - if ( p && *p ) { - return p-1; - } - } - return 0; -} - - -XMLNode* XMLText::ShallowClone( XMLDocument* doc ) const -{ - if ( !doc ) { - doc = _document; - } - XMLText* text = doc->NewText( Value() ); // fixme: this will always allocate memory. Intern? - text->SetCData( this->CData() ); - return text; -} - - -bool XMLText::ShallowEqual( const XMLNode* compare ) const -{ - const XMLText* text = compare->ToText(); - return ( text && XMLUtil::StringEqual( text->Value(), Value() ) ); -} - - -bool XMLText::Accept( XMLVisitor* visitor ) const -{ - return visitor->Visit( *this ); -} - - -// --------- XMLComment ---------- // - -XMLComment::XMLComment( XMLDocument* doc ) : XMLNode( doc ) -{ -} - - -XMLComment::~XMLComment() -{ -} - - -char* XMLComment::ParseDeep( char* p, StrPair* ) -{ - // Comment parses as text. - const char* start = p; - p = _value.ParseText( p, "-->", StrPair::COMMENT ); - if ( p == 0 ) { - _document->SetError( XML_ERROR_PARSING_COMMENT, start, 0 ); - } - return p; -} - - -XMLNode* XMLComment::ShallowClone( XMLDocument* doc ) const -{ - if ( !doc ) { - doc = _document; - } - XMLComment* comment = doc->NewComment( Value() ); // fixme: this will always allocate memory. Intern? - return comment; -} - - -bool XMLComment::ShallowEqual( const XMLNode* compare ) const -{ - const XMLComment* comment = compare->ToComment(); - return ( comment && XMLUtil::StringEqual( comment->Value(), Value() )); -} - - -bool XMLComment::Accept( XMLVisitor* visitor ) const -{ - return visitor->Visit( *this ); -} - - -// --------- XMLDeclaration ---------- // - -XMLDeclaration::XMLDeclaration( XMLDocument* doc ) : XMLNode( doc ) -{ -} - - -XMLDeclaration::~XMLDeclaration() -{ - //printf( "~XMLDeclaration\n" ); -} - - -char* XMLDeclaration::ParseDeep( char* p, StrPair* ) -{ - // Declaration parses as text. - const char* start = p; - p = _value.ParseText( p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION ); - if ( p == 0 ) { - _document->SetError( XML_ERROR_PARSING_DECLARATION, start, 0 ); - } - return p; -} - - -XMLNode* XMLDeclaration::ShallowClone( XMLDocument* doc ) const -{ - if ( !doc ) { - doc = _document; - } - XMLDeclaration* dec = doc->NewDeclaration( Value() ); // fixme: this will always allocate memory. Intern? - return dec; -} - - -bool XMLDeclaration::ShallowEqual( const XMLNode* compare ) const -{ - const XMLDeclaration* declaration = compare->ToDeclaration(); - return ( declaration && XMLUtil::StringEqual( declaration->Value(), Value() )); -} - - - -bool XMLDeclaration::Accept( XMLVisitor* visitor ) const -{ - return visitor->Visit( *this ); -} - -// --------- XMLUnknown ---------- // - -XMLUnknown::XMLUnknown( XMLDocument* doc ) : XMLNode( doc ) -{ -} - - -XMLUnknown::~XMLUnknown() -{ -} - - -char* XMLUnknown::ParseDeep( char* p, StrPair* ) -{ - // Unknown parses as text. - const char* start = p; - - p = _value.ParseText( p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION ); - if ( !p ) { - _document->SetError( XML_ERROR_PARSING_UNKNOWN, start, 0 ); - } - return p; -} - - -XMLNode* XMLUnknown::ShallowClone( XMLDocument* doc ) const -{ - if ( !doc ) { - doc = _document; - } - XMLUnknown* text = doc->NewUnknown( Value() ); // fixme: this will always allocate memory. Intern? - return text; -} - - -bool XMLUnknown::ShallowEqual( const XMLNode* compare ) const -{ - const XMLUnknown* unknown = compare->ToUnknown(); - return ( unknown && XMLUtil::StringEqual( unknown->Value(), Value() )); -} - - -bool XMLUnknown::Accept( XMLVisitor* visitor ) const -{ - return visitor->Visit( *this ); -} - -// --------- XMLAttribute ---------- // - -const char* XMLAttribute::Name() const -{ - return _name.GetStr(); -} - -const char* XMLAttribute::Value() const -{ - return _value.GetStr(); -} - -char* XMLAttribute::ParseDeep( char* p, bool processEntities ) -{ - // Parse using the name rules: bug fix, was using ParseText before - p = _name.ParseName( p ); - if ( !p || !*p ) { - return 0; - } - - // Skip white space before = - p = XMLUtil::SkipWhiteSpace( p ); - if ( !p || *p != '=' ) { - return 0; - } - - ++p; // move up to opening quote - p = XMLUtil::SkipWhiteSpace( p ); - if ( *p != '\"' && *p != '\'' ) { - return 0; - } - - char endTag[2] = { *p, 0 }; - ++p; // move past opening quote - - p = _value.ParseText( p, endTag, processEntities ? StrPair::ATTRIBUTE_VALUE : StrPair::ATTRIBUTE_VALUE_LEAVE_ENTITIES ); - return p; -} - - -void XMLAttribute::SetName( const char* n ) -{ - _name.SetStr( n ); -} - - -XMLError XMLAttribute::QueryIntValue( int* value ) const -{ - if ( XMLUtil::ToInt( Value(), value )) { - return XML_NO_ERROR; - } - return XML_WRONG_ATTRIBUTE_TYPE; -} - - -XMLError XMLAttribute::QueryUnsignedValue( unsigned int* value ) const -{ - if ( XMLUtil::ToUnsigned( Value(), value )) { - return XML_NO_ERROR; - } - return XML_WRONG_ATTRIBUTE_TYPE; -} - - -XMLError XMLAttribute::QueryBoolValue( bool* value ) const -{ - if ( XMLUtil::ToBool( Value(), value )) { - return XML_NO_ERROR; - } - return XML_WRONG_ATTRIBUTE_TYPE; -} - - -XMLError XMLAttribute::QueryFloatValue( float* value ) const -{ - if ( XMLUtil::ToFloat( Value(), value )) { - return XML_NO_ERROR; - } - return XML_WRONG_ATTRIBUTE_TYPE; -} - - -XMLError XMLAttribute::QueryDoubleValue( double* value ) const -{ - if ( XMLUtil::ToDouble( Value(), value )) { - return XML_NO_ERROR; - } - return XML_WRONG_ATTRIBUTE_TYPE; -} - - -void XMLAttribute::SetAttribute( const char* v ) -{ - _value.SetStr( v ); -} - - -void XMLAttribute::SetAttribute( int v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - _value.SetStr( buf ); -} - - -void XMLAttribute::SetAttribute( unsigned v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - _value.SetStr( buf ); -} - - -void XMLAttribute::SetAttribute( bool v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - _value.SetStr( buf ); -} - -void XMLAttribute::SetAttribute( double v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - _value.SetStr( buf ); -} - -void XMLAttribute::SetAttribute( float v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - _value.SetStr( buf ); -} - - -// --------- XMLElement ---------- // -XMLElement::XMLElement( XMLDocument* doc ) : XMLNode( doc ), - _closingType( 0 ), - _rootAttribute( 0 ) -{ -} - - -XMLElement::~XMLElement() -{ - while( _rootAttribute ) { - XMLAttribute* next = _rootAttribute->_next; - DeleteAttribute( _rootAttribute ); - _rootAttribute = next; - } -} - - -XMLAttribute* XMLElement::FindAttribute( const char* name ) -{ - for( XMLAttribute* a = _rootAttribute; a; a = a->_next ) { - if ( XMLUtil::StringEqual( a->Name(), name ) ) { - return a; - } - } - return 0; -} - - -const XMLAttribute* XMLElement::FindAttribute( const char* name ) const -{ - for( XMLAttribute* a = _rootAttribute; a; a = a->_next ) { - if ( XMLUtil::StringEqual( a->Name(), name ) ) { - return a; - } - } - return 0; -} - - -const char* XMLElement::Attribute( const char* name, const char* value ) const -{ - const XMLAttribute* a = FindAttribute( name ); - if ( !a ) { - return 0; - } - if ( !value || XMLUtil::StringEqual( a->Value(), value )) { - return a->Value(); - } - return 0; -} - - -const char* XMLElement::GetText() const -{ - if ( FirstChild() && FirstChild()->ToText() ) { - return FirstChild()->Value(); - } - return 0; -} - - -void XMLElement::SetText( const char* inText ) -{ - if ( FirstChild() && FirstChild()->ToText() ) - FirstChild()->SetValue( inText ); - else { - XMLText* theText = GetDocument()->NewText( inText ); - InsertFirstChild( theText ); - } -} - - -void XMLElement::SetText( int v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - SetText( buf ); -} - - -void XMLElement::SetText( unsigned v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - SetText( buf ); -} - - -void XMLElement::SetText( bool v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - SetText( buf ); -} - - -void XMLElement::SetText( float v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - SetText( buf ); -} - - -void XMLElement::SetText( double v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - SetText( buf ); -} - - -XMLError XMLElement::QueryIntText( int* ival ) const -{ - if ( FirstChild() && FirstChild()->ToText() ) { - const char* t = FirstChild()->Value(); - if ( XMLUtil::ToInt( t, ival ) ) { - return XML_SUCCESS; - } - return XML_CAN_NOT_CONVERT_TEXT; - } - return XML_NO_TEXT_NODE; -} - - -XMLError XMLElement::QueryUnsignedText( unsigned* uval ) const -{ - if ( FirstChild() && FirstChild()->ToText() ) { - const char* t = FirstChild()->Value(); - if ( XMLUtil::ToUnsigned( t, uval ) ) { - return XML_SUCCESS; - } - return XML_CAN_NOT_CONVERT_TEXT; - } - return XML_NO_TEXT_NODE; -} - - -XMLError XMLElement::QueryBoolText( bool* bval ) const -{ - if ( FirstChild() && FirstChild()->ToText() ) { - const char* t = FirstChild()->Value(); - if ( XMLUtil::ToBool( t, bval ) ) { - return XML_SUCCESS; - } - return XML_CAN_NOT_CONVERT_TEXT; - } - return XML_NO_TEXT_NODE; -} - - -XMLError XMLElement::QueryDoubleText( double* dval ) const -{ - if ( FirstChild() && FirstChild()->ToText() ) { - const char* t = FirstChild()->Value(); - if ( XMLUtil::ToDouble( t, dval ) ) { - return XML_SUCCESS; - } - return XML_CAN_NOT_CONVERT_TEXT; - } - return XML_NO_TEXT_NODE; -} - - -XMLError XMLElement::QueryFloatText( float* fval ) const -{ - if ( FirstChild() && FirstChild()->ToText() ) { - const char* t = FirstChild()->Value(); - if ( XMLUtil::ToFloat( t, fval ) ) { - return XML_SUCCESS; - } - return XML_CAN_NOT_CONVERT_TEXT; - } - return XML_NO_TEXT_NODE; -} - - - -XMLAttribute* XMLElement::FindOrCreateAttribute( const char* name ) -{ - XMLAttribute* last = 0; - XMLAttribute* attrib = 0; - for( attrib = _rootAttribute; - attrib; - last = attrib, attrib = attrib->_next ) { - if ( XMLUtil::StringEqual( attrib->Name(), name ) ) { - break; - } - } - if ( !attrib ) { - attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); - attrib->_memPool = &_document->_attributePool; - if ( last ) { - last->_next = attrib; - } - else { - _rootAttribute = attrib; - } - attrib->SetName( name ); - attrib->_memPool->SetTracked(); // always created and linked. - } - return attrib; -} - - -void XMLElement::DeleteAttribute( const char* name ) -{ - XMLAttribute* prev = 0; - for( XMLAttribute* a=_rootAttribute; a; a=a->_next ) { - if ( XMLUtil::StringEqual( name, a->Name() ) ) { - if ( prev ) { - prev->_next = a->_next; - } - else { - _rootAttribute = a->_next; - } - DeleteAttribute( a ); - break; - } - prev = a; - } -} - - -char* XMLElement::ParseAttributes( char* p ) -{ - const char* start = p; - XMLAttribute* prevAttribute = 0; - - // Read the attributes. - while( p ) { - p = XMLUtil::SkipWhiteSpace( p ); - if ( !p || !(*p) ) { - _document->SetError( XML_ERROR_PARSING_ELEMENT, start, Name() ); - return 0; - } - - // attribute. - if (XMLUtil::IsNameStartChar( *p ) ) { - XMLAttribute* attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); - attrib->_memPool = &_document->_attributePool; - attrib->_memPool->SetTracked(); - - p = attrib->ParseDeep( p, _document->ProcessEntities() ); - if ( !p || Attribute( attrib->Name() ) ) { - DeleteAttribute( attrib ); - _document->SetError( XML_ERROR_PARSING_ATTRIBUTE, start, p ); - return 0; - } - // There is a minor bug here: if the attribute in the source xml - // document is duplicated, it will not be detected and the - // attribute will be doubly added. However, tracking the 'prevAttribute' - // avoids re-scanning the attribute list. Preferring performance for - // now, may reconsider in the future. - if ( prevAttribute ) { - prevAttribute->_next = attrib; - } - else { - _rootAttribute = attrib; - } - prevAttribute = attrib; - } - // end of the tag - else if ( *p == '/' && *(p+1) == '>' ) { - _closingType = CLOSED; - return p+2; // done; sealed element. - } - // end of the tag - else if ( *p == '>' ) { - ++p; - break; - } - else { - _document->SetError( XML_ERROR_PARSING_ELEMENT, start, p ); - return 0; - } - } - return p; -} - -void XMLElement::DeleteAttribute( XMLAttribute* attribute ) -{ - if ( attribute == 0 ) { - return; - } - MemPool* pool = attribute->_memPool; - attribute->~XMLAttribute(); - pool->Free( attribute ); -} - -// -// -// foobar -// -char* XMLElement::ParseDeep( char* p, StrPair* strPair ) -{ - // Read the element name. - p = XMLUtil::SkipWhiteSpace( p ); - if ( !p ) { - return 0; - } - - // The closing element is the form. It is - // parsed just like a regular element then deleted from - // the DOM. - if ( *p == '/' ) { - _closingType = CLOSING; - ++p; - } - - p = _value.ParseName( p ); - if ( _value.Empty() ) { - return 0; - } - - p = ParseAttributes( p ); - if ( !p || !*p || _closingType ) { - return p; - } - - p = XMLNode::ParseDeep( p, strPair ); - return p; -} - - - -XMLNode* XMLElement::ShallowClone( XMLDocument* doc ) const -{ - if ( !doc ) { - doc = _document; - } - XMLElement* element = doc->NewElement( Value() ); // fixme: this will always allocate memory. Intern? - for( const XMLAttribute* a=FirstAttribute(); a; a=a->Next() ) { - element->SetAttribute( a->Name(), a->Value() ); // fixme: this will always allocate memory. Intern? - } - return element; -} - - -bool XMLElement::ShallowEqual( const XMLNode* compare ) const -{ - const XMLElement* other = compare->ToElement(); - if ( other && XMLUtil::StringEqual( other->Value(), Value() )) { - - const XMLAttribute* a=FirstAttribute(); - const XMLAttribute* b=other->FirstAttribute(); - - while ( a && b ) { - if ( !XMLUtil::StringEqual( a->Value(), b->Value() ) ) { - return false; - } - a = a->Next(); - b = b->Next(); - } - if ( a || b ) { - // different count - return false; - } - return true; - } - return false; -} - - -bool XMLElement::Accept( XMLVisitor* visitor ) const -{ - if ( visitor->VisitEnter( *this, _rootAttribute ) ) { - for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { - if ( !node->Accept( visitor ) ) { - break; - } - } - } - return visitor->VisitExit( *this ); -} - - -// --------- XMLDocument ----------- // - -// Warning: List must match 'enum XMLError' -const char* XMLDocument::_errorNames[XML_ERROR_COUNT] = { - "XML_SUCCESS", - "XML_NO_ATTRIBUTE", - "XML_WRONG_ATTRIBUTE_TYPE", - "XML_ERROR_FILE_NOT_FOUND", - "XML_ERROR_FILE_COULD_NOT_BE_OPENED", - "XML_ERROR_FILE_READ_ERROR", - "XML_ERROR_ELEMENT_MISMATCH", - "XML_ERROR_PARSING_ELEMENT", - "XML_ERROR_PARSING_ATTRIBUTE", - "XML_ERROR_IDENTIFYING_TAG", - "XML_ERROR_PARSING_TEXT", - "XML_ERROR_PARSING_CDATA", - "XML_ERROR_PARSING_COMMENT", - "XML_ERROR_PARSING_DECLARATION", - "XML_ERROR_PARSING_UNKNOWN", - "XML_ERROR_EMPTY_DOCUMENT", - "XML_ERROR_MISMATCHED_ELEMENT", - "XML_ERROR_PARSING", - "XML_CAN_NOT_CONVERT_TEXT", - "XML_NO_TEXT_NODE" -}; - - -XMLDocument::XMLDocument( bool processEntities, Whitespace whitespace ) : - XMLNode( 0 ), - _writeBOM( false ), - _processEntities( processEntities ), - _errorID( XML_NO_ERROR ), - _whitespace( whitespace ), - _errorStr1( 0 ), - _errorStr2( 0 ), - _charBuffer( 0 ) -{ - _document = this; // avoid warning about 'this' in initializer list -} - - -XMLDocument::~XMLDocument() -{ - Clear(); -} - - -void XMLDocument::Clear() -{ - DeleteChildren(); - - _errorID = XML_NO_ERROR; - _errorStr1 = 0; - _errorStr2 = 0; - - delete [] _charBuffer; - _charBuffer = 0; - -#if 0 - _textPool.Trace( "text" ); - _elementPool.Trace( "element" ); - _commentPool.Trace( "comment" ); - _attributePool.Trace( "attribute" ); -#endif - -#ifndef _RELEASE - if ( Error() == false ) { - TIXMLASSERT( _elementPool.CurrentAllocs() == _elementPool.Untracked() ); - TIXMLASSERT( _attributePool.CurrentAllocs() == _attributePool.Untracked() ); - TIXMLASSERT( _textPool.CurrentAllocs() == _textPool.Untracked() ); - TIXMLASSERT( _commentPool.CurrentAllocs() == _commentPool.Untracked() ); - } -#endif -} - - -XMLElement* XMLDocument::NewElement( const char* name ) -{ - XMLElement* ele = new (_elementPool.Alloc()) XMLElement( this ); - ele->_memPool = &_elementPool; - ele->SetName( name ); - return ele; -} - - -XMLComment* XMLDocument::NewComment( const char* str ) -{ - XMLComment* comment = new (_commentPool.Alloc()) XMLComment( this ); - comment->_memPool = &_commentPool; - comment->SetValue( str ); - return comment; -} - - -XMLText* XMLDocument::NewText( const char* str ) -{ - XMLText* text = new (_textPool.Alloc()) XMLText( this ); - text->_memPool = &_textPool; - text->SetValue( str ); - return text; -} - - -XMLDeclaration* XMLDocument::NewDeclaration( const char* str ) -{ - XMLDeclaration* dec = new (_commentPool.Alloc()) XMLDeclaration( this ); - dec->_memPool = &_commentPool; - dec->SetValue( str ? str : "xml version=\"1.0\" encoding=\"UTF-8\"" ); - return dec; -} - - -XMLUnknown* XMLDocument::NewUnknown( const char* str ) -{ - XMLUnknown* unk = new (_commentPool.Alloc()) XMLUnknown( this ); - unk->_memPool = &_commentPool; - unk->SetValue( str ); - return unk; -} - -static FILE* callfopen( const char* filepath, const char* mode ) -{ -#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) - FILE* fp = 0; - errno_t err = fopen_s( &fp, filepath, mode ); - if ( err ) { - return 0; - } -#else - FILE* fp = fopen( filepath, mode ); -#endif - return fp; -} - -XMLError XMLDocument::LoadFile( const char* filename ) -{ - Clear(); - FILE* fp = callfopen( filename, "rb" ); - if ( !fp ) { - SetError( XML_ERROR_FILE_NOT_FOUND, filename, 0 ); - return _errorID; - } - LoadFile( fp ); - fclose( fp ); - return _errorID; -} - - -XMLError XMLDocument::LoadFile( FILE* fp ) -{ - Clear(); - - fseek( fp, 0, SEEK_SET ); - if ( fgetc( fp ) == EOF && ferror( fp ) != 0 ) { - SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); - return _errorID; - } - - fseek( fp, 0, SEEK_END ); - const long filelength = ftell( fp ); - fseek( fp, 0, SEEK_SET ); - if ( filelength == -1L ) { - SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); - return _errorID; - } - - const size_t size = filelength; - if ( size == 0 ) { - SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); - return _errorID; - } - - _charBuffer = new char[size+1]; - size_t read = fread( _charBuffer, 1, size, fp ); - if ( read != size ) { - SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); - return _errorID; - } - - _charBuffer[size] = 0; - - const char* p = _charBuffer; - p = XMLUtil::SkipWhiteSpace( p ); - p = XMLUtil::ReadBOM( p, &_writeBOM ); - if ( !p || !*p ) { - SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); - return _errorID; - } - - ParseDeep( _charBuffer + (p-_charBuffer), 0 ); - return _errorID; -} - - -XMLError XMLDocument::SaveFile( const char* filename, bool compact ) -{ - FILE* fp = callfopen( filename, "w" ); - if ( !fp ) { - SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, filename, 0 ); - return _errorID; - } - SaveFile(fp, compact); - fclose( fp ); - return _errorID; -} - - -XMLError XMLDocument::SaveFile( FILE* fp, bool compact ) -{ - XMLPrinter stream( fp, compact ); - Print( &stream ); - return _errorID; -} - - -XMLError XMLDocument::Parse( const char* p, size_t len ) -{ - const char* start = p; - Clear(); - - if ( len == 0 || !p || !*p ) { - SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); - return _errorID; - } - if ( len == (size_t)(-1) ) { - len = strlen( p ); - } - _charBuffer = new char[ len+1 ]; - memcpy( _charBuffer, p, len ); - _charBuffer[len] = 0; - - p = XMLUtil::SkipWhiteSpace( p ); - p = XMLUtil::ReadBOM( p, &_writeBOM ); - if ( !p || !*p ) { - SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); - return _errorID; - } - - ptrdiff_t delta = p - start; // skip initial whitespace, BOM, etc. - ParseDeep( _charBuffer+delta, 0 ); - if (_errorID) { - // clean up now essentially dangling memory. - // and the parse fail can put objects in the - // pools that are dead and inaccessible. - DeleteChildren(); - _elementPool.Clear(); - _attributePool.Clear(); - _textPool.Clear(); - _commentPool.Clear(); - } - return _errorID; -} - - -void XMLDocument::Print( XMLPrinter* streamer ) const -{ - XMLPrinter stdStreamer( stdout ); - if ( !streamer ) { - streamer = &stdStreamer; - } - Accept( streamer ); -} - - -void XMLDocument::SetError( XMLError error, const char* str1, const char* str2 ) -{ - _errorID = error; - _errorStr1 = str1; - _errorStr2 = str2; -} - -const char* XMLDocument::ErrorName() const -{ - TIXMLASSERT(_errorID >= 0 && _errorID < XML_ERROR_COUNT ); - return _errorNames[_errorID]; -} - -void XMLDocument::PrintError() const -{ - if ( _errorID ) { - static const int LEN = 20; - char buf1[LEN] = { 0 }; - char buf2[LEN] = { 0 }; - - if ( _errorStr1 ) { - TIXML_SNPRINTF( buf1, LEN, "%s", _errorStr1 ); - } - if ( _errorStr2 ) { - TIXML_SNPRINTF( buf2, LEN, "%s", _errorStr2 ); - } - - printf( "XMLDocument error id=%d '%s' str1=%s str2=%s\n", - _errorID, ErrorName(), buf1, buf2 ); - } -} - - -XMLPrinter::XMLPrinter( FILE* file, bool compact, int depth ) : - _elementJustOpened( false ), - _firstElement( true ), - _fp( file ), - _depth( depth ), - _textDepth( -1 ), - _processEntities( true ), - _compactMode( compact ) -{ - for( int i=0; i'] = true; // not required, but consistency is nice - _buffer.Push( 0 ); -} - - -void XMLPrinter::Print( const char* format, ... ) -{ - va_list va; - va_start( va, format ); - - if ( _fp ) { - vfprintf( _fp, format, va ); - } - else { -#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) - #if defined(WINCE) - int len = 512; - do { - len = len*2; - char* str = new char[len](); - len = _vsnprintf(str, len, format, va); - delete[] str; - }while (len < 0); - #else - int len = _vscprintf( format, va ); - #endif -#else - int len = vsnprintf( 0, 0, format, va ); -#endif - // Close out and re-start the va-args - va_end( va ); - va_start( va, format ); - char* p = _buffer.PushArr( len ) - 1; // back up over the null terminator. -#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) - #if defined(WINCE) - _vsnprintf( p, len+1, format, va ); - #else - vsnprintf_s( p, len+1, _TRUNCATE, format, va ); - #endif -#else - vsnprintf( p, len+1, format, va ); -#endif - } - va_end( va ); -} - - -void XMLPrinter::PrintSpace( int depth ) -{ - for( int i=0; i 0 && *q < ENTITY_RANGE ) { - // Check for entities. If one is found, flush - // the stream up until the entity, write the - // entity, and keep looking. - if ( flag[(unsigned)(*q)] ) { - while ( p < q ) { - Print( "%c", *p ); - ++p; - } - for( int i=0; i 0) ) { - Print( "%s", p ); - } -} - - -void XMLPrinter::PushHeader( bool writeBOM, bool writeDec ) -{ - if ( writeBOM ) { - static const unsigned char bom[] = { TIXML_UTF_LEAD_0, TIXML_UTF_LEAD_1, TIXML_UTF_LEAD_2, 0 }; - Print( "%s", bom ); - } - if ( writeDec ) { - PushDeclaration( "xml version=\"1.0\"" ); - } -} - - -void XMLPrinter::OpenElement( const char* name, bool compactMode ) -{ - if ( _elementJustOpened ) { - SealElement(); - } - _stack.Push( name ); - - if ( _textDepth < 0 && !_firstElement && !compactMode ) { - Print( "\n" ); - } - if ( !compactMode ) { - PrintSpace( _depth ); - } - - Print( "<%s", name ); - _elementJustOpened = true; - _firstElement = false; - ++_depth; -} - - -void XMLPrinter::PushAttribute( const char* name, const char* value ) -{ - TIXMLASSERT( _elementJustOpened ); - Print( " %s=\"", name ); - PrintString( value, false ); - Print( "\"" ); -} - - -void XMLPrinter::PushAttribute( const char* name, int v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - PushAttribute( name, buf ); -} - - -void XMLPrinter::PushAttribute( const char* name, unsigned v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - PushAttribute( name, buf ); -} - - -void XMLPrinter::PushAttribute( const char* name, bool v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - PushAttribute( name, buf ); -} - - -void XMLPrinter::PushAttribute( const char* name, double v ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( v, buf, BUF_SIZE ); - PushAttribute( name, buf ); -} - - -void XMLPrinter::CloseElement( bool compactMode ) -{ - --_depth; - const char* name = _stack.Pop(); - - if ( _elementJustOpened ) { - Print( "/>" ); - } - else { - if ( _textDepth < 0 && !compactMode) { - Print( "\n" ); - PrintSpace( _depth ); - } - Print( "", name ); - } - - if ( _textDepth == _depth ) { - _textDepth = -1; - } - if ( _depth == 0 && !compactMode) { - Print( "\n" ); - } - _elementJustOpened = false; -} - - -void XMLPrinter::SealElement() -{ - _elementJustOpened = false; - Print( ">" ); -} - - -void XMLPrinter::PushText( const char* text, bool cdata ) -{ - _textDepth = _depth-1; - - if ( _elementJustOpened ) { - SealElement(); - } - if ( cdata ) { - Print( "" ); - } - else { - PrintString( text, true ); - } -} - -void XMLPrinter::PushText( int value ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( value, buf, BUF_SIZE ); - PushText( buf, false ); -} - - -void XMLPrinter::PushText( unsigned value ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( value, buf, BUF_SIZE ); - PushText( buf, false ); -} - - -void XMLPrinter::PushText( bool value ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( value, buf, BUF_SIZE ); - PushText( buf, false ); -} - - -void XMLPrinter::PushText( float value ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( value, buf, BUF_SIZE ); - PushText( buf, false ); -} - - -void XMLPrinter::PushText( double value ) -{ - char buf[BUF_SIZE]; - XMLUtil::ToStr( value, buf, BUF_SIZE ); - PushText( buf, false ); -} - - -void XMLPrinter::PushComment( const char* comment ) -{ - if ( _elementJustOpened ) { - SealElement(); - } - if ( _textDepth < 0 && !_firstElement && !_compactMode) { - Print( "\n" ); - PrintSpace( _depth ); - } - _firstElement = false; - Print( "", comment ); -} - - -void XMLPrinter::PushDeclaration( const char* value ) -{ - if ( _elementJustOpened ) { - SealElement(); - } - if ( _textDepth < 0 && !_firstElement && !_compactMode) { - Print( "\n" ); - PrintSpace( _depth ); - } - _firstElement = false; - Print( "", value ); -} - - -void XMLPrinter::PushUnknown( const char* value ) -{ - if ( _elementJustOpened ) { - SealElement(); - } - if ( _textDepth < 0 && !_firstElement && !_compactMode) { - Print( "\n" ); - PrintSpace( _depth ); - } - _firstElement = false; - Print( "", value ); -} - - -bool XMLPrinter::VisitEnter( const XMLDocument& doc ) -{ - _processEntities = doc.ProcessEntities(); - if ( doc.HasBOM() ) { - PushHeader( true, false ); - } - return true; -} - - -bool XMLPrinter::VisitEnter( const XMLElement& element, const XMLAttribute* attribute ) -{ - const XMLElement* parentElem = element.Parent()->ToElement(); - bool compactMode = parentElem ? CompactMode(*parentElem) : _compactMode; - OpenElement( element.Name(), compactMode ); - while ( attribute ) { - PushAttribute( attribute->Name(), attribute->Value() ); - attribute = attribute->Next(); - } - return true; -} - - -bool XMLPrinter::VisitExit( const XMLElement& element ) -{ - CloseElement( CompactMode(element) ); - return true; -} - - -bool XMLPrinter::Visit( const XMLText& text ) -{ - PushText( text.Value(), text.CData() ); - return true; -} - - -bool XMLPrinter::Visit( const XMLComment& comment ) -{ - PushComment( comment.Value() ); - return true; -} - -bool XMLPrinter::Visit( const XMLDeclaration& declaration ) -{ - PushDeclaration( declaration.Value() ); - return true; -} - - -bool XMLPrinter::Visit( const XMLUnknown& unknown ) -{ - PushUnknown( unknown.Value() ); - return true; -} - -} // namespace tinyxml2 - diff --git a/libs/IO/TinyXML2.h b/libs/IO/TinyXML2.h deleted file mode 100644 index 3e95a402f..000000000 --- a/libs/IO/TinyXML2.h +++ /dev/null @@ -1,2075 +0,0 @@ -/* -Original code by Lee Thomason (www.grinninglizard.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#ifndef TINYXML2_INCLUDED -#define TINYXML2_INCLUDED - -#if defined(ANDROID_NDK) || defined(__BORLANDC__) -# include -# include -# include -# include -# include -# include -#else -# include -# include -# include -# include -# include -# include -#endif - -/* - TODO: intern strings instead of allocation. -*/ -/* - gcc: - g++ -Wall -DDEBUG tinyxml2.cpp xmltest.cpp -o gccxmltest.exe - - Formatting, Artistic Style: - AStyle.exe --style=1tbs --indent-switches --break-closing-brackets --indent-preprocessor tinyxml2.cpp tinyxml2.h -*/ - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4251) -#endif - -#define TINYXML2_LIB IO_API - - -#ifndef _RELEASE -# if defined(_MSC_VER) -# define TIXMLASSERT( x ) ASSERT(x) //if ( !(x)) { __debugbreak(); } //if ( !(x)) WinDebugBreak() -# elif defined (ANDROID_NDK) -# include -# define TIXMLASSERT( x ) if ( !(x)) { __android_log_assert( "assert", "grinliz", "ASSERT in '%s' at %d.", __FILE__, __LINE__ ); } -# else -# include -# define TIXMLASSERT ASSERT -# endif -# else -# define TIXMLASSERT( x ) {} -#endif - - -#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) -// Microsoft visual studio, version 2005 and higher. -/*int _snprintf_s( - char *buffer, - size_t sizeOfBuffer, - size_t count, - const char *format [, - argument] ... -);*/ -inline int TIXML_SNPRINTF( char* buffer, size_t size, const char* format, ... ) -{ - va_list va; - va_start( va, format ); - int result = vsnprintf_s( buffer, size, _TRUNCATE, format, va ); - va_end( va ); - return result; -} -#define TIXML_SSCANF sscanf_s -#elif defined WINCE -#define TIXML_SNPRINTF _snprintf -#define TIXML_SSCANF sscanf -#else -// GCC version 3 and higher -//#warning( "Using sn* functions." ) -#define TIXML_SNPRINTF snprintf -#define TIXML_SSCANF sscanf -#endif - -/* Versioning, past 1.0.14: - http://semver.org/ -*/ -static const int TIXML2_MAJOR_VERSION = 2; -static const int TIXML2_MINOR_VERSION = 2; -static const int TIXML2_PATCH_VERSION = 0; - -namespace tinyxml2 -{ -class XMLDocument; -class XMLElement; -class XMLAttribute; -class XMLComment; -class XMLText; -class XMLDeclaration; -class XMLUnknown; -class XMLPrinter; - -/* - A class that wraps strings. Normally stores the start and end - pointers into the XML file itself, and will apply normalization - and entity translation if actually read. Can also store (and memory - manage) a traditional char[] -*/ -class StrPair -{ -public: - enum { - NEEDS_ENTITY_PROCESSING = 0x01, - NEEDS_NEWLINE_NORMALIZATION = 0x02, - COLLAPSE_WHITESPACE = 0x04, - - TEXT_ELEMENT = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, - TEXT_ELEMENT_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, - ATTRIBUTE_NAME = 0, - ATTRIBUTE_VALUE = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, - ATTRIBUTE_VALUE_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, - COMMENT = NEEDS_NEWLINE_NORMALIZATION - }; - - StrPair() : _flags( 0 ), _start( 0 ), _end( 0 ) {} - ~StrPair(); - - void Set( char* start, char* end, int flags ) { - Reset(); - _start = start; - _end = end; - _flags = flags | NEEDS_FLUSH; - } - - const char* GetStr(); - - bool Empty() const { - return _start == _end; - } - - void SetInternedStr( const char* str ) { - Reset(); - _start = const_cast(str); - } - - void SetStr( const char* str, int flags=0 ); - - char* ParseText( char* in, const char* endTag, int strFlags ); - char* ParseName( char* in ); - -private: - void Reset(); - void CollapseWhitespace(); - - enum { - NEEDS_FLUSH = 0x100, - NEEDS_DELETE = 0x200 - }; - - // After parsing, if *_end != 0, it can be set to zero. - int _flags; - char* _start; - char* _end; -}; - - -/* - A dynamic array of Plain Old Data. Doesn't support constructors, etc. - Has a small initial memory pool, so that low or no usage will not - cause a call to new/delete -*/ -template -class DynArray -{ -public: - DynArray< T, INIT >() { - _mem = _pool; - _allocated = INIT; - _size = 0; - } - - ~DynArray() { - if ( _mem != _pool ) { - delete [] _mem; - } - } - - void Clear() { - _size = 0; - } - - void Push( T t ) { - EnsureCapacity( _size+1 ); - _mem[_size++] = t; - } - - T* PushArr( int count ) { - EnsureCapacity( _size+count ); - T* ret = &_mem[_size]; - _size += count; - return ret; - } - - T Pop() { - return _mem[--_size]; - } - - void PopArr( int count ) { - TIXMLASSERT( _size >= count ); - _size -= count; - } - - bool Empty() const { - return _size == 0; - } - - T& operator[](int i) { - TIXMLASSERT( i>= 0 && i < _size ); - return _mem[i]; - } - - const T& operator[](int i) const { - TIXMLASSERT( i>= 0 && i < _size ); - return _mem[i]; - } - - const T& PeekTop() const { - TIXMLASSERT( _size > 0 ); - return _mem[ _size - 1]; - } - - int Size() const { - return _size; - } - - int Capacity() const { - return _allocated; - } - - const T* Mem() const { - return _mem; - } - - T* Mem() { - return _mem; - } - -private: - void EnsureCapacity( int cap ) { - if ( cap > _allocated ) { - int newAllocated = cap * 2; - T* newMem = new T[newAllocated]; - memcpy( newMem, _mem, sizeof(T)*_size ); // warning: not using constructors, only works for PODs - if ( _mem != _pool ) { - delete [] _mem; - } - _mem = newMem; - _allocated = newAllocated; - } - } - - T* _mem; - T _pool[INIT]; - int _allocated; // objects allocated - int _size; // number objects in use -}; - - -/* - Parent virtual class of a pool for fast allocation - and deallocation of objects. -*/ -class MemPool -{ -public: - MemPool() {} - virtual ~MemPool() {} - - virtual int ItemSize() const = 0; - virtual void* Alloc() = 0; - virtual void Free( void* ) = 0; - virtual void SetTracked() = 0; - virtual void Clear() = 0; -}; - - -/* - Template child class to create pools of the correct type. -*/ -template< int SIZE > -class MemPoolT : public MemPool -{ -public: - MemPoolT() : _root(0), _currentAllocs(0), _nAllocs(0), _maxAllocs(0), _nUntracked(0) {} - ~MemPoolT() { - Clear(); - } - - void Clear() { - // Delete the blocks. - while( !_blockPtrs.Empty()) { - Block* b = _blockPtrs.Pop(); - delete b; - } - _root = 0; - _currentAllocs = 0; - _nAllocs = 0; - _maxAllocs = 0; - _nUntracked = 0; - } - - virtual int ItemSize() const { - return SIZE; - } - int CurrentAllocs() const { - return _currentAllocs; - } - - virtual void* Alloc() { - if ( !_root ) { - // Need a new block. - Block* block = new Block(); - _blockPtrs.Push( block ); - - for( int i=0; ichunk[i].next = &block->chunk[i+1]; - } - block->chunk[COUNT-1].next = 0; - _root = block->chunk; - } - void* result = _root; - _root = _root->next; - - ++_currentAllocs; - if ( _currentAllocs > _maxAllocs ) { - _maxAllocs = _currentAllocs; - } - _nAllocs++; - _nUntracked++; - return result; - } - - virtual void Free( void* mem ) { - if ( !mem ) { - return; - } - --_currentAllocs; - Chunk* chunk = static_cast( mem ); -#ifndef _RELEASE - memset( chunk, 0xfe, sizeof(Chunk) ); -#endif - chunk->next = _root; - _root = chunk; - } - void Trace( const char* name ) { - printf( "Mempool %s watermark=%d [%dk] current=%d size=%d nAlloc=%d blocks=%d\n", - name, _maxAllocs, _maxAllocs*SIZE/1024, _currentAllocs, SIZE, _nAllocs, _blockPtrs.Size() ); - } - - void SetTracked() { - _nUntracked--; - } - - int Untracked() const { - return _nUntracked; - } - - // This number is perf sensitive. 4k seems like a good tradeoff on my machine. - // The test file is large, 170k. - // Release: VS2010 gcc(no opt) - // 1k: 4000 - // 2k: 4000 - // 4k: 3900 21000 - // 16k: 5200 - // 32k: 4300 - // 64k: 4000 21000 - enum { COUNT = (4*1024)/SIZE }; // Some compilers do not accept to use COUNT in private part if COUNT is private - -private: - union Chunk { - Chunk* next; - char mem[SIZE]; - }; - struct Block { - Chunk chunk[COUNT]; - }; - DynArray< Block*, 10 > _blockPtrs; - Chunk* _root; - - int _currentAllocs; - int _nAllocs; - int _maxAllocs; - int _nUntracked; -}; - - - -/** - Implements the interface to the "Visitor pattern" (see the Accept() method.) - If you call the Accept() method, it requires being passed a XMLVisitor - class to handle callbacks. For nodes that contain other nodes (Document, Element) - you will get called with a VisitEnter/VisitExit pair. Nodes that are always leafs - are simply called with Visit(). - - If you return 'true' from a Visit method, recursive parsing will continue. If you return - false, no children of this node or its siblings will be visited. - - All flavors of Visit methods have a default implementation that returns 'true' (continue - visiting). You need to only override methods that are interesting to you. - - Generally Accept() is called on the XMLDocument, although all nodes support visiting. - - You should never change the document from a callback. - - @sa XMLNode::Accept() -*/ -class TINYXML2_LIB XMLVisitor -{ -public: - virtual ~XMLVisitor() {} - - /// Visit a document. - virtual bool VisitEnter( const XMLDocument& /*doc*/ ) { - return true; - } - /// Visit a document. - virtual bool VisitExit( const XMLDocument& /*doc*/ ) { - return true; - } - - /// Visit an element. - virtual bool VisitEnter( const XMLElement& /*element*/, const XMLAttribute* /*firstAttribute*/ ) { - return true; - } - /// Visit an element. - virtual bool VisitExit( const XMLElement& /*element*/ ) { - return true; - } - - /// Visit a declaration. - virtual bool Visit( const XMLDeclaration& /*declaration*/ ) { - return true; - } - /// Visit a text node. - virtual bool Visit( const XMLText& /*text*/ ) { - return true; - } - /// Visit a comment node. - virtual bool Visit( const XMLComment& /*comment*/ ) { - return true; - } - /// Visit an unknown node. - virtual bool Visit( const XMLUnknown& /*unknown*/ ) { - return true; - } -}; - -// WARNING: must match XMLErrorNames[] -enum XMLError { - XML_SUCCESS = 0, - XML_NO_ERROR = 0, - XML_NO_ATTRIBUTE, - XML_WRONG_ATTRIBUTE_TYPE, - XML_ERROR_FILE_NOT_FOUND, - XML_ERROR_FILE_COULD_NOT_BE_OPENED, - XML_ERROR_FILE_READ_ERROR, - XML_ERROR_ELEMENT_MISMATCH, - XML_ERROR_PARSING_ELEMENT, - XML_ERROR_PARSING_ATTRIBUTE, - XML_ERROR_IDENTIFYING_TAG, - XML_ERROR_PARSING_TEXT, - XML_ERROR_PARSING_CDATA, - XML_ERROR_PARSING_COMMENT, - XML_ERROR_PARSING_DECLARATION, - XML_ERROR_PARSING_UNKNOWN, - XML_ERROR_EMPTY_DOCUMENT, - XML_ERROR_MISMATCHED_ELEMENT, - XML_ERROR_PARSING, - XML_CAN_NOT_CONVERT_TEXT, - XML_NO_TEXT_NODE, - - XML_ERROR_COUNT -}; - - -/* - Utility functionality. -*/ -class XMLUtil -{ -public: - static const char* SkipWhiteSpace( const char* p ) { - while( IsWhiteSpace(*p) ) { - ++p; - } - return p; - } - static char* SkipWhiteSpace( char* p ) { - return const_cast( SkipWhiteSpace( const_cast(p) ) ); - } - - // Anything in the high order range of UTF-8 is assumed to not be whitespace. This isn't - // correct, but simple, and usually works. - static bool IsWhiteSpace( char p ) { - return !IsUTF8Continuation(p) && isspace( static_cast(p) ); - } - - inline static bool IsNameStartChar( unsigned char ch ) { - return ( ( ch < 128 ) ? isalpha( ch ) : 1 ) - || ch == ':' - || ch == '_'; - } - - inline static bool IsNameChar( unsigned char ch ) { - return IsNameStartChar( ch ) - || isdigit( ch ) - || ch == '.' - || ch == '-'; - } - - inline static bool StringEqual( const char* p, const char* q, int nChar=INT_MAX ) { - int n = 0; - if ( p == q ) { - return true; - } - while( *p && *q && *p == *q && n(const_cast(this)->FirstChildElement( value )); - } - - /// Get the last child node, or null if none exists. - const XMLNode* LastChild() const { - return _lastChild; - } - - XMLNode* LastChild() { - return const_cast(const_cast(this)->LastChild() ); - } - - /** Get the last child element or optionally the last child - element with the specified name. - */ - const XMLElement* LastChildElement( const char* value=0 ) const; - - XMLElement* LastChildElement( const char* value=0 ) { - return const_cast(const_cast(this)->LastChildElement(value) ); - } - - /// Get the previous (left) sibling node of this node. - const XMLNode* PreviousSibling() const { - return _prev; - } - - XMLNode* PreviousSibling() { - return _prev; - } - - /// Get the previous (left) sibling element of this node, with an optionally supplied name. - const XMLElement* PreviousSiblingElement( const char* value=0 ) const ; - - XMLElement* PreviousSiblingElement( const char* value=0 ) { - return const_cast(const_cast(this)->PreviousSiblingElement( value ) ); - } - - /// Get the next (right) sibling node of this node. - const XMLNode* NextSibling() const { - return _next; - } - - XMLNode* NextSibling() { - return _next; - } - - /// Get the next (right) sibling element of this node, with an optionally supplied name. - const XMLElement* NextSiblingElement( const char* value=0 ) const; - - XMLElement* NextSiblingElement( const char* value=0 ) { - return const_cast(const_cast(this)->NextSiblingElement( value ) ); - } - - /** - Add a child node as the last (right) child. - If the child node is already part of the document, - it is moved from its old location to the new location. - Returns the addThis argument or 0 if the node does not - belong to the same document. - */ - XMLNode* InsertEndChild( XMLNode* addThis ); - - XMLNode* LinkEndChild( XMLNode* addThis ) { - return InsertEndChild( addThis ); - } - /** - Add a child node as the first (left) child. - If the child node is already part of the document, - it is moved from its old location to the new location. - Returns the addThis argument or 0 if the node does not - belong to the same document. - */ - XMLNode* InsertFirstChild( XMLNode* addThis ); - /** - Add a node after the specified child node. - If the child node is already part of the document, - it is moved from its old location to the new location. - Returns the addThis argument or 0 if the afterThis node - is not a child of this node, or if the node does not - belong to the same document. - */ - XMLNode* InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ); - - /** - Delete all the children of this node. - */ - void DeleteChildren(); - - /** - Delete a child of this node. - */ - void DeleteChild( XMLNode* node ); - - /** - Make a copy of this node, but not its children. - You may pass in a Document pointer that will be - the owner of the new Node. If the 'document' is - null, then the node returned will be allocated - from the current Document. (this->GetDocument()) - - Note: if called on a XMLDocument, this will return null. - */ - virtual XMLNode* ShallowClone( XMLDocument* document ) const = 0; - - /** - Test if 2 nodes are the same, but don't test children. - The 2 nodes do not need to be in the same Document. - - Note: if called on a XMLDocument, this will return false. - */ - virtual bool ShallowEqual( const XMLNode* compare ) const = 0; - - /** Accept a hierarchical visit of the nodes in the TinyXML-2 DOM. Every node in the - XML tree will be conditionally visited and the host will be called back - via the XMLVisitor interface. - - This is essentially a SAX interface for TinyXML-2. (Note however it doesn't re-parse - the XML for the callbacks, so the performance of TinyXML-2 is unchanged by using this - interface versus any other.) - - The interface has been based on ideas from: - - - http://www.saxproject.org/ - - http://c2.com/cgi/wiki?HierarchicalVisitorPattern - - Which are both good references for "visiting". - - An example of using Accept(): - @verbatim - XMLPrinter printer; - tinyxmlDoc.Accept( &printer ); - const char* xmlcstr = printer.CStr(); - @endverbatim - */ - virtual bool Accept( XMLVisitor* visitor ) const = 0; - - // internal - virtual char* ParseDeep( char*, StrPair* ); - -protected: - XMLNode( XMLDocument* ); - virtual ~XMLNode(); - XMLNode( const XMLNode& ); // not supported - XMLNode& operator=( const XMLNode& ); // not supported - - XMLDocument* _document; - XMLNode* _parent; - mutable StrPair _value; - - XMLNode* _firstChild; - XMLNode* _lastChild; - - XMLNode* _prev; - XMLNode* _next; - -private: - MemPool* _memPool; - void Unlink( XMLNode* child ); - static void DeleteNode( XMLNode* node ); -}; - - -/** XML text. - - Note that a text node can have child element nodes, for example: - @verbatim - This is bold - @endverbatim - - A text node can have 2 ways to output the next. "normal" output - and CDATA. It will default to the mode it was parsed from the XML file and - you generally want to leave it alone, but you can change the output mode with - SetCData() and query it with CData(). -*/ -class TINYXML2_LIB XMLText : public XMLNode -{ - friend class XMLBase; - friend class XMLDocument; -public: - virtual bool Accept( XMLVisitor* visitor ) const; - - virtual XMLText* ToText() { - return this; - } - virtual const XMLText* ToText() const { - return this; - } - - /// Declare whether this should be CDATA or standard text. - void SetCData( bool isCData ) { - _isCData = isCData; - } - /// Returns true if this is a CDATA text element. - bool CData() const { - return _isCData; - } - - char* ParseDeep( char*, StrPair* endTag ); - virtual XMLNode* ShallowClone( XMLDocument* document ) const; - virtual bool ShallowEqual( const XMLNode* compare ) const; - -protected: - XMLText( XMLDocument* doc ) : XMLNode( doc ), _isCData( false ) {} - virtual ~XMLText() {} - XMLText( const XMLText& ); // not supported - XMLText& operator=( const XMLText& ); // not supported - -private: - bool _isCData; -}; - - -/** An XML Comment. */ -class TINYXML2_LIB XMLComment : public XMLNode -{ - friend class XMLDocument; -public: - virtual XMLComment* ToComment() { - return this; - } - virtual const XMLComment* ToComment() const { - return this; - } - - virtual bool Accept( XMLVisitor* visitor ) const; - - char* ParseDeep( char*, StrPair* endTag ); - virtual XMLNode* ShallowClone( XMLDocument* document ) const; - virtual bool ShallowEqual( const XMLNode* compare ) const; - -protected: - XMLComment( XMLDocument* doc ); - virtual ~XMLComment(); - XMLComment( const XMLComment& ); // not supported - XMLComment& operator=( const XMLComment& ); // not supported - -private: -}; - - -/** In correct XML the declaration is the first entry in the file. - @verbatim - - @endverbatim - - TinyXML-2 will happily read or write files without a declaration, - however. - - The text of the declaration isn't interpreted. It is parsed - and written as a string. -*/ -class TINYXML2_LIB XMLDeclaration : public XMLNode -{ - friend class XMLDocument; -public: - virtual XMLDeclaration* ToDeclaration() { - return this; - } - virtual const XMLDeclaration* ToDeclaration() const { - return this; - } - - virtual bool Accept( XMLVisitor* visitor ) const; - - char* ParseDeep( char*, StrPair* endTag ); - virtual XMLNode* ShallowClone( XMLDocument* document ) const; - virtual bool ShallowEqual( const XMLNode* compare ) const; - -protected: - XMLDeclaration( XMLDocument* doc ); - virtual ~XMLDeclaration(); - XMLDeclaration( const XMLDeclaration& ); // not supported - XMLDeclaration& operator=( const XMLDeclaration& ); // not supported -}; - - -/** Any tag that TinyXML-2 doesn't recognize is saved as an - unknown. It is a tag of text, but should not be modified. - It will be written back to the XML, unchanged, when the file - is saved. - - DTD tags get thrown into XMLUnknowns. -*/ -class TINYXML2_LIB XMLUnknown : public XMLNode -{ - friend class XMLDocument; -public: - virtual XMLUnknown* ToUnknown() { - return this; - } - virtual const XMLUnknown* ToUnknown() const { - return this; - } - - virtual bool Accept( XMLVisitor* visitor ) const; - - char* ParseDeep( char*, StrPair* endTag ); - virtual XMLNode* ShallowClone( XMLDocument* document ) const; - virtual bool ShallowEqual( const XMLNode* compare ) const; - -protected: - XMLUnknown( XMLDocument* doc ); - virtual ~XMLUnknown(); - XMLUnknown( const XMLUnknown& ); // not supported - XMLUnknown& operator=( const XMLUnknown& ); // not supported -}; - - - -/** An attribute is a name-value pair. Elements have an arbitrary - number of attributes, each with a unique name. - - @note The attributes are not XMLNodes. You may only query the - Next() attribute in a list. -*/ -class TINYXML2_LIB XMLAttribute -{ - friend class XMLElement; -public: - /// The name of the attribute. - const char* Name() const; - - /// The value of the attribute. - const char* Value() const; - - /// The next attribute in the list. - const XMLAttribute* Next() const { - return _next; - } - - /** IntValue interprets the attribute as an integer, and returns the value. - If the value isn't an integer, 0 will be returned. There is no error checking; - use QueryIntValue() if you need error checking. - */ - int IntValue() const { - int i=0; - QueryIntValue( &i ); - return i; - } - /// Query as an unsigned integer. See IntValue() - unsigned UnsignedValue() const { - unsigned i=0; - QueryUnsignedValue( &i ); - return i; - } - /// Query as a boolean. See IntValue() - bool BoolValue() const { - bool b=false; - QueryBoolValue( &b ); - return b; - } - /// Query as a double. See IntValue() - double DoubleValue() const { - double d=0; - QueryDoubleValue( &d ); - return d; - } - /// Query as a float. See IntValue() - float FloatValue() const { - float f=0; - QueryFloatValue( &f ); - return f; - } - - /** QueryIntValue interprets the attribute as an integer, and returns the value - in the provided parameter. The function will return XML_NO_ERROR on success, - and XML_WRONG_ATTRIBUTE_TYPE if the conversion is not successful. - */ - XMLError QueryIntValue( int* value ) const; - /// See QueryIntValue - XMLError QueryUnsignedValue( unsigned int* value ) const; - /// See QueryIntValue - XMLError QueryBoolValue( bool* value ) const; - /// See QueryIntValue - XMLError QueryDoubleValue( double* value ) const; - /// See QueryIntValue - XMLError QueryFloatValue( float* value ) const; - - /// Set the attribute to a string value. - void SetAttribute( const char* value ); - /// Set the attribute to value. - void SetAttribute( int value ); - /// Set the attribute to value. - void SetAttribute( unsigned value ); - /// Set the attribute to value. - void SetAttribute( bool value ); - /// Set the attribute to value. - void SetAttribute( double value ); - /// Set the attribute to value. - void SetAttribute( float value ); - -private: - enum { BUF_SIZE = 200 }; - - XMLAttribute() : _next( 0 ), _memPool( 0 ) {} - virtual ~XMLAttribute() {} - - XMLAttribute( const XMLAttribute& ); // not supported - void operator=( const XMLAttribute& ); // not supported - void SetName( const char* name ); - - char* ParseDeep( char* p, bool processEntities ); - - mutable StrPair _name; - mutable StrPair _value; - XMLAttribute* _next; - MemPool* _memPool; -}; - - -/** The element is a container class. It has a value, the element name, - and can contain other elements, text, comments, and unknowns. - Elements also contain an arbitrary number of attributes. -*/ -class TINYXML2_LIB XMLElement : public XMLNode -{ - friend class XMLBase; - friend class XMLDocument; -public: - /// Get the name of an element (which is the Value() of the node.) - const char* Name() const { - return Value(); - } - /// Set the name of the element. - void SetName( const char* str, bool staticMem=false ) { - SetValue( str, staticMem ); - } - - virtual XMLElement* ToElement() { - return this; - } - virtual const XMLElement* ToElement() const { - return this; - } - virtual bool Accept( XMLVisitor* visitor ) const; - - /** Given an attribute name, Attribute() returns the value - for the attribute of that name, or null if none - exists. For example: - - @verbatim - const char* value = ele->Attribute( "foo" ); - @endverbatim - - The 'value' parameter is normally null. However, if specified, - the attribute will only be returned if the 'name' and 'value' - match. This allow you to write code: - - @verbatim - if ( ele->Attribute( "foo", "bar" ) ) callFooIsBar(); - @endverbatim - - rather than: - @verbatim - if ( ele->Attribute( "foo" ) ) { - if ( strcmp( ele->Attribute( "foo" ), "bar" ) == 0 ) callFooIsBar(); - } - @endverbatim - */ - const char* Attribute( const char* name, const char* value=0 ) const; - - /** Given an attribute name, IntAttribute() returns the value - of the attribute interpreted as an integer. 0 will be - returned if there is an error. For a method with error - checking, see QueryIntAttribute() - */ - int IntAttribute( const char* name ) const { - int i=0; - QueryIntAttribute( name, &i ); - return i; - } - /// See IntAttribute() - unsigned UnsignedAttribute( const char* name ) const { - unsigned i=0; - QueryUnsignedAttribute( name, &i ); - return i; - } - /// See IntAttribute() - bool BoolAttribute( const char* name ) const { - bool b=false; - QueryBoolAttribute( name, &b ); - return b; - } - /// See IntAttribute() - double DoubleAttribute( const char* name ) const { - double d=0; - QueryDoubleAttribute( name, &d ); - return d; - } - /// See IntAttribute() - float FloatAttribute( const char* name ) const { - float f=0; - QueryFloatAttribute( name, &f ); - return f; - } - - /** Given an attribute name, QueryIntAttribute() returns - XML_NO_ERROR, XML_WRONG_ATTRIBUTE_TYPE if the conversion - can't be performed, or XML_NO_ATTRIBUTE if the attribute - doesn't exist. If successful, the result of the conversion - will be written to 'value'. If not successful, nothing will - be written to 'value'. This allows you to provide default - value: - - @verbatim - int value = 10; - QueryIntAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 - @endverbatim - */ - XMLError QueryIntAttribute( const char* name, int* value ) const { - const XMLAttribute* a = FindAttribute( name ); - if ( !a ) { - return XML_NO_ATTRIBUTE; - } - return a->QueryIntValue( value ); - } - /// See QueryIntAttribute() - XMLError QueryUnsignedAttribute( const char* name, unsigned int* value ) const { - const XMLAttribute* a = FindAttribute( name ); - if ( !a ) { - return XML_NO_ATTRIBUTE; - } - return a->QueryUnsignedValue( value ); - } - /// See QueryIntAttribute() - XMLError QueryBoolAttribute( const char* name, bool* value ) const { - const XMLAttribute* a = FindAttribute( name ); - if ( !a ) { - return XML_NO_ATTRIBUTE; - } - return a->QueryBoolValue( value ); - } - /// See QueryIntAttribute() - XMLError QueryDoubleAttribute( const char* name, double* value ) const { - const XMLAttribute* a = FindAttribute( name ); - if ( !a ) { - return XML_NO_ATTRIBUTE; - } - return a->QueryDoubleValue( value ); - } - /// See QueryIntAttribute() - XMLError QueryFloatAttribute( const char* name, float* value ) const { - const XMLAttribute* a = FindAttribute( name ); - if ( !a ) { - return XML_NO_ATTRIBUTE; - } - return a->QueryFloatValue( value ); - } - - - /** Given an attribute name, QueryAttribute() returns - XML_NO_ERROR, XML_WRONG_ATTRIBUTE_TYPE if the conversion - can't be performed, or XML_NO_ATTRIBUTE if the attribute - doesn't exist. It is overloaded for the primitive types, - and is a generally more convenient replacement of - QueryIntAttribute() and related functions. - - If successful, the result of the conversion - will be written to 'value'. If not successful, nothing will - be written to 'value'. This allows you to provide default - value: - - @verbatim - int value = 10; - QueryAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 - @endverbatim - */ - int QueryAttribute( const char* name, int* value ) const { - return QueryIntAttribute( name, value ); - } - - int QueryAttribute( const char* name, unsigned int* value ) const { - return QueryUnsignedAttribute( name, value ); - } - - int QueryAttribute( const char* name, bool* value ) const { - return QueryBoolAttribute( name, value ); - } - - int QueryAttribute( const char* name, double* value ) const { - return QueryDoubleAttribute( name, value ); - } - - int QueryAttribute( const char* name, float* value ) const { - return QueryFloatAttribute( name, value ); - } - - /// Sets the named attribute to value. - void SetAttribute( const char* name, const char* value ) { - XMLAttribute* a = FindOrCreateAttribute( name ); - a->SetAttribute( value ); - } - /// Sets the named attribute to value. - void SetAttribute( const char* name, int value ) { - XMLAttribute* a = FindOrCreateAttribute( name ); - a->SetAttribute( value ); - } - /// Sets the named attribute to value. - void SetAttribute( const char* name, unsigned value ) { - XMLAttribute* a = FindOrCreateAttribute( name ); - a->SetAttribute( value ); - } - /// Sets the named attribute to value. - void SetAttribute( const char* name, bool value ) { - XMLAttribute* a = FindOrCreateAttribute( name ); - a->SetAttribute( value ); - } - /// Sets the named attribute to value. - void SetAttribute( const char* name, double value ) { - XMLAttribute* a = FindOrCreateAttribute( name ); - a->SetAttribute( value ); - } - /// Sets the named attribute to value. - void SetAttribute( const char* name, float value ) { - XMLAttribute* a = FindOrCreateAttribute( name ); - a->SetAttribute( value ); - } - - /** - Delete an attribute. - */ - void DeleteAttribute( const char* name ); - - /// Return the first attribute in the list. - const XMLAttribute* FirstAttribute() const { - return _rootAttribute; - } - /// Query a specific attribute in the list. - const XMLAttribute* FindAttribute( const char* name ) const; - - /** Convenience function for easy access to the text inside an element. Although easy - and concise, GetText() is limited compared to getting the XMLText child - and accessing it directly. - - If the first child of 'this' is a XMLText, the GetText() - returns the character string of the Text node, else null is returned. - - This is a convenient method for getting the text of simple contained text: - @verbatim - This is text - const char* str = fooElement->GetText(); - @endverbatim - - 'str' will be a pointer to "This is text". - - Note that this function can be misleading. If the element foo was created from - this XML: - @verbatim - This is text - @endverbatim - - then the value of str would be null. The first child node isn't a text node, it is - another element. From this XML: - @verbatim - This is text - @endverbatim - GetText() will return "This is ". - */ - const char* GetText() const; - - /** Convenience function for easy access to the text inside an element. Although easy - and concise, SetText() is limited compared to creating an XMLText child - and mutating it directly. - - If the first child of 'this' is a XMLText, SetText() sets its value to - the given string, otherwise it will create a first child that is an XMLText. - - This is a convenient method for setting the text of simple contained text: - @verbatim - This is text - fooElement->SetText( "Hullaballoo!" ); - Hullaballoo! - @endverbatim - - Note that this function can be misleading. If the element foo was created from - this XML: - @verbatim - This is text - @endverbatim - - then it will not change "This is text", but rather prefix it with a text element: - @verbatim - Hullaballoo!This is text - @endverbatim - - For this XML: - @verbatim - - @endverbatim - SetText() will generate - @verbatim - Hullaballoo! - @endverbatim - */ - void SetText( const char* inText ); - /// Convenience method for setting text inside and element. See SetText() for important limitations. - void SetText( int value ); - /// Convenience method for setting text inside and element. See SetText() for important limitations. - void SetText( unsigned value ); - /// Convenience method for setting text inside and element. See SetText() for important limitations. - void SetText( bool value ); - /// Convenience method for setting text inside and element. See SetText() for important limitations. - void SetText( double value ); - /// Convenience method for setting text inside and element. See SetText() for important limitations. - void SetText( float value ); - - /** - Convenience method to query the value of a child text node. This is probably best - shown by example. Given you have a document is this form: - @verbatim - - 1 - 1.4 - - @endverbatim - - The QueryIntText() and similar functions provide a safe and easier way to get to the - "value" of x and y. - - @verbatim - int x = 0; - float y = 0; // types of x and y are contrived for example - const XMLElement* xElement = pointElement->FirstChildElement( "x" ); - const XMLElement* yElement = pointElement->FirstChildElement( "y" ); - xElement->QueryIntText( &x ); - yElement->QueryFloatText( &y ); - @endverbatim - - @returns XML_SUCCESS (0) on success, XML_CAN_NOT_CONVERT_TEXT if the text cannot be converted - to the requested type, and XML_NO_TEXT_NODE if there is no child text to query. - - */ - XMLError QueryIntText( int* ival ) const; - /// See QueryIntText() - XMLError QueryUnsignedText( unsigned* uval ) const; - /// See QueryIntText() - XMLError QueryBoolText( bool* bval ) const; - /// See QueryIntText() - XMLError QueryDoubleText( double* dval ) const; - /// See QueryIntText() - XMLError QueryFloatText( float* fval ) const; - - // internal: - enum { - OPEN, // - CLOSED, // - CLOSING // - }; - int ClosingType() const { - return _closingType; - } - char* ParseDeep( char* p, StrPair* endTag ); - virtual XMLNode* ShallowClone( XMLDocument* document ) const; - virtual bool ShallowEqual( const XMLNode* compare ) const; - -private: - XMLElement( XMLDocument* doc ); - virtual ~XMLElement(); - XMLElement( const XMLElement& ); // not supported - void operator=( const XMLElement& ); // not supported - - XMLAttribute* FindAttribute( const char* name ); - XMLAttribute* FindOrCreateAttribute( const char* name ); - //void LinkAttribute( XMLAttribute* attrib ); - char* ParseAttributes( char* p ); - static void DeleteAttribute( XMLAttribute* attribute ); - - enum { BUF_SIZE = 200 }; - int _closingType; - // The attribute list is ordered; there is no 'lastAttribute' - // because the list needs to be scanned for dupes before adding - // a new attribute. - XMLAttribute* _rootAttribute; -}; - - -enum Whitespace { - PRESERVE_WHITESPACE, - COLLAPSE_WHITESPACE -}; - - -/** A Document binds together all the functionality. - It can be saved, loaded, and printed to the screen. - All Nodes are connected and allocated to a Document. - If the Document is deleted, all its Nodes are also deleted. -*/ -class TINYXML2_LIB XMLDocument : public XMLNode -{ - friend class XMLElement; -public: - /// constructor - XMLDocument( bool processEntities = true, Whitespace = PRESERVE_WHITESPACE ); - ~XMLDocument(); - - virtual XMLDocument* ToDocument() { - return this; - } - virtual const XMLDocument* ToDocument() const { - return this; - } - - /** - Parse an XML file from a character string. - Returns XML_NO_ERROR (0) on success, or - an errorID. - - You may optionally pass in the 'nBytes', which is - the number of bytes which will be parsed. If not - specified, TinyXML-2 will assume 'xml' points to a - null terminated string. - */ - XMLError Parse( const char* xml, size_t nBytes=(size_t)(-1) ); - - /** - Load an XML file from disk. - Returns XML_NO_ERROR (0) on success, or - an errorID. - */ - XMLError LoadFile( const char* filename ); - - /** - Load an XML file from disk. You are responsible - for providing and closing the FILE*. - - Returns XML_NO_ERROR (0) on success, or - an errorID. - */ - XMLError LoadFile( FILE* ); - - /** - Save the XML file to disk. - Returns XML_NO_ERROR (0) on success, or - an errorID. - */ - XMLError SaveFile( const char* filename, bool compact = false ); - - /** - Save the XML file to disk. You are responsible - for providing and closing the FILE*. - - Returns XML_NO_ERROR (0) on success, or - an errorID. - */ - XMLError SaveFile( FILE* fp, bool compact = false ); - - bool ProcessEntities() const { - return _processEntities; - } - Whitespace WhitespaceMode() const { - return _whitespace; - } - - /** - Returns true if this document has a leading Byte Order Mark of UTF8. - */ - bool HasBOM() const { - return _writeBOM; - } - /** Sets whether to write the BOM when writing the file. - */ - void SetBOM( bool useBOM ) { - _writeBOM = useBOM; - } - - /** Return the root element of DOM. Equivalent to FirstChildElement(). - To get the first node, use FirstChild(). - */ - XMLElement* RootElement() { - return FirstChildElement(); - } - const XMLElement* RootElement() const { - return FirstChildElement(); - } - - /** Print the Document. If the Printer is not provided, it will - print to stdout. If you provide Printer, this can print to a file: - @verbatim - XMLPrinter printer( fp ); - doc.Print( &printer ); - @endverbatim - - Or you can use a printer to print to memory: - @verbatim - XMLPrinter printer; - doc.Print( &printer ); - // printer.CStr() has a const char* to the XML - @endverbatim - */ - void Print( XMLPrinter* streamer=0 ) const; - virtual bool Accept( XMLVisitor* visitor ) const; - - /** - Create a new Element associated with - this Document. The memory for the Element - is managed by the Document. - */ - XMLElement* NewElement( const char* name ); - /** - Create a new Comment associated with - this Document. The memory for the Comment - is managed by the Document. - */ - XMLComment* NewComment( const char* comment ); - /** - Create a new Text associated with - this Document. The memory for the Text - is managed by the Document. - */ - XMLText* NewText( const char* text ); - /** - Create a new Declaration associated with - this Document. The memory for the object - is managed by the Document. - - If the 'text' param is null, the standard - declaration is used.: - @verbatim - - @endverbatim - */ - XMLDeclaration* NewDeclaration( const char* text=0 ); - /** - Create a new Unknown associated with - this Document. The memory for the object - is managed by the Document. - */ - XMLUnknown* NewUnknown( const char* text ); - - /** - Delete a node associated with this document. - It will be unlinked from the DOM. - */ - void DeleteNode( XMLNode* node ) { - node->_parent->DeleteChild( node ); - } - - void SetError( XMLError error, const char* str1, const char* str2 ); - - /// Return true if there was an error parsing the document. - bool Error() const { - return _errorID != XML_NO_ERROR; - } - /// Return the errorID. - XMLError ErrorID() const { - return _errorID; - } - const char* ErrorName() const; - - /// Return a possibly helpful diagnostic location or string. - const char* GetErrorStr1() const { - return _errorStr1; - } - /// Return a possibly helpful secondary diagnostic location or string. - const char* GetErrorStr2() const { - return _errorStr2; - } - /// If there is an error, print it to stdout. - void PrintError() const; - - /// Clear the document, resetting it to the initial state. - void Clear(); - - // internal - char* Identify( char* p, XMLNode** node ); - - virtual XMLNode* ShallowClone( XMLDocument* /*document*/ ) const { - return 0; - } - virtual bool ShallowEqual( const XMLNode* /*compare*/ ) const { - return false; - } - -private: - XMLDocument( const XMLDocument& ); // not supported - void operator=( const XMLDocument& ); // not supported - - bool _writeBOM; - bool _processEntities; - XMLError _errorID; - Whitespace _whitespace; - const char* _errorStr1; - const char* _errorStr2; - char* _charBuffer; - - MemPoolT< sizeof(XMLElement) > _elementPool; - MemPoolT< sizeof(XMLAttribute) > _attributePool; - MemPoolT< sizeof(XMLText) > _textPool; - MemPoolT< sizeof(XMLComment) > _commentPool; - - static const char* _errorNames[XML_ERROR_COUNT]; -}; - - -/** - A XMLHandle is a class that wraps a node pointer with null checks; this is - an incredibly useful thing. Note that XMLHandle is not part of the TinyXML-2 - DOM structure. It is a separate utility class. - - Take an example: - @verbatim - - - - - - - @endverbatim - - Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very - easy to write a *lot* of code that looks like: - - @verbatim - XMLElement* root = document.FirstChildElement( "Document" ); - if ( root ) - { - XMLElement* element = root->FirstChildElement( "Element" ); - if ( element ) - { - XMLElement* child = element->FirstChildElement( "Child" ); - if ( child ) - { - XMLElement* child2 = child->NextSiblingElement( "Child" ); - if ( child2 ) - { - // Finally do something useful. - @endverbatim - - And that doesn't even cover "else" cases. XMLHandle addresses the verbosity - of such code. A XMLHandle checks for null pointers so it is perfectly safe - and correct to use: - - @verbatim - XMLHandle docHandle( &document ); - XMLElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild().NextSibling().ToElement(); - if ( child2 ) - { - // do something useful - @endverbatim - - Which is MUCH more concise and useful. - - It is also safe to copy handles - internally they are nothing more than node pointers. - @verbatim - XMLHandle handleCopy = handle; - @endverbatim - - See also XMLConstHandle, which is the same as XMLHandle, but operates on const objects. -*/ -class TINYXML2_LIB XMLHandle -{ -public: - /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. - XMLHandle( XMLNode* node ) { - _node = node; - } - /// Create a handle from a node. - XMLHandle( XMLNode& node ) { - _node = &node; - } - /// Copy constructor - XMLHandle( const XMLHandle& ref ) { - _node = ref._node; - } - /// Assignment - XMLHandle& operator=( const XMLHandle& ref ) { - _node = ref._node; - return *this; - } - - /// Get the first child of this handle. - XMLHandle FirstChild() { - return XMLHandle( _node ? _node->FirstChild() : 0 ); - } - /// Get the first child element of this handle. - XMLHandle FirstChildElement( const char* value=0 ) { - return XMLHandle( _node ? _node->FirstChildElement( value ) : 0 ); - } - /// Get the last child of this handle. - XMLHandle LastChild() { - return XMLHandle( _node ? _node->LastChild() : 0 ); - } - /// Get the last child element of this handle. - XMLHandle LastChildElement( const char* _value=0 ) { - return XMLHandle( _node ? _node->LastChildElement( _value ) : 0 ); - } - /// Get the previous sibling of this handle. - XMLHandle PreviousSibling() { - return XMLHandle( _node ? _node->PreviousSibling() : 0 ); - } - /// Get the previous sibling element of this handle. - XMLHandle PreviousSiblingElement( const char* _value=0 ) { - return XMLHandle( _node ? _node->PreviousSiblingElement( _value ) : 0 ); - } - /// Get the next sibling of this handle. - XMLHandle NextSibling() { - return XMLHandle( _node ? _node->NextSibling() : 0 ); - } - /// Get the next sibling element of this handle. - XMLHandle NextSiblingElement( const char* _value=0 ) { - return XMLHandle( _node ? _node->NextSiblingElement( _value ) : 0 ); - } - - /// Safe cast to XMLNode. This can return null. - XMLNode* ToNode() { - return _node; - } - /// Safe cast to XMLElement. This can return null. - XMLElement* ToElement() { - return ( ( _node == 0 ) ? 0 : _node->ToElement() ); - } - /// Safe cast to XMLText. This can return null. - XMLText* ToText() { - return ( ( _node == 0 ) ? 0 : _node->ToText() ); - } - /// Safe cast to XMLUnknown. This can return null. - XMLUnknown* ToUnknown() { - return ( ( _node == 0 ) ? 0 : _node->ToUnknown() ); - } - /// Safe cast to XMLDeclaration. This can return null. - XMLDeclaration* ToDeclaration() { - return ( ( _node == 0 ) ? 0 : _node->ToDeclaration() ); - } - -private: - XMLNode* _node; -}; - - -/** - A variant of the XMLHandle class for working with const XMLNodes and Documents. It is the - same in all regards, except for the 'const' qualifiers. See XMLHandle for API. -*/ -class TINYXML2_LIB XMLConstHandle -{ -public: - XMLConstHandle( const XMLNode* node ) { - _node = node; - } - XMLConstHandle( const XMLNode& node ) { - _node = &node; - } - XMLConstHandle( const XMLConstHandle& ref ) { - _node = ref._node; - } - - XMLConstHandle& operator=( const XMLConstHandle& ref ) { - _node = ref._node; - return *this; - } - - const XMLConstHandle FirstChild() const { - return XMLConstHandle( _node ? _node->FirstChild() : 0 ); - } - const XMLConstHandle FirstChildElement( const char* value=0 ) const { - return XMLConstHandle( _node ? _node->FirstChildElement( value ) : 0 ); - } - const XMLConstHandle LastChild() const { - return XMLConstHandle( _node ? _node->LastChild() : 0 ); - } - const XMLConstHandle LastChildElement( const char* _value=0 ) const { - return XMLConstHandle( _node ? _node->LastChildElement( _value ) : 0 ); - } - const XMLConstHandle PreviousSibling() const { - return XMLConstHandle( _node ? _node->PreviousSibling() : 0 ); - } - const XMLConstHandle PreviousSiblingElement( const char* _value=0 ) const { - return XMLConstHandle( _node ? _node->PreviousSiblingElement( _value ) : 0 ); - } - const XMLConstHandle NextSibling() const { - return XMLConstHandle( _node ? _node->NextSibling() : 0 ); - } - const XMLConstHandle NextSiblingElement( const char* _value=0 ) const { - return XMLConstHandle( _node ? _node->NextSiblingElement( _value ) : 0 ); - } - - - const XMLNode* ToNode() const { - return _node; - } - const XMLElement* ToElement() const { - return ( ( _node == 0 ) ? 0 : _node->ToElement() ); - } - const XMLText* ToText() const { - return ( ( _node == 0 ) ? 0 : _node->ToText() ); - } - const XMLUnknown* ToUnknown() const { - return ( ( _node == 0 ) ? 0 : _node->ToUnknown() ); - } - const XMLDeclaration* ToDeclaration() const { - return ( ( _node == 0 ) ? 0 : _node->ToDeclaration() ); - } - -private: - const XMLNode* _node; -}; - - -/** - Printing functionality. The XMLPrinter gives you more - options than the XMLDocument::Print() method. - - It can: - -# Print to memory. - -# Print to a file you provide. - -# Print XML without a XMLDocument. - - Print to Memory - - @verbatim - XMLPrinter printer; - doc.Print( &printer ); - SomeFunction( printer.CStr() ); - @endverbatim - - Print to a File - - You provide the file pointer. - @verbatim - XMLPrinter printer( fp ); - doc.Print( &printer ); - @endverbatim - - Print without a XMLDocument - - When loading, an XML parser is very useful. However, sometimes - when saving, it just gets in the way. The code is often set up - for streaming, and constructing the DOM is just overhead. - - The Printer supports the streaming case. The following code - prints out a trivially simple XML file without ever creating - an XML document. - - @verbatim - XMLPrinter printer( fp ); - printer.OpenElement( "foo" ); - printer.PushAttribute( "foo", "bar" ); - printer.CloseElement(); - @endverbatim -*/ -class TINYXML2_LIB XMLPrinter : public XMLVisitor -{ -public: - /** Construct the printer. If the FILE* is specified, - this will print to the FILE. Else it will print - to memory, and the result is available in CStr(). - If 'compact' is set to true, then output is created - with only required whitespace and newlines. - */ - XMLPrinter( FILE* file=0, bool compact = false, int depth = 0 ); - virtual ~XMLPrinter() {} - - /** If streaming, write the BOM and declaration. */ - void PushHeader( bool writeBOM, bool writeDeclaration ); - /** If streaming, start writing an element. - The element must be closed with CloseElement() - */ - void OpenElement( const char* name, bool compactMode=false ); - /// If streaming, add an attribute to an open element. - void PushAttribute( const char* name, const char* value ); - void PushAttribute( const char* name, int value ); - void PushAttribute( const char* name, unsigned value ); - void PushAttribute( const char* name, bool value ); - void PushAttribute( const char* name, double value ); - /// If streaming, close the Element. - virtual void CloseElement( bool compactMode=false ); - - /// Add a text node. - void PushText( const char* text, bool cdata=false ); - /// Add a text node from an integer. - void PushText( int value ); - /// Add a text node from an unsigned. - void PushText( unsigned value ); - /// Add a text node from a bool. - void PushText( bool value ); - /// Add a text node from a float. - void PushText( float value ); - /// Add a text node from a double. - void PushText( double value ); - - /// Add a comment - void PushComment( const char* comment ); - - void PushDeclaration( const char* value ); - void PushUnknown( const char* value ); - - virtual bool VisitEnter( const XMLDocument& /*doc*/ ); - virtual bool VisitExit( const XMLDocument& /*doc*/ ) { - return true; - } - - virtual bool VisitEnter( const XMLElement& element, const XMLAttribute* attribute ); - virtual bool VisitExit( const XMLElement& element ); - - virtual bool Visit( const XMLText& text ); - virtual bool Visit( const XMLComment& comment ); - virtual bool Visit( const XMLDeclaration& declaration ); - virtual bool Visit( const XMLUnknown& unknown ); - - /** - If in print to memory mode, return a pointer to - the XML file in memory. - */ - const char* CStr() const { - return _buffer.Mem(); - } - /** - If in print to memory mode, return the size - of the XML file in memory. (Note the size returned - includes the terminating null.) - */ - int CStrSize() const { - return _buffer.Size(); - } - /** - If in print to memory mode, reset the buffer to the - beginning. - */ - void ClearBuffer() { - _buffer.Clear(); - _buffer.Push(0); - } - -protected: - virtual bool CompactMode( const XMLElement& ) { return _compactMode; } - - /** Prints out the space before an element. You may override to change - the space and tabs used. A PrintSpace() override should call Print(). - */ - virtual void PrintSpace( int depth ); - void Print( const char* format, ... ); - - void SealElement(); - bool _elementJustOpened; - DynArray< const char*, 10 > _stack; - -private: - void PrintString( const char*, bool restrictedEntitySet ); // prints out, after detecting entities. - - bool _firstElement; - FILE* _fp; - int _depth; - int _textDepth; - bool _processEntities; - bool _compactMode; - - enum { - ENTITY_RANGE = 64, - BUF_SIZE = 200 - }; - bool _entityFlag[ENTITY_RANGE]; - bool _restrictedEntityFlag[ENTITY_RANGE]; - - DynArray< char, 20 > _buffer; -}; - - -} // tinyxml2 - -#if defined(_MSC_VER) -# pragma warning(pop) -#endif - -#endif // TINYXML2_INCLUDED diff --git a/libs/MVS/CMakeLists.txt b/libs/MVS/CMakeLists.txt index fa8780f79..14be62072 100644 --- a/libs/MVS/CMakeLists.txt +++ b/libs/MVS/CMakeLists.txt @@ -42,7 +42,7 @@ cxx_library_with_type_no_pch(MVS "Libs" "" "${cxx_default}" set_target_pch(MVS Common.h) # Link its dependencies -TARGET_LINK_LIBRARIES(MVS PRIVATE Common Math IO ${CERES_LIBS} ${CGAL_LIBS} ${CUDA_CUDA_LIBRARY} CGAL::CGAL) +TARGET_LINK_LIBRARIES(MVS PRIVATE Common Math IO ${CERES_LIBS} ${CGAL_LIBS} ${CUDA_CUDA_LIBRARY}) # Install SET_TARGET_PROPERTIES(MVS PROPERTIES diff --git a/libs/MVS/DepthMap.cpp b/libs/MVS/DepthMap.cpp index ed2350524..9c4d77c8a 100644 --- a/libs/MVS/DepthMap.cpp +++ b/libs/MVS/DepthMap.cpp @@ -61,44 +61,45 @@ using namespace MVS; namespace MVS { DEFOPT_SPACE(OPTDENSE, _T("Dense")) -MDEFVAR_OPTDENSE_uint32(nMinResolution, "Min Resolution", "Do not scale images lower than this resolution", "640") DEFVAR_OPTDENSE_uint32(nResolutionLevel, "Resolution Level", "How many times to scale down the images before dense reconstruction", "1") +MDEFVAR_OPTDENSE_uint32(nMaxResolution, "Max Resolution", "Do not scale images lower than this resolution", "3200") +MDEFVAR_OPTDENSE_uint32(nMinResolution, "Min Resolution", "Do not scale images lower than this resolution", "640") DEFVAR_OPTDENSE_uint32(nMinViews, "Min Views", "minimum number of agreeing views to validate a depth", "2") MDEFVAR_OPTDENSE_uint32(nMaxViews, "Max Views", "maximum number of neighbor images used to compute the depth-map for the reference image", "12") DEFVAR_OPTDENSE_uint32(nMinViewsFuse, "Min Views Fuse", "minimum number of images that agrees with an estimate during fusion in order to consider it inlier", "2") DEFVAR_OPTDENSE_uint32(nMinViewsFilter, "Min Views Filter", "minimum number of images that agrees with an estimate in order to consider it inlier", "2") MDEFVAR_OPTDENSE_uint32(nMinViewsFilterAdjust, "Min Views Filter Adjust", "minimum number of images that agrees with an estimate in order to consider it inlier (0 - disabled)", "1") MDEFVAR_OPTDENSE_uint32(nMinViewsTrustPoint, "Min Views Trust Point", "min-number of views so that the point is considered for approximating the depth-maps (<2 - random initialization)", "2") -MDEFVAR_OPTDENSE_uint32(nNumViews, "Num Views", "Number of views used for depth-map estimation (0 - all views available)", "1", "0") +MDEFVAR_OPTDENSE_uint32(nNumViews, "Num Views", "Number of views used for depth-map estimation (0 - all views available)", "0", "1", "4") MDEFVAR_OPTDENSE_bool(bFilterAdjust, "Filter Adjust", "adjust depth estimates during filtering", "1") MDEFVAR_OPTDENSE_bool(bAddCorners, "Add Corners", "add support points at image corners with nearest neighbor disparities", "1") MDEFVAR_OPTDENSE_float(fViewMinScore, "View Min Score", "Min score to consider a neighbor images (0 - disabled)", "2.0") MDEFVAR_OPTDENSE_float(fViewMinScoreRatio, "View Min Score Ratio", "Min score ratio to consider a neighbor images", "0.3") -MDEFVAR_OPTDENSE_float(fMinArea, "Min Area", "Min shared area for accepting the depth triangulation", "0.1") +MDEFVAR_OPTDENSE_float(fMinArea, "Min Area", "Min shared area for accepting the depth triangulation", "0.05") MDEFVAR_OPTDENSE_float(fMinAngle, "Min Angle", "Min angle for accepting the depth triangulation", "3.0") MDEFVAR_OPTDENSE_float(fOptimAngle, "Optim Angle", "Optimal angle for computing the depth triangulation", "10.0") -MDEFVAR_OPTDENSE_float(fMaxAngle, "Max Angle", "Max angle for accepting the depth triangulation", "45.0") +MDEFVAR_OPTDENSE_float(fMaxAngle, "Max Angle", "Max angle for accepting the depth triangulation", "65.0") MDEFVAR_OPTDENSE_float(fDescriptorMinMagnitudeThreshold, "Descriptor Min Magnitude Threshold", "minimum texture variance accepted when matching two patches (0 - disabled)", "0.01") MDEFVAR_OPTDENSE_float(fDepthDiffThreshold, "Depth Diff Threshold", "maximum variance allowed for the depths during refinement", "0.01") +MDEFVAR_OPTDENSE_float(fNormalDiffThreshold, "Normal Diff Threshold", "maximum variance allowed for the normal during fusion (degrees)", "25") MDEFVAR_OPTDENSE_float(fPairwiseMul, "Pairwise Mul", "pairwise cost scale to match the unary cost", "0.3") -MDEFVAR_OPTDENSE_float(fOptimizerEps, "Optimizer Eps", "MRF optimizer stop epsilon", "0.005") +MDEFVAR_OPTDENSE_float(fOptimizerEps, "Optimizer Eps", "MRF optimizer stop epsilon", "0.001") MDEFVAR_OPTDENSE_int32(nOptimizerMaxIters, "Optimizer Max Iters", "MRF optimizer max number of iterations", "80") MDEFVAR_OPTDENSE_uint32(nSpeckleSize, "Speckle Size", "maximal size of a speckle (small speckles get removed)", "100") MDEFVAR_OPTDENSE_uint32(nIpolGapSize, "Interpolate Gap Size", "interpolate small gaps (left<->right, top<->bottom)", "7") -MDEFVAR_OPTDENSE_uint32(nOptimize, "Optimize", "should we filter the extracted depth-maps?", "7") // see OPTIMIZE_FLAGS -MDEFVAR_OPTDENSE_uint32(nEstimateColors, "Estimate Colors", "should we estimate the colors for the dense point-cloud?", "1", "0") +MDEFVAR_OPTDENSE_uint32(nOptimize, "Optimize", "should we filter the extracted depth-maps?", "7") // see DepthFlags +MDEFVAR_OPTDENSE_uint32(nEstimateColors, "Estimate Colors", "should we estimate the colors for the dense point-cloud?", "2", "0", "1") MDEFVAR_OPTDENSE_uint32(nEstimateNormals, "Estimate Normals", "should we estimate the normals for the dense point-cloud?", "0", "1", "2") -MDEFVAR_OPTDENSE_float(fNCCThresholdKeep, "NCC Threshold Keep", "Maximum 1-NCC score accepted for a match", "0.5", "0.3") -MDEFVAR_OPTDENSE_float(fNCCThresholdRefine, "NCC Threshold Refine", "1-NCC score under which a match is not refined anymore", "0.03") +MDEFVAR_OPTDENSE_float(fNCCThresholdKeep, "NCC Threshold Keep", "Maximum 1-NCC score accepted for a match", "0.55", "0.3") MDEFVAR_OPTDENSE_uint32(nEstimationIters, "Estimation Iters", "Number of iterations for depth-map refinement", "4") MDEFVAR_OPTDENSE_uint32(nRandomIters, "Random Iters", "Number of iterations for random assignment per pixel", "6") MDEFVAR_OPTDENSE_uint32(nRandomMaxScale, "Random Max Scale", "Maximum number of iterations to skip during random assignment", "2") -MDEFVAR_OPTDENSE_float(fRandomDepthRatio, "Random Depth Ratio", "Depth range ratio of the current estimate for random plane assignment", "0.01") -MDEFVAR_OPTDENSE_float(fRandomAngle1Range, "Random Angle1 Range", "Angle 1 range for random plane assignment (in degrees)", "90.0") -MDEFVAR_OPTDENSE_float(fRandomAngle2Range, "Random Angle2 Range", "Angle 2 range for random plane assignment (in degrees)", "15.0") -MDEFVAR_OPTDENSE_float(fRandomSmoothDepth, "Random Smooth Depth", "Depth variance used during neighbor smoothness assignment (ratio)", "0.006") -MDEFVAR_OPTDENSE_float(fRandomSmoothNormal, "Random Smooth Normal", "Normal variance used during neighbor smoothness assignment (degrees)", "8.5") -MDEFVAR_OPTDENSE_float(fRandomSmoothBonus, "Random Smooth Bonus", "Score factor used to encourage smoothness (1 - disabled)", "0.9") +MDEFVAR_OPTDENSE_float(fRandomDepthRatio, "Random Depth Ratio", "Depth range ratio of the current estimate for random plane assignment", "0.003", "0.004") +MDEFVAR_OPTDENSE_float(fRandomAngle1Range, "Random Angle1 Range", "Angle 1 range for random plane assignment (degrees)", "16.0", "20.0") +MDEFVAR_OPTDENSE_float(fRandomAngle2Range, "Random Angle2 Range", "Angle 2 range for random plane assignment (degrees)", "10.0", "12.0") +MDEFVAR_OPTDENSE_float(fRandomSmoothDepth, "Random Smooth Depth", "Depth variance used during neighbor smoothness assignment (ratio)", "0.02") +MDEFVAR_OPTDENSE_float(fRandomSmoothNormal, "Random Smooth Normal", "Normal variance used during neighbor smoothness assignment (degrees)", "13") +MDEFVAR_OPTDENSE_float(fRandomSmoothBonus, "Random Smooth Bonus", "Score factor used to encourage smoothness (1 - disabled)", "0.93") } @@ -228,7 +229,7 @@ unsigned DepthData::DecRef() // 1 2 3 // 1 2 4 7 5 3 6 8 9 --> 4 5 6 // 7 8 9 -void DepthEstimator::MapMatrix2ZigzagIdx(const Image8U::Size& size, DepthEstimator::MapRefArr& coords, BitMatrix& mask, int rawStride) +void DepthEstimator::MapMatrix2ZigzagIdx(const Image8U::Size& size, DepthEstimator::MapRefArr& coords, const BitMatrix& mask, int rawStride) { typedef DepthEstimator::MapRef MapRef; const int w = size.width; @@ -257,32 +258,60 @@ void DepthEstimator::MapMatrix2ZigzagIdx(const Image8U::Size& size, DepthEstimat } } -// replace POWI(0.5f, (int)invScaleRange): 0 1 2 3 4 5 6 7 8 9 10 11 +// replace POWI(0.5f, (int)idxScaleRange): 0 1 2 3 4 5 6 7 8 9 10 11 const float DepthEstimator::scaleRanges[12] = {1.f, 0.5f, 0.25f, 0.125f, 0.0625f, 0.03125f, 0.015625f, 0.0078125f, 0.00390625f, 0.001953125f, 0.0009765625f, 0.00048828125f}; -DepthEstimator::DepthEstimator(DepthData& _depthData0, volatile Thread::safe_t& _idx, const Image64F& _image0Sum, const MapRefArr& _coords, ENDIRECTION _dir) +DepthEstimator::DepthEstimator( + unsigned nIter, DepthData& _depthData0, volatile Thread::safe_t& _idx, + #if DENSE_NCC == DENSE_NCC_WEIGHTED + WeightMap& _weightMap0, + #else + const Image64F& _image0Sum, + #endif + const MapRefArr& _coords) : - neighborsData(0,4), neighbors(0,2), + #ifndef _RELEASE + rnd(SEACAVE::Random::default_seed), + #endif idxPixel(_idx), + neighbors(0,2), + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + neighborsClose(0,4), + #endif scores(_depthData0.images.size()-1), depthMap0(_depthData0.depthMap), normalMap0(_depthData0.normalMap), confMap0(_depthData0.confMap), + #if DENSE_NCC == DENSE_NCC_WEIGHTED + weightMap0(_weightMap0), + #endif + nIteration(nIter), images(InitImages(_depthData0)), image0(_depthData0.images[0]), - image0Sum(_image0Sum), coords(_coords), size(_depthData0.images.First().image.size()), + #if DENSE_NCC != DENSE_NCC_WEIGHTED + image0Sum(_image0Sum), + #endif + coords(_coords), size(_depthData0.images.First().image.size()), + dMin(_depthData0.dMin), dMax(_depthData0.dMax), + dMinSqr(SQRT(_depthData0.dMin)), dMaxSqr(SQRT(_depthData0.dMax)), + dir(nIter%2 ? RB2LT : LT2RB), #if DENSE_AGGNCC == DENSE_AGGNCC_NTH idxScore((_depthData0.images.size()-1)/3), + #elif DENSE_AGGNCC == DENSE_AGGNCC_MINMEAN + idxScore(_depthData0.images.size()<=2 ? 0u : 1u), #endif - dir(_dir), dMin(_depthData0.dMin), dMax(_depthData0.dMax), smoothBonusDepth(1.f-OPTDENSE::fRandomSmoothBonus), smoothBonusNormal((1.f-OPTDENSE::fRandomSmoothBonus)*0.96f), - smoothSigmaDepth(-1.f/(2.f*SQUARE(OPTDENSE::fRandomSmoothDepth))), // used in exp(-x^2 / (2*(0.006^2))) - smoothSigmaNormal(-1.f/(2.f*SQUARE(FD2R(OPTDENSE::fRandomSmoothNormal)))), // used in exp(-x^2 / (2*(0.15^2))) + smoothSigmaDepth(-1.f/(2.f*SQUARE(OPTDENSE::fRandomSmoothDepth))), // used in exp(-x^2 / (2*(0.02^2))) + smoothSigmaNormal(-1.f/(2.f*SQUARE(FD2R(OPTDENSE::fRandomSmoothNormal)))), // used in exp(-x^2 / (2*(0.22^2))) thMagnitudeSq(OPTDENSE::fDescriptorMinMagnitudeThreshold>0?SQUARE(OPTDENSE::fDescriptorMinMagnitudeThreshold):-1.f), angle1Range(FD2R(OPTDENSE::fRandomAngle1Range)), angle2Range(FD2R(OPTDENSE::fRandomAngle2Range)), - thConfSmall(OPTDENSE::fNCCThresholdKeep*0.25f), - thConfBig(OPTDENSE::fNCCThresholdKeep*0.5f), + thConfSmall(OPTDENSE::fNCCThresholdKeep*0.2f), + thConfBig(OPTDENSE::fNCCThresholdKeep*0.4f), + thConfRand(OPTDENSE::fNCCThresholdKeep*0.9f), thRobust(OPTDENSE::fNCCThresholdKeep*1.2f) + #if DENSE_REFINE == DENSE_REFINE_EXACT + , thPerturbation(1.f/POW(2.f,float(nIter+1))) + #endif { - ASSERT(_depthData0.images.size() >= 2); + ASSERT(_depthData0.images.size() >= 1); } // center a patch of given size on the segment @@ -295,83 +324,179 @@ bool DepthEstimator::PreparePixelPatch(const ImageRef& x) // fetch the patch pixel values in the main image bool DepthEstimator::FillPixelPatch() { + #if DENSE_NCC != DENSE_NCC_WEIGHTED const float mean(GetImage0Sum(x0)/nTexels); normSq0 = 0; float* pTexel0 = texels0.data(); - for (int i=-nSizeHalfWindow; i<=nSizeHalfWindow; ++i) - for (int j=-nSizeHalfWindow; j<=nSizeHalfWindow; ++j) + for (int i=-nSizeHalfWindow; i<=nSizeHalfWindow; i+=nSizeStep) + for (int j=-nSizeHalfWindow; j<=nSizeHalfWindow; j+=nSizeStep) normSq0 += SQUARE(*pTexel0++ = image0.image(x0.y+i, x0.x+j)-mean); - X0 = (const Vec3&)image0.camera.TransformPointI2C(Cast(x0)); - return normSq0 > thMagnitudeSq; + #else + Weight& w = weightMap0[x0.y*image0.image.width()+x0.x]; + if (w.normSq0 == 0) { + w.sumWeights = 0; + int n = 0; + const float colCenter = image0.image(x0); + for (int i=-nSizeHalfWindow; i<=nSizeHalfWindow; i+=nSizeStep) { + for (int j=-nSizeHalfWindow; j<=nSizeHalfWindow; j+=nSizeStep) { + Weight::Pixel& pw = w.weights[n++]; + w.normSq0 += + (pw.tempWeight = image0.image(x0.y+i, x0.x+j)) * + (pw.weight = GetWeight(ImageRef(j,i), colCenter)); + w.sumWeights += pw.weight; + } + } + ASSERT(n == nTexels); + const float tm(w.normSq0/w.sumWeights); + w.normSq0 = 0; + n = 0; + do { + Weight::Pixel& pw = w.weights[n]; + const float t(pw.tempWeight - tm); + w.normSq0 += (pw.tempWeight = pw.weight * t) * t; + } while (++n < nTexels); + } + normSq0 = w.normSq0; + #endif + if (normSq0 < thMagnitudeSq) + return false; + reinterpret_cast(X0) = image0.camera.TransformPointI2C(Cast(x0)); + return true; } -// compute pixel's NCC score -float DepthEstimator::ScorePixel(Depth depth, const Normal& normal) +// compute pixel's NCC score in the given target image +float DepthEstimator::ScorePixelImage(const ViewData& image1, Depth depth, const Normal& normal) { - ASSERT(depth > 0 && normal.dot(Cast(static_cast(X0))) < 0); - FOREACH(idx, images) { - // center a patch of given size on the segment and fetch the pixel values in the target image - const ViewData& image1 = images[idx]; - float& score = scores[idx]; - const Matrix3x3f H(ComputeHomographyMatrix(image1, depth, normal)); - Point2f pt; - int n(0); - float sum(0); - #if DENSE_NCC != DENSE_NCC_DEFAULT - float sumSq(0), num(0); - #endif - for (int i=-nSizeHalfWindow; i<=nSizeHalfWindow; ++i) { - for (int j=-nSizeHalfWindow; j<=nSizeHalfWindow; ++j) { - ProjectVertex_3x3_2_2(H.val, Point2f((float)(x0.x+j), (float)(x0.y+i)).ptr(), pt.ptr()); - if (!image1.view.image.isInsideWithBorder(pt)) { - score = thRobust; - goto NEXT_IMAGE; - } - #if DENSE_NCC == DENSE_NCC_FAST - const float v(image1.view.image.sample(pt)); - sum += v; - sumSq += SQUARE(v); - num += texels0(n++)*v; - #else - sum += texels1(n++) = image1.view.image.sample(pt); - #endif - } + // center a patch of given size on the segment and fetch the pixel values in the target image + Matrix3x3f H(ComputeHomographyMatrix(image1, depth, normal)); + Point3f X; + ProjectVertex_3x3_2_3(H.val, Point2f(float(x0.x-nSizeHalfWindow),float(x0.y-nSizeHalfWindow)).ptr(), X.ptr()); + Point3f baseX(X); + H *= float(nSizeStep); + int n(0); + float sum(0); + #if DENSE_NCC != DENSE_NCC_DEFAULT + float sumSq(0), num(0); + #endif + #if DENSE_NCC == DENSE_NCC_WEIGHTED + const Weight& w = weightMap0[x0.y*image0.image.width()+x0.x]; + #endif + for (int i=-nSizeHalfWindow; i<=nSizeHalfWindow; i+=nSizeStep) { + for (int j=-nSizeHalfWindow; j<=nSizeHalfWindow; j+=nSizeStep) { + const Point2f pt(X); + if (!image1.view.image.isInsideWithBorder(pt)) + return thRobust; + const float v(image1.view.image.sample(pt)); + #if DENSE_NCC == DENSE_NCC_FAST + sum += v; + sumSq += SQUARE(v); + num += texels0(n++)*v; + #elif DENSE_NCC == DENSE_NCC_WEIGHTED + const Weight::Pixel& pw = w.weights[n++]; + const float vw(v*pw.weight); + sum += vw; + sumSq += v*vw; + num += v*pw.tempWeight; + #else + sum += texels1(n++)=v; + #endif + X.x += H[0]; X.y += H[3]; X.z += H[6]; } - { - ASSERT(n == nTexels); - // score similarity of the reference and target texture patches - #if DENSE_NCC == DENSE_NCC_FAST - const float normSq1(sumSq-SQUARE(sum/nSizeWindow)); + baseX.x += H[1]; baseX.y += H[4]; baseX.z += H[7]; + X = baseX; + } + ASSERT(n == nTexels); + // score similarity of the reference and target texture patches + #if DENSE_NCC == DENSE_NCC_FAST + const float normSq1(sumSq-SQUARE(sum/nSizeWindow)); + #elif DENSE_NCC == DENSE_NCC_WEIGHTED + const float normSq1(sumSq-SQUARE(sum)/w.sumWeights); + #else + const float normSq1(normSqDelta(texels1.data(), sum/(float)nTexels)); + #endif + const float nrmSq(normSq0*normSq1); + if (nrmSq <= 0.f) + return thRobust; + #if DENSE_NCC == DENSE_NCC_DEFAULT + const float num(texels0.dot(texels1)); + #endif + const float ncc(CLAMP(num/SQRT(nrmSq), -1.f, 1.f)); + float score = 1.f - ncc; + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + // encourage smoothness + for (const NeighborEstimate& neighbor: neighborsClose) { + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + const float factorDepth(DENSE_EXP(SQUARE(plane.Distance(neighbor.X)/depth) * smoothSigmaDepth)); #else - const float normSq1(normSqDelta(texels1.data(), sum/(float)nTexels)); - #endif - const float nrm(normSq0*normSq1); - if (nrm <= 0.f) { - score = thRobust; - continue; - } - #if DENSE_NCC == DENSE_NCC_DEFAULT - const float num(texels0.dot(texels1)); + const float factorDepth(DENSE_EXP(SQUARE((depth-neighbor.depth)/depth) * smoothSigmaDepth)); #endif - const float ncc(CLAMP(num/SQRT(nrm), -1.f, 1.f)); - score = 1.f - ncc; - // encourage smoothness - for (const NeighborData& neighbor: neighborsData) { - score *= 1.f - smoothBonusDepth * EXP(SQUARE((depth-neighbor.depth)/depth) * smoothSigmaDepth); - score *= 1.f - smoothBonusNormal * EXP(SQUARE(ACOS(ComputeAngle(normal.ptr(), neighbor.normal.ptr()))) * smoothSigmaNormal); - } - } - NEXT_IMAGE:; + const float factorNormal(DENSE_EXP(SQUARE(ACOS(ComputeAngle(normal.ptr(), neighbor.normal.ptr()))) * smoothSigmaNormal)); + score *= (1.f - smoothBonusDepth * factorDepth) * (1.f - smoothBonusNormal * factorNormal); } + #endif + ASSERT(ISFINITE(score)); + return score; +} + +// compute pixel's NCC score +float DepthEstimator::ScorePixel(Depth depth, const Normal& normal) +{ + ASSERT(depth > 0 && normal.dot(Cast(static_cast(X0))) < 0); + // compute score for this pixel as seen in each view + ASSERT(scores.size() == images.size()); + FOREACH(idxView, images) + scores[idxView] = ScorePixelImage(images[idxView], depth, normal); #if DENSE_AGGNCC == DENSE_AGGNCC_NTH // set score as the nth element - return scores.size() > 1 ? scores.GetNth(idxScore) : scores.front(); + return scores.GetNth(idxScore); #elif DENSE_AGGNCC == DENSE_AGGNCC_MEAN // set score as the average similarity + #if 1 return scores.mean(); #else + const float* pscore(scores.data()); + const float* pescore(pscore+scores.rows()); + float score(0); + do { + score += MINF(*pscore, thRobust); + } while (++pscore <= pescore); + return score/scores.rows(); + #endif + #elif DENSE_AGGNCC == DENSE_AGGNCC_MIN // set score as the min similarity return scores.minCoeff(); + #else + // set score as the min-mean similarity + if (idxScore == 0) + return *std::min_element(scores.cbegin(), scores.cend()); + #if 0 + return std::accumulate(scores.cbegin(), &scores.GetNth(idxScore), 0.f) / idxScore; + #elif 1 + const float* pescore(&scores.PartialSort(idxScore)); + const float* pscore(scores.cbegin()); + int n(0); float score(0); + do { + const float s(*pscore); + if (s < thRobust) { + score += s; + ++n; + } + } while (++pscore <= pescore); + return n ? score/n : thRobust; + #else + const float thScore(MAXF(*std::min_element(scores.cbegin(), scores.cend()), 0.05f)*2); + const float* pscore(scores.cbegin()); + const float* pescore(pscore+scores.size()); + int n(0); float score(0); + do { + const float s(*pscore); + if (s <= thScore) { + score += s; + ++n; + } + } while (++pscore <= pescore); + return score/n; + #endif #endif } @@ -381,136 +506,378 @@ void DepthEstimator::ProcessPixel(IDX idx) { // compute pixel coordinates from pixel index and its neighbors ASSERT(dir == LT2RB || dir == RB2LT); - if (!PreparePixelPatch(dir == LT2RB ? coords[idx] : coords[coords.GetSize()-1-idx])) + if (!PreparePixelPatch(dir == LT2RB ? coords[idx] : coords[coords.GetSize()-1-idx]) || !FillPixelPatch()) return; - - float& conf = confMap0(x0); - unsigned invScaleRange(DecodeScoreScale(conf)); - if ((invScaleRange <= 2 || conf > OPTDENSE::fNCCThresholdRefine) && FillPixelPatch()) { - // find neighbors - neighbors.Empty(); - neighborsData.Empty(); - if (dir == LT2RB) { - // direction from left-top to right-bottom corner - if (x0.x > nSizeHalfWindow) { - const ImageRef nx(x0.x-1, x0.y); - const Depth ndepth(depthMap0(nx)); - if (ndepth > 0) { - neighbors.emplace_back(nx); - neighborsData.emplace_back(ndepth,normalMap0(nx)); - } - } - if (x0.y > nSizeHalfWindow) { - const ImageRef nx(x0.x, x0.y-1); - const Depth ndepth(depthMap0(nx)); - if (ndepth > 0) { - neighbors.emplace_back(nx); - neighborsData.emplace_back(ndepth,normalMap0(nx)); - } - } - if (x0.x < size.width-nSizeHalfWindow) { - const ImageRef nx(x0.x+1, x0.y); - const Depth ndepth(depthMap0(nx)); - if (ndepth > 0) - neighborsData.emplace_back(ndepth,normalMap0(nx)); - } - if (x0.y < size.height-nSizeHalfWindow) { - const ImageRef nx(x0.x, x0.y+1); - const Depth ndepth(depthMap0(nx)); - if (ndepth > 0) - neighborsData.emplace_back(ndepth,normalMap0(nx)); - } - } else { - ASSERT(dir == RB2LT); - // direction from right-bottom to left-top corner - if (x0.x < size.width-nSizeHalfWindow) { - const ImageRef nx(x0.x+1, x0.y); - const Depth ndepth(depthMap0(nx)); - if (ndepth > 0) { - neighbors.emplace_back(nx); - neighborsData.emplace_back(ndepth,normalMap0(nx)); - } - } - if (x0.y < size.height-nSizeHalfWindow) { - const ImageRef nx(x0.x, x0.y+1); - const Depth ndepth(depthMap0(nx)); - if (ndepth > 0) { - neighbors.emplace_back(nx); - neighborsData.emplace_back(ndepth,normalMap0(nx)); - } + // find neighbors + neighbors.Empty(); + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + neighborsClose.Empty(); + #endif + if (dir == LT2RB) { + // direction from left-top to right-bottom corner + if (x0.x > nSizeHalfWindow) { + const ImageRef nx(x0.x-1, x0.y); + const Depth ndepth(depthMap0(nx)); + if (ndepth > 0) { + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + neighbors.emplace_back(nx); + neighborsClose.emplace_back(NeighborEstimate{ndepth,normalMap0(nx) + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + , Cast(image0.camera.TransformPointI2C(Point3(nx, ndepth))) + #endif + }); + #else + neighbors.emplace_back(NeighborData{nx,ndepth,normalMap0(nx)}); + #endif } - if (x0.x > nSizeHalfWindow) { - const ImageRef nx(x0.x-1, x0.y); - const Depth ndepth(depthMap0(nx)); - if (ndepth > 0) - neighborsData.emplace_back(ndepth,normalMap0(nx)); + } + if (x0.y > nSizeHalfWindow) { + const ImageRef nx(x0.x, x0.y-1); + const Depth ndepth(depthMap0(nx)); + if (ndepth > 0) { + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + neighbors.emplace_back(nx); + neighborsClose.emplace_back(NeighborEstimate{ndepth,normalMap0(nx) + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + , Cast(image0.camera.TransformPointI2C(Point3(nx, ndepth))) + #endif + }); + #else + neighbors.emplace_back(NeighborData{nx,ndepth,normalMap0(nx)}); + #endif } - if (x0.y > nSizeHalfWindow) { - const ImageRef nx(x0.x, x0.y-1); - const Depth ndepth(depthMap0(nx)); - if (ndepth > 0) - neighborsData.emplace_back(ndepth,normalMap0(nx)); + } + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + if (x0.x < size.width-nSizeHalfWindow) { + const ImageRef nx(x0.x+1, x0.y); + const Depth ndepth(depthMap0(nx)); + if (ndepth > 0) + neighborsClose.emplace_back(NeighborEstimate{ndepth,normalMap0(nx) + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + , Cast(image0.camera.TransformPointI2C(Point3(nx, ndepth))) + #endif + }); + } + if (x0.y < size.height-nSizeHalfWindow) { + const ImageRef nx(x0.x, x0.y+1); + const Depth ndepth(depthMap0(nx)); + if (ndepth > 0) + neighborsClose.emplace_back(NeighborEstimate{ndepth,normalMap0(nx) + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + , Cast(image0.camera.TransformPointI2C(Point3(nx, ndepth))) + #endif + }); + } + #endif + } else { + ASSERT(dir == RB2LT); + // direction from right-bottom to left-top corner + if (x0.x < size.width-nSizeHalfWindow) { + const ImageRef nx(x0.x+1, x0.y); + const Depth ndepth(depthMap0(nx)); + if (ndepth > 0) { + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + neighbors.emplace_back(nx); + neighborsClose.emplace_back(NeighborEstimate{ndepth,normalMap0(nx) + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + , Cast(image0.camera.TransformPointI2C(Point3(nx, ndepth))) + #endif + }); + #else + neighbors.emplace_back(NeighborData{nx,ndepth,normalMap0(nx)}); + #endif } } - Depth& depth = depthMap0(x0); - Normal& normal = normalMap0(x0); - const Normal viewDir(Cast(static_cast(X0))); - ASSERT(depth > 0 && normal.dot(viewDir) < 0); - // check if any of the neighbor estimates are better then the current estimate - FOREACH(n, neighbors) { - float nconf(confMap0(neighbors[n])); - const unsigned ninvScaleRange(DecodeScoreScale(nconf)); - if (nconf >= OPTDENSE::fNCCThresholdKeep) - continue; - const NeighborData& neighbor = neighborsData[n]; - ASSERT(neighbor.depth > 0); - if (neighbor.normal.dot(viewDir) >= 0) - continue; - const float newconf(ScorePixel(neighbor.depth, neighbor.normal)); - ASSERT(newconf >= 0 && newconf <= 2); - if (conf > newconf) { - conf = newconf; - depth = neighbor.depth; - normal = neighbor.normal; - invScaleRange = (ninvScaleRange>1 ? ninvScaleRange-1 : ninvScaleRange); + if (x0.y < size.height-nSizeHalfWindow) { + const ImageRef nx(x0.x, x0.y+1); + const Depth ndepth(depthMap0(nx)); + if (ndepth > 0) { + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + neighbors.emplace_back(nx); + neighborsClose.emplace_back(NeighborEstimate{ndepth,normalMap0(nx) + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + , Cast(image0.camera.TransformPointI2C(Point3(nx, ndepth))) + #endif + }); + #else + neighbors.emplace_back(NeighborData{nx,ndepth,normalMap0(nx)}); + #endif } } - // check few random solutions close to the current estimate in an attempt to find a better estimate - float depthRange(MaxDepthDifference(depth, OPTDENSE::fRandomDepthRatio)); - if (invScaleRange > OPTDENSE::nRandomMaxScale) - invScaleRange = OPTDENSE::nRandomMaxScale; - else if (invScaleRange == 0) { - if (conf <= thConfSmall) - invScaleRange = 1; - else if (conf <= thConfBig) - depthRange *= 0.5f; + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + if (x0.x > nSizeHalfWindow) { + const ImageRef nx(x0.x-1, x0.y); + const Depth ndepth(depthMap0(nx)); + if (ndepth > 0) + neighborsClose.emplace_back(NeighborEstimate{ndepth,normalMap0(nx) + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + , Cast(image0.camera.TransformPointI2C(Point3(nx, ndepth))) + #endif + }); } - float scaleRange(scaleRanges[invScaleRange]); - Point2f p; - Normal2Dir(normal, p); - Normal nnormal; - for (unsigned iter=invScaleRange; iter= 0) - continue; + if (x0.y > nSizeHalfWindow) { + const ImageRef nx(x0.x, x0.y-1); + const Depth ndepth(depthMap0(nx)); + if (ndepth > 0) + neighborsClose.emplace_back(NeighborEstimate{ndepth,normalMap0(nx) + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + , Cast(image0.camera.TransformPointI2C(Point3(nx, ndepth))) + #endif + }); + } + #endif + } + float& conf = confMap0(x0); + Depth& depth = depthMap0(x0); + Normal& normal = normalMap0(x0); + const Normal viewDir(Cast(static_cast(X0))); + ASSERT(depth > 0 && normal.dot(viewDir) < 0); + #if DENSE_REFINE == DENSE_REFINE_ITER + // check if any of the neighbor estimates are better then the current estimate + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + FOREACH(n, neighbors) { + const ImageRef& nx = neighbors[n]; + #else + for (NeighborData& neighbor: neighbors) { + const ImageRef& nx = neighbor.x; + #endif + if (confMap0(nx) >= OPTDENSE::fNCCThresholdKeep) + continue; + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + NeighborEstimate& neighbor = neighborsClose[n]; + #endif + neighbor.depth = InterpolatePixel(nx, neighbor.depth, neighbor.normal); + CorrectNormal(neighbor.normal); + ASSERT(neighbor.depth > 0 && neighbor.normal.dot(viewDir) <= 0); + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + InitPlane(neighbor.depth, neighbor.normal); + #endif + const float nconf(ScorePixel(neighbor.depth, neighbor.normal)); + ASSERT(nconf >= 0 && nconf <= 2); + if (conf > nconf) { + conf = nconf; + depth = neighbor.depth; + normal = neighbor.normal; + } + } + // try random values around the current estimate in order to refine it + unsigned idxScaleRange(0); + RefineIters: + if (conf <= thConfSmall) + idxScaleRange = 2; + else if (conf <= thConfBig) + idxScaleRange = 1; + else if (conf >= thConfRand) { + // try completely random values in order to find an initial estimate + for (unsigned iter=0; iter= 0); if (conf > nconf) { conf = nconf; depth = ndepth; normal = nnormal; - p = np; - scaleRange *= 0.5f; - ++invScaleRange; + if (conf < thConfRand) + goto RefineIters; } } + return; + } + float scaleRange(scaleRanges[idxScaleRange]); + const float depthRange(MaxDepthDifference(depth, OPTDENSE::fRandomDepthRatio)); + Point2f p; + Normal2Dir(normal, p); + Normal nnormal; + for (unsigned iter=0; iter= 0) + continue; + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + InitPlane(ndepth, nnormal); + #endif + const float nconf(ScorePixel(ndepth, nnormal)); + ASSERT(nconf >= 0); + if (conf > nconf) { + conf = nconf; + depth = ndepth; + normal = nnormal; + p = np; + scaleRange = scaleRanges[++idxScaleRange]; + } + } + #else + // current pixel estimate + PixelEstimate currEstimate{depth, normal}; + // propagate depth estimate from the best neighbor estimate + PixelEstimate prevEstimate; float prevCost(FLT_MAX); + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + FOREACH(n, neighbors) { + const ImageRef& nx = neighbors[n]; + #else + for (const NeighborData& neighbor: neighbors) { + const ImageRef& nx = neighbor.x; + #endif + float nconf(confMap0(nx)); + const unsigned nidxScaleRange(DecodeScoreScale(nconf)); + ASSERT(nconf >= 0 && nconf <= 2); + if (nconf >= OPTDENSE::fNCCThresholdKeep) + continue; + if (prevCost <= nconf) + continue; + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + const NeighborEstimate& neighbor = neighborsClose[n]; + #endif + if (neighbor.normal.dot(viewDir) >= 0) + continue; + prevEstimate.depth = InterpolatePixel(nx, neighbor.depth, neighbor.normal); + prevEstimate.normal = neighbor.normal; + CorrectNormal(prevEstimate.normal); + prevCost = nconf; + } + if (prevCost == FLT_MAX) + prevEstimate = PerturbEstimate(currEstimate, thPerturbation); + // randomly sampled estimate + PixelEstimate randEstimate(PerturbEstimate(currEstimate, thPerturbation)); + // select best pixel estimate + const int numCosts = 5; + float costs[numCosts] = {0,0,0,0,0}; + const Depth depths[numCosts] = { + currEstimate.depth, prevEstimate.depth, randEstimate.depth, + currEstimate.depth, randEstimate.depth}; + const Normal normals[numCosts] = { + currEstimate.normal, prevEstimate.normal, + randEstimate.normal, randEstimate.normal, + currEstimate.normal}; + conf = FLT_MAX; + for (int idxCost=0; idxCost= 0); + if (conf > nconf) { + conf = nconf; + depth = ndepth; + normal = nnormal; + } + } + #endif +} + +// interpolate given pixel's estimate to the current position +Depth DepthEstimator::InterpolatePixel(const ImageRef& nx, Depth depth, const Normal& normal) const +{ + ASSERT(depth > 0 && normal.dot(image0.camera.TransformPointI2C(Cast(nx))) < 0); + Depth depthNew; + #if 1 + // compute as intersection of the lines + // {(x1, y1), (x2, y2)} from neighbor's 3D point towards normal direction + // and + // {(0, 0), (x4, 1)} from camera center towards current pixel direction + // in the x or y plane + if (x0.x == nx.x) { + const float fy = (float)image0.camera.K[4]; + const float cy = (float)image0.camera.K[5]; + const float x1 = depth * (nx.y - cy) / fy; + const float y1 = depth; + const float x4 = (x0.y - cy) / fy; + const float denom = normal.z + x4 * normal.y; + if (ISZERO(denom)) + return depth; + const float x2 = x1 + normal.z; + const float y2 = y1 - normal.y; + const float nom = y1 * x2 - x1 * y2; + depthNew = nom / denom; } - conf = EncodeScoreScale(conf, invScaleRange); + else { + ASSERT(x0.y == nx.y); + const float fx = (float)image0.camera.K[0]; + const float cx = (float)image0.camera.K[2]; + ASSERT(image0.camera.K[1] == 0); + const float x1 = depth * (nx.x - cx) / fx; + const float y1 = depth; + const float x4 = (x0.x - cx) / fx; + const float denom = normal.z + x4 * normal.x; + if (ISZERO(denom)) + return depth; + const float x2 = x1 + normal.z; + const float y2 = y1 - normal.x; + const float nom = y1 * x2 - x1 * y2; + depthNew = nom / denom; + } + #else + // compute as the ray - plane intersection + { + #if 0 + const Plane plane(Cast(normal), image0.camera.TransformPointI2C(Point3(nx, depth))); + const Ray3 ray(Point3::ZERO, normalized(X0)); + depthNew = (Depth)ray.Intersects(plane).z(); + #else + const Point3 planeN(normal); + const REAL planeD(planeN.dot(image0.camera.TransformPointI2C(Point3(nx, depth)))); + depthNew = (Depth)(planeD / planeN.dot(reinterpret_cast(X0))); + #endif + } + #endif + return ISINSIDE(depthNew,dMin,dMax) ? depthNew : depth; +} + +#if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE +// compute plane defined by current depth and normal estimate +void DepthEstimator::InitPlane(Depth depth, const Normal& normal) +{ + #if 0 + plane.Set(reinterpret_cast(normal), Vec3f(depth*Cast(X0))); + #else + plane.m_vN = reinterpret_cast(normal); + plane.m_fD = -depth*reinterpret_cast(normal).dot(Cast(X0)); + #endif +} +#endif + +#if DENSE_REFINE == DENSE_REFINE_EXACT +DepthEstimator::PixelEstimate DepthEstimator::PerturbEstimate(const PixelEstimate& est, float perturbation) +{ + PixelEstimate ptbEst; + + // perturb depth + const float minDepth = est.depth * (1.f-perturbation); + const float maxDepth = est.depth * (1.f+perturbation); + ptbEst.depth = CLAMP(rnd.randomUniform(minDepth, maxDepth), dMin, dMax); + + // perturb normal + const Normal viewDir(Cast(static_cast(X0))); + std::uniform_real_distribution urd(-1.f, 1.f); + const int numMaxTrials = 3; + int numTrials = 0; + perturbation *= FHALF_PI; + while(true) { + // generate random perturbation rotation + const RMatrixBaseF R(urd(rnd)*perturbation, urd(rnd)*perturbation, urd(rnd)*perturbation); + // perturb normal vector + ptbEst.normal = R * est.normal; + // make sure the perturbed normal is still looking towards the camera, + // otherwise try again with a smaller perturbation + if (ptbEst.normal.dot(viewDir) < 0.f) + break; + if (++numTrials == numMaxTrials) { + ptbEst.normal = est.normal; + return ptbEst; + } + perturbation *= 0.5f; + } + ASSERT(ISEQUAL(norm(ptbEst.normal), 1.f)); + + return ptbEst; } +#endif /*----------------------------------------------------------------*/ diff --git a/libs/MVS/DepthMap.h b/libs/MVS/DepthMap.h index 915e93685..426b8b6d1 100644 --- a/libs/MVS/DepthMap.h +++ b/libs/MVS/DepthMap.h @@ -44,13 +44,31 @@ // NCC type used for patch-similarity computation during depth-map estimation #define DENSE_NCC_DEFAULT 0 #define DENSE_NCC_FAST 1 -#define DENSE_NCC DENSE_NCC_FAST +#define DENSE_NCC_WEIGHTED 2 +#define DENSE_NCC DENSE_NCC_WEIGHTED // NCC score aggregation type used during depth-map estimation #define DENSE_AGGNCC_NTH 0 #define DENSE_AGGNCC_MEAN 1 #define DENSE_AGGNCC_MIN 2 -#define DENSE_AGGNCC DENSE_AGGNCC_MEAN +#define DENSE_AGGNCC_MINMEAN 3 +#define DENSE_AGGNCC DENSE_AGGNCC_MINMEAN + +// type of smoothness used during depth-map estimation +#define DENSE_SMOOTHNESS_NA 0 +#define DENSE_SMOOTHNESS_FAST 1 +#define DENSE_SMOOTHNESS_PLANE 2 +#define DENSE_SMOOTHNESS DENSE_SMOOTHNESS_PLANE + +// type of refinement used during depth-map estimation +#define DENSE_REFINE_ITER 0 +#define DENSE_REFINE_EXACT 1 +#define DENSE_REFINE DENSE_REFINE_ITER + +// exp function type used during depth estimation +#define DENSE_EXP_DEFUALT EXP +#define DENSE_EXP_FAST FEXP // ~10% faster, but slightly less precise +#define DENSE_EXP DENSE_EXP_DEFUALT #define ComposeDepthFilePathBase(b, i, e) MAKE_PATH(String::FormatString((b + "%04u." e).c_str(), i)) #define ComposeDepthFilePath(i, e) MAKE_PATH(String::FormatString("depth%04u." e, i)) @@ -69,8 +87,9 @@ enum DepthFlags { ADJUST_FILTER = (1 << 2), OPTIMIZE = (REMOVE_SPECKLES|FILL_GAPS) }; -extern unsigned nMinResolution; extern unsigned nResolutionLevel; +extern unsigned nMaxResolution; +extern unsigned nMinResolution; extern unsigned nMinViews; extern unsigned nMaxViews; extern unsigned nMinViewsFuse; @@ -88,6 +107,7 @@ extern float fOptimAngle; extern float fMaxAngle; extern float fDescriptorMinMagnitudeThreshold; extern float fDepthDiffThreshold; +extern float fNormalDiffThreshold; extern float fPairwiseMul; extern float fOptimizerEps; extern int nOptimizerMaxIters; @@ -97,7 +117,6 @@ extern unsigned nOptimize; extern unsigned nEstimateColors; extern unsigned nEstimateNormals; extern float fNCCThresholdKeep; -extern float fNCCThresholdRefine; extern unsigned nEstimationIters; extern unsigned nRandomIters; extern unsigned nRandomMaxScale; @@ -111,6 +130,18 @@ extern float fRandomSmoothBonus; /*----------------------------------------------------------------*/ +template +struct WeightedPatchFix { + struct Pixel { + float weight; + float tempWeight; + }; + Pixel weights[nTexels]; + float sumWeights; + float normSq0; + WeightedPatchFix() : normSq0(0) {} +}; + struct MVS_API DepthData { struct ViewData { float scale; // image scale relative to the reference image @@ -126,7 +157,7 @@ struct MVS_API DepthData { return true; } }; - typedef SEACAVE::cList ViewDataArr; + typedef CLISTDEF2IDX(ViewData,IIndex) ViewDataArr; ViewDataArr images; // array of images used to compute this depth-map (reference image is the first) ViewScoreArr neighbors; // array of all images seeing this depth-map (ordered by decreasing importance) @@ -186,10 +217,11 @@ typedef MVS_API CLISTDEFIDX(DepthData,IIndex) DepthDataArr; struct MVS_API DepthEstimator { - enum { TexelChannels = 1 }; - enum { nSizeHalfWindow = 3 }; + enum { nSizeHalfWindow = 5 }; enum { nSizeWindow = nSizeHalfWindow*2+1 }; - enum { nTexels = nSizeWindow*nSizeWindow*TexelChannels }; + enum { nSizeStep = 2 }; + enum { TexelChannels = 1 }; + enum { nTexels = SQUARE((nSizeHalfWindow*2+nSizeStep)/nSizeStep)*TexelChannels }; enum ENDIRECTION { LT2RB = 0, @@ -202,11 +234,30 @@ struct MVS_API DepthEstimator { typedef Eigen::Matrix TexelVec; struct NeighborData { + ImageRef x; + Depth depth; + Normal normal; + }; + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + struct NeighborEstimate { Depth depth; Normal normal; - inline NeighborData() {} - inline NeighborData(Depth d, const Normal& n) : depth(d), normal(n) {} + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + Planef::POINT X; + #endif }; + #endif + #if DENSE_REFINE == DENSE_REFINE_EXACT + struct PixelEstimate { + Depth depth; + Normal normal; + }; + #endif + + #if DENSE_NCC == DENSE_NCC_WEIGHTED + typedef WeightedPatchFix Weight; + typedef CLISTDEFIDX(Weight,int) WeightMap; + #endif struct ViewData { const DepthData::ViewData& view; @@ -221,50 +272,99 @@ struct MVS_API DepthEstimator { Hr(image0.camera.K.inv()) {} }; - CLISTDEF0IDX(NeighborData,IIndex) neighborsData; // neighbor pixel depths to be used for smoothing - CLISTDEF0IDX(ImageRef,IIndex) neighbors; // neighbor pixels coordinates to be processed + SEACAVE::Random rnd; + volatile Thread::safe_t& idxPixel; // current image index to be processed + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_NA + CLISTDEF0IDX(NeighborData,IIndex) neighbors; // neighbor pixels coordinates to be processed + #else + CLISTDEF0IDX(ImageRef,IIndex) neighbors; // neighbor pixels coordinates to be processed + #endif + #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA + CLISTDEF0IDX(NeighborEstimate,IIndex) neighborsClose; // close neighbor pixel depths to be used for smoothing + #endif Vec3 X0; // ImageRef x0; // constants during one pixel loop float normSq0; // + #if DENSE_NCC != DENSE_NCC_WEIGHTED TexelVec texels0; // + #endif #if DENSE_NCC == DENSE_NCC_DEFAULT TexelVec texels1; #endif - #if DENSE_AGGNCC == DENSE_AGGNCC_NTH + #if DENSE_AGGNCC == DENSE_AGGNCC_NTH || DENSE_AGGNCC == DENSE_AGGNCC_MINMEAN FloatArr scores; #else Eigen::VectorXf scores; #endif + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + Planef plane; // plane defined by current depth and normal estimate + #endif DepthMap& depthMap0; NormalMap& normalMap0; ConfidenceMap& confMap0; + #if DENSE_NCC == DENSE_NCC_WEIGHTED + WeightMap& weightMap0; + #endif - const CLISTDEF0(ViewData) images; // neighbor images used + const unsigned nIteration; // current PatchMatch iteration + const CLISTDEF0IDX(ViewData,IIndex) images; // neighbor images used const DepthData::ViewData& image0; + #if DENSE_NCC != DENSE_NCC_WEIGHTED const Image64F& image0Sum; // integral image used to fast compute patch mean intensity + #endif const MapRefArr& coords; const Image8U::Size size; - #if DENSE_AGGNCC == DENSE_AGGNCC_NTH + const Depth dMin, dMax; + const Depth dMinSqr, dMaxSqr; + const ENDIRECTION dir; + #if DENSE_AGGNCC == DENSE_AGGNCC_NTH || DENSE_AGGNCC == DENSE_AGGNCC_MINMEAN const IDX idxScore; #endif - const ENDIRECTION dir; - const Depth dMin, dMax; - DepthEstimator(DepthData& _depthData0, volatile Thread::safe_t& _idx, const Image64F& _image0Sum, const MapRefArr& _coords, ENDIRECTION _dir); + DepthEstimator( + unsigned nIter, DepthData& _depthData0, volatile Thread::safe_t& _idx, + #if DENSE_NCC == DENSE_NCC_WEIGHTED + WeightMap& _weightMap0, + #else + const Image64F& _image0Sum, + #endif + const MapRefArr& _coords); bool PreparePixelPatch(const ImageRef&); bool FillPixelPatch(); + float ScorePixelImage(const ViewData& image1, Depth, const Normal&); float ScorePixel(Depth, const Normal&); void ProcessPixel(IDX idx); - + Depth InterpolatePixel(const ImageRef&, Depth, const Normal&) const; + #if DENSE_SMOOTHNESS == DENSE_SMOOTHNESS_PLANE + void InitPlane(Depth, const Normal&); + #endif + #if DENSE_REFINE == DENSE_REFINE_EXACT + PixelEstimate PerturbEstimate(const PixelEstimate&, float perturbation); + #endif + + #if DENSE_NCC != DENSE_NCC_WEIGHTED inline float GetImage0Sum(const ImageRef& p) const { const ImageRef p0(p.x-nSizeHalfWindow, p.y-nSizeHalfWindow); const ImageRef p1(p0.x+nSizeWindow, p0.y); const ImageRef p2(p0.x, p0.y+nSizeWindow); - const ImageRef p3(p0.x+nSizeWindow, p0.y+nSizeWindow); + const ImageRef p3(p1.x, p2.y); return (float)(image0Sum(p3) - image0Sum(p2) - image0Sum(p1) + image0Sum(p0)); } + #endif + + #if DENSE_NCC == DENSE_NCC_WEIGHTED + float GetWeight(const ImageRef& x, float center) const { + // color weight [0..1] + const float sigmaColor(-1.f/(2.f*SQUARE(0.2f))); + const float wColor(SQUARE(image0.image(x0+x)-center) * sigmaColor); + // spatial weight [0..1] + const float sigmaSpatial(-1.f/(2.f*SQUARE((int)nSizeHalfWindow))); + const float wSpatial(float(SQUARE(x.x) + SQUARE(x.y)) * sigmaSpatial); + return DENSE_EXP(wColor+wSpatial); + } + #endif inline Matrix3x3f ComputeHomographyMatrix(const ViewData& img, Depth depth, const Normal& normal) const { #if 0 @@ -277,10 +377,10 @@ struct MVS_API DepthEstimator { #endif } - static inline CLISTDEF0(ViewData) InitImages(const DepthData& depthData) { - CLISTDEF0(ViewData) images(0, depthData.images.GetSize()-1); + static inline CLISTDEF0IDX(ViewData,IIndex) InitImages(const DepthData& depthData) { + CLISTDEF0IDX(ViewData,IIndex) images(0, depthData.images.GetSize()-1); const DepthData::ViewData& image0(depthData.images.First()); - for (IDX i=1; i 0); - return randomRange(dMin, dMax); + inline Depth RandomDepth(Depth dMinSqr, Depth dMaxSqr) { + ASSERT(dMinSqr > 0 && dMinSqr < dMaxSqr); + return SQUARE(rnd.randomRange(dMinSqr, dMaxSqr)); } - static inline Normal RandomNormal(const Point3f& viewRay) { + inline Normal RandomNormal(const Point3f& viewRay) { Normal normal; - Dir2Normal(Point2f(randomRange(FD2R(0.f),FD2R(360.f)), randomRange(FD2R(120.f),FD2R(180.f))), normal); + Dir2Normal(Point2f(rnd.randomRange(FD2R(0.f),FD2R(180.f)), rnd.randomRange(FD2R(90.f),FD2R(180.f))), normal); return normal.dot(viewRay) > 0 ? -normal : normal; } - // encode/decode NCC score and refinement level in one float - static inline float EncodeScoreScale(float score, unsigned invScaleRange=0) { - ASSERT(score >= 0.f && score <= 2.01f); - return score*0.1f+(float)invScaleRange; - } - static inline unsigned DecodeScale(float score) { - return (unsigned)FLOOR2INT(score); - } - static inline unsigned DecodeScoreScale(float& score) { - const unsigned invScaleRange(DecodeScale(score)); - score = (score-(float)invScaleRange)*10.f; - //ASSERT(score >= 0.f && score <= 2.01f); //problems in multi-threading - return invScaleRange; - } - static inline float DecodeScore(float score) { - DecodeScoreScale(score); - return score; - } - - // Encodes/decodes a normalized 3D vector in two parameters for the direction - template - static inline void Normal2Dir(const TPoint3& d, TPoint2& p) { - // empirically tested - ASSERT(ISEQUAL(norm(d), T(1))); - p.y = TR(atan2(sqrt(d.x*d.x + d.y*d.y), d.z)); - p.x = TR(atan2(d.y, d.x)); - } - template - static inline void Dir2Normal(const TPoint2& p, TPoint3& d) { - // empirically tested - const T siny(sin(p.y)); - d.x = TR(cos(p.x)*siny); - d.y = TR(sin(p.x)*siny); - d.z = TR(cos(p.y)); - ASSERT(ISEQUAL(norm(d), TR(1))); + // adjust normal such that it makes at most 90 degrees with the viewing angle + inline void CorrectNormal(Normal& normal) const { + const Normal viewDir(Cast(X0)); + const float cosAngLen(normal.dot(viewDir)); + if (cosAngLen >= 0) + normal = RMatrixBaseF(normal.cross(viewDir), MINF((ACOS(cosAngLen/norm(viewDir))-FD2R(90.f))*1.01f, -0.001f)) * normal; } - static void MapMatrix2ZigzagIdx(const Image8U::Size& size, DepthEstimator::MapRefArr& coords, BitMatrix& mask, int rawStride=16); + static void MapMatrix2ZigzagIdx(const Image8U::Size& size, DepthEstimator::MapRefArr& coords, const BitMatrix& mask, int rawStride=16); const float smoothBonusDepth, smoothBonusNormal; const float smoothSigmaDepth, smoothSigmaNormal; const float thMagnitudeSq; const float angle1Range, angle2Range; - const float thConfSmall, thConfBig; + const float thConfSmall, thConfBig, thConfRand; const float thRobust; + #if DENSE_REFINE == DENSE_REFINE_EXACT + const float thPerturbation; + #endif static const float scaleRanges[12]; }; /*----------------------------------------------------------------*/ diff --git a/libs/MVS/Image.cpp b/libs/MVS/Image.cpp index 133da424f..c5454b665 100644 --- a/libs/MVS/Image.cpp +++ b/libs/MVS/Image.cpp @@ -161,15 +161,15 @@ float Image::ResizeImage(unsigned nMaxResolution) /*----------------------------------------------------------------*/ // compute image scale for a given max and min resolution, using the current image file data -unsigned Image::RecomputeMaxResolution(unsigned& level, unsigned minImageSize) const +unsigned Image::RecomputeMaxResolution(unsigned& level, unsigned minImageSize, unsigned maxImageSize) const { IMAGEPTR pImage(ReadImageHeader(name)); if (pImage == NULL) { // something went wrong, use the current known size (however it will most probably fail later) - return Image8U3::computeMaxResolution(width, height, level, minImageSize); + return Image8U3::computeMaxResolution(width, height, level, minImageSize, maxImageSize); } // re-compute max image size - return Image8U3::computeMaxResolution(pImage->GetWidth(), pImage->GetHeight(), level, minImageSize); + return Image8U3::computeMaxResolution(pImage->GetWidth(), pImage->GetHeight(), level, minImageSize, maxImageSize); } // RecomputeMaxResolution /*----------------------------------------------------------------*/ @@ -195,4 +195,17 @@ void Image::UpdateCamera(const PlatformArr& platforms) { camera = GetCamera(platforms, Image8U::Size(width, height)); } // UpdateCamera +// computes camera's field of view for the given direction +REAL Image::ComputeFOV(int dir) const +{ + switch (dir) { + case 0: // width + return 2*ATAN(REAL(width)/(camera.K(0,0)*2)); + case 1: // height + return 2*ATAN(REAL(height)/(camera.K(1,1)*2)); + case 2: // diagonal + return 2*ATAN(SQRT(REAL(SQUARE(width)+SQUARE(height)))/(camera.K(0,0)+camera.K(1,1))); + } + return 0; +} // ComputeFOV /*----------------------------------------------------------------*/ diff --git a/libs/MVS/Image.h b/libs/MVS/Image.h index d071842b7..20d68065e 100644 --- a/libs/MVS/Image.h +++ b/libs/MVS/Image.h @@ -105,10 +105,11 @@ class MVS_API Image bool ReloadImage(unsigned nMaxResolution=0, bool bLoadPixels=true); void ReleaseImage(); float ResizeImage(unsigned nMaxResolution=0); - unsigned RecomputeMaxResolution(unsigned& level, unsigned minImageSize) const; + unsigned RecomputeMaxResolution(unsigned& level, unsigned minImageSize, unsigned maxImageSize=INT_MAX) const; Camera GetCamera(const PlatformArr& platforms, const Image8U::Size& resolution) const; void UpdateCamera(const PlatformArr& platforms); + REAL ComputeFOV(int dir) const; float GetNormalizationScale() const { ASSERT(width > 0 && height > 0); @@ -142,7 +143,7 @@ class MVS_API Image BOOST_SERIALIZATION_SPLIT_MEMBER() #endif }; -typedef MVS_API SEACAVE::cList ImageArr; +typedef MVS_API CLISTDEF2IDX(Image,IIndex) ImageArr; /*----------------------------------------------------------------*/ } // namespace MVS diff --git a/libs/MVS/PointCloud.cpp b/libs/MVS/PointCloud.cpp index c43524d5e..42e61230c 100644 --- a/libs/MVS/PointCloud.cpp +++ b/libs/MVS/PointCloud.cpp @@ -185,7 +185,7 @@ bool PointCloud::Load(const String& fileName) } // Load // save the dense point cloud as PLY file -bool PointCloud::Save(const String& fileName) const +bool PointCloud::Save(const String& fileName, bool bLegacyTypes) const { if (points.IsEmpty()) return false; @@ -195,6 +195,8 @@ bool PointCloud::Save(const String& fileName) const ASSERT(!fileName.IsEmpty()); Util::ensureFolder(fileName); PLY ply; + if (bLegacyTypes) + ply.set_legacy_type_names(); if (!ply.write(fileName, 1, BasicPLY::elem_names, PLY::BINARY_LE, 64*1024)) return false; diff --git a/libs/MVS/PointCloud.h b/libs/MVS/PointCloud.h index 6f351ebd4..ce2a9722d 100644 --- a/libs/MVS/PointCloud.h +++ b/libs/MVS/PointCloud.h @@ -93,7 +93,7 @@ class MVS_API PointCloud Box GetAABB(unsigned minViews) const; bool Load(const String& fileName); - bool Save(const String& fileName) const; + bool Save(const String& fileName, bool bLegacyTypes=false) const; #ifdef _USE_BOOST // implement BOOST serialization diff --git a/libs/MVS/Scene.cpp b/libs/MVS/Scene.cpp index 6520b5cc6..7932ae348 100644 --- a/libs/MVS/Scene.cpp +++ b/libs/MVS/Scene.cpp @@ -422,9 +422,13 @@ bool Scene::Save(const String& fileName, ARCHIVE_TYPE type) const inline float Footprint(const Camera& camera, const Point3f& X) { + #if 0 const REAL fSphereRadius(1); const Point3 cX(camera.TransformPointW2C(Cast(X))); return (float)norm(camera.TransformPointC2I(Point3(cX.x+fSphereRadius,cX.y,cX.z))-camera.TransformPointC2I(cX))+std::numeric_limits::epsilon(); + #else + return (float)(camera.GetFocalLength()/camera.PointDepth(X)); + #endif } // compute visibility for the reference image @@ -466,16 +470,15 @@ bool Scene::SelectNeighborViews(uint32_t ID, IndexArr& points, unsigned nMinView // score shared views const Point3f V1(imageData.camera.C - Cast(point)); const float footprint1(Footprint(imageData.camera, point)); - FOREACHPTR(pView, views) { - const PointCloud::View& view = *pView; + for (const PointCloud::View& view: views) { if (view == ID) continue; const Image& imageData2 = images[view]; const Point3f V2(imageData2.camera.C - Cast(point)); - const float footprint2(Footprint(imageData2.camera, point)); const float fAngle(ACOS(ComputeAngle(V1.ptr(), V2.ptr()))); - const float fScaleRatio(footprint1/footprint2); const float wAngle(MINF(POW(fAngle/fOptimAngle, 1.5f), 1.f)); + const float footprint2(Footprint(imageData2.camera, point)); + const float fScaleRatio(footprint1/footprint2); float wScale; if (fScaleRatio > 1.6f) wScale = SQUARE(1.6f/fScaleRatio); @@ -494,40 +497,38 @@ bool Scene::SelectNeighborViews(uint32_t ID, IndexArr& points, unsigned nMinView ASSERT(nPoints > 3); // select best neighborViews - Point2fArr pointsA(0, points.GetSize()), pointsB(0, points.GetSize()); + Point2fArr projs(0, points.GetSize()); FOREACH(IDB, images) { const Image& imageDataB = images[IDB]; if (!imageDataB.IsValid()) continue; const Score& score = scores[IDB]; - if (score.points == 0) + if (score.points < 3) continue; ASSERT(ID != IDB); - ViewScore& neighbor = neighbors.AddEmpty(); // compute how well the matched features are spread out (image covered area) const Point2f boundsA(imageData.GetSize()); const Point2f boundsB(imageDataB.GetSize()); - ASSERT(pointsA.IsEmpty() && pointsB.IsEmpty()); - FOREACHPTR(pIdx, points) { - const PointCloud::ViewArr& views = pointcloud.pointViews[*pIdx]; + ASSERT(projs.IsEmpty()); + for (uint32_t idx: points) { + const PointCloud::ViewArr& views = pointcloud.pointViews[idx]; ASSERT(views.IsSorted()); ASSERT(views.FindFirst(ID) != PointCloud::ViewArr::NO_INDEX); if (views.FindFirst(IDB) == PointCloud::ViewArr::NO_INDEX) continue; - const PointCloud::Point& point = pointcloud.points[*pIdx]; - Point2f& ptA = pointsA.AddConstruct(imageData.camera.ProjectPointP(point)); - Point2f& ptB = pointsB.AddConstruct(imageDataB.camera.ProjectPointP(point)); - if (!imageData.camera.IsInside(ptA, boundsA) || !imageDataB.camera.IsInside(ptB, boundsB)) { - pointsA.RemoveLast(); - pointsB.RemoveLast(); - } + const PointCloud::Point& point = pointcloud.points[idx]; + Point2f& ptA = projs.AddConstruct(imageData.camera.ProjectPointP(point)); + Point2f ptB = imageDataB.camera.ProjectPointP(point); + if (!imageData.camera.IsInside(ptA, boundsA) || !imageDataB.camera.IsInside(ptB, boundsB)) + projs.RemoveLast(); } - ASSERT(pointsA.GetSize() == pointsB.GetSize() && pointsA.GetSize() <= score.points); - const float areaA(ComputeCoveredArea((const float*)pointsA.Begin(), pointsA.GetSize(), boundsA.ptr())); - const float areaB(ComputeCoveredArea((const float*)pointsB.Begin(), pointsB.GetSize(), boundsB.ptr())); - const float area(MINF(areaA, areaB)); - pointsA.Empty(); pointsB.Empty(); + ASSERT(projs.GetSize() <= score.points); + if (projs.IsEmpty()) + continue; + const float area(ComputeCoveredArea((const float*)projs.Begin(), projs.GetSize(), boundsA.ptr())); + projs.Empty(); // store image score + ViewScore& neighbor = neighbors.AddEmpty(); neighbor.idx.ID = IDB; neighbor.idx.points = score.points; neighbor.idx.scale = score.avgScale/score.points; diff --git a/libs/MVS/Scene.h b/libs/MVS/Scene.h index a84b98928..581ad120e 100644 --- a/libs/MVS/Scene.h +++ b/libs/MVS/Scene.h @@ -81,6 +81,7 @@ class MVS_API Scene bool DenseReconstruction(); void DenseReconstructionEstimate(void*); void DenseReconstructionFilter(void*); + void PointCloudFilter(int thRemove=-1); // Mesh reconstruction bool ReconstructMesh(float distInsert=2, bool bUseFreeSpaceSupport=true, unsigned nItersFixNonManifold=4, diff --git a/libs/MVS/SceneDensify.cpp b/libs/MVS/SceneDensify.cpp index 03d5006c8..8424f7cfa 100644 --- a/libs/MVS/SceneDensify.cpp +++ b/libs/MVS/SceneDensify.cpp @@ -118,6 +118,13 @@ class EVTAdjustDepthMap : public Event /*----------------------------------------------------------------*/ +// convert the ZNCC score to a weight used to average the fused points +inline float Conf2Weight(float conf, Depth depth) { + return 1.f/(MAXF(1.f-conf,0.03f)*depth*depth); +} +/*----------------------------------------------------------------*/ + + // S T R U C T S /////////////////////////////////////////////////// @@ -138,7 +145,7 @@ class DepthMapsData bool GapInterpolation(DepthData& depthData); bool FilterDepthMap(DepthData& depthData, const IIndexArr& idxNeighbors, bool bAdjust=true); - void FuseDepthMaps(PointCloud& pointcloud, bool bEstimateNormal); + void FuseDepthMaps(PointCloud& pointcloud, bool bEstimateColor, bool bEstimateNormal); protected: static void* STCALL ScoreDepthMapTmp(void*); @@ -373,7 +380,7 @@ bool DepthMapsData::InitViews(DepthData& depthData, IIndex idxNeighbor, IIndex n // print selected views if (g_nVerbosityLevel > 2) { String msg; - for (IDX i=1; i TriangulatePointsDelaunay(CGAL::Delaunay& delaunay, const ASSERT(sizeof(Point3) == sizeof(X3D)); ASSERT(sizeof(Point3) == sizeof(CGAL::Point)); std::pair depthBounds(FLT_MAX, 0.f); - FOREACH(p, points) { - const PointCloud::Point& point = scene.pointcloud.points[points[p]]; - const Point3 ptCam(image.camera.TransformPointW2C(Cast(point))); - const Point2 ptImg(image.camera.TransformPointC2I(ptCam)); - delaunay.insert(CGAL::Point(ptImg.x, ptImg.y, ptCam.z)); - const Depth depth((float)ptCam.z); - if (depthBounds.first > depth) - depthBounds.first = depth; - if (depthBounds.second < depth) - depthBounds.second = depth; + for (uint32_t idx: points) { + const Point3f pt(image.camera.ProjectPointP3(scene.pointcloud.points[idx])); + delaunay.insert(CGAL::Point(pt.x/pt.z, pt.y/pt.z, pt.z)); + if (depthBounds.first > pt.z) + depthBounds.first = pt.z; + if (depthBounds.second < pt.z) + depthBounds.second = pt.z; } // if full size depth-map requested if (OPTDENSE::bAddCorners) { @@ -445,7 +449,7 @@ std::pair TriangulatePointsDelaunay(CGAL::Delaunay& delaunay, const if (cfc == 0) continue; // normally this should never happen const CGAL::FaceCirculator done(cfc); - Point3d& poszA = (Point3d&)vcorner->point(); + Point3d& poszA = reinterpret_cast(vcorner->point()); const Point2d& posA = reinterpret_cast(poszA); const Ray3d rayA(Point3d::ZERO, normalized(image.camera.TransformPointI2C(poszA))); DepthDistArr depths(0, numPoints); @@ -459,9 +463,9 @@ std::pair TriangulatePointsDelaunay(CGAL::Delaunay& delaunay, const // compute the depth as the intersection of the corner ray with // the plane defined by the face's vertices { - const Point3d& poszB0 = (const Point3d&)fc->vertex(0)->point(); - const Point3d& poszB1 = (const Point3d&)fc->vertex(1)->point(); - const Point3d& poszB2 = (const Point3d&)fc->vertex(2)->point(); + const Point3d& poszB0 = reinterpret_cast(fc->vertex(0)->point()); + const Point3d& poszB1 = reinterpret_cast(fc->vertex(1)->point()); + const Point3d& poszB2 = reinterpret_cast(fc->vertex(2)->point()); const Planed planeB( image.camera.TransformPointI2C(poszB0), image.camera.TransformPointI2C(poszB1), @@ -470,9 +474,13 @@ std::pair TriangulatePointsDelaunay(CGAL::Delaunay& delaunay, const const Point3d poszB(rayA.Intersects(planeB)); if (poszB.z <= 0) continue; - const Point2d posB((reinterpret_cast(poszB0)+reinterpret_cast(poszB1)+reinterpret_cast(poszB2))/3.f); - const REAL dist(norm(posB-posA)); - depths.StoreTop(DepthDist((float)poszB.z, 1.f/(float)dist)); + const Point2d posB(( + reinterpret_cast(poszB0)+ + reinterpret_cast(poszB1)+ + reinterpret_cast(poszB2))/3.f + ); + const double dist(norm(posB-posA)); + depths.StoreTop(DepthDist(CLAMP((float)poszB.z,depthBounds.first,depthBounds.second), INVERT((float)dist))); } Continue:; } while (++cfc != done); @@ -520,8 +528,9 @@ bool DepthMapsData::InitDepthMap(DepthData& depthData) inline void operator()(const ImageRef& pt) { if (!depthMap.isInside(pt)) return; - const float z(INVERT(normalPlane.dot(P.TransformPointI2C(Point2f(pt))))); - ASSERT(z > 0); + const Depth z(INVERT(normalPlane.dot(P.TransformPointI2C(Point2f(pt))))); + if (z <= 0) // due to numerical instability + return; depthMap(pt) = z; normalMap(pt) = normal; } @@ -529,9 +538,9 @@ bool DepthMapsData::InitDepthMap(DepthData& depthData) RasterDepthDataPlaneData data = {camera, depthData.depthMap, depthData.normalMap}; for (CGAL::Delaunay::Face_iterator it=delaunay.faces_begin(); it!=delaunay.faces_end(); ++it) { const CGAL::Delaunay::Face& face = *it; - const Point3f i0((const Point3&)face.vertex(0)->point()); - const Point3f i1((const Point3&)face.vertex(1)->point()); - const Point3f i2((const Point3&)face.vertex(2)->point()); + const Point3f i0(reinterpret_cast(face.vertex(0)->point())); + const Point3f i1(reinterpret_cast(face.vertex(1)->point())); + const Point3f i2(reinterpret_cast(face.vertex(2)->point())); // compute the plane defined by the 3 points const Point3f c0(camera.TransformPointI2C(i0)); const Point3f c1(camera.TransformPointI2C(i1)); @@ -560,21 +569,21 @@ void* STCALL DepthMapsData::ScoreDepthMapTmp(void* arg) if (!estimator.PreparePixelPatch(x) || !estimator.FillPixelPatch()) { estimator.depthMap0(x) = 0; estimator.normalMap0(x) = Normal::ZERO; - estimator.confMap0(x) = DepthEstimator::EncodeScoreScale(2.f); + estimator.confMap0(x) = 2.f; continue; } Depth& depth = estimator.depthMap0(x); Normal& normal = estimator.normalMap0(x); const Normal viewDir(Cast(static_cast(estimator.X0))); - if (depth <= 0) { + if (!ISINSIDE(depth, estimator.dMin, estimator.dMax)) { // init with random values - depth = DepthEstimator::RandomDepth(estimator.dMin, estimator.dMax); - normal = DepthEstimator::RandomNormal(viewDir); + depth = estimator.RandomDepth(estimator.dMinSqr, estimator.dMaxSqr); + normal = estimator.RandomNormal(viewDir); } else if (normal.dot(viewDir) >= 0) { // replace invalid normal with random values - normal = DepthEstimator::RandomNormal(viewDir); + normal = estimator.RandomNormal(viewDir); } - estimator.confMap0(x) = DepthEstimator::EncodeScoreScale(estimator.ScorePixel(depth, normal)); + estimator.confMap0(x) = estimator.ScorePixel(depth, normal); } return NULL; } @@ -595,29 +604,35 @@ void* STCALL DepthMapsData::EndDepthMapTmp(void* arg) const float fOptimAngle(FD2R(OPTDENSE::fOptimAngle)); while ((idx=(IDX)Thread::safeInc(estimator.idxPixel)) < estimator.coords.GetSize()) { const ImageRef& x = estimator.coords[idx]; + ASSERT(estimator.depthMap0(x) >= 0); Depth& depth = estimator.depthMap0(x); - Normal& normal = estimator.normalMap0(x); float& conf = estimator.confMap0(x); - const unsigned invScaleRange(DepthEstimator::DecodeScoreScale(conf)); - ASSERT(depth >= 0); // check if the score is good enough // and that the cross-estimates is close enough to the current estimate - if (conf > OPTDENSE::fNCCThresholdKeep) { + if (depth <= 0 || conf >= OPTDENSE::fNCCThresholdKeep) { #if 1 // used if gap-interpolation is active conf = 0; - normal = Normal::ZERO; + estimator.normalMap0(x) = Normal::ZERO; #endif depth = 0; } else { + #if 1 + // converted ZNCC [0-2] score, where 0 is best, to [0-1] confidence, where 1 is best + conf = conf>=1.f ? 0.f : 1.f-conf; + #else #if 1 FOREACH(i, estimator.images) estimator.scores[i] = ComputeAngle(estimator.image0.camera.TransformPointI2W(Point3(x,depth)).ptr(), estimator.image0.camera.C.ptr(), estimator.images[i].view.camera.C.ptr()); #if DENSE_AGGNCC == DENSE_AGGNCC_NTH - const float fCosAngle(estimator.scores.size() > 1 ? estimator.scores.GetNth(estimator.idxScore) : estimator.scores.front()); + const float fCosAngle(estimator.scores.GetNth(estimator.idxScore)); #elif DENSE_AGGNCC == DENSE_AGGNCC_MEAN const float fCosAngle(estimator.scores.mean()); - #else + #elif DENSE_AGGNCC == DENSE_AGGNCC_MIN const float fCosAngle(estimator.scores.minCoeff()); + #else + const float fCosAngle(estimator.idxScore ? + std::accumulate(estimator.scores.begin(), &estimator.scores.PartialSort(estimator.idxScore), 0.f) / estimator.idxScore : + *std::min_element(estimator.scores.cbegin(), estimator.scores.cend())); #endif const float wAngle(MINF(POW(ACOS(fCosAngle)/fOptimAngle,1.5f),1.f)); #else @@ -625,10 +640,9 @@ void* STCALL DepthMapsData::EndDepthMapTmp(void* arg) #endif #if 1 conf = wAngle/MAXF(conf,1e-2f); - #elif 1 - conf = wAngle/(depth*SQUARE(MAXF(conf,1e-2f))); #else - conf = SQRT((float)invScaleRange)*wAngle/(depth*SQUARE(MAXF(conf,1e-2f))); + conf = wAngle/(depth*SQUARE(MAXF(conf,1e-2f))); + #endif #endif } } @@ -665,7 +679,7 @@ bool DepthMapsData::EstimateDepthMap(IIndex idxImage) // initialize the depth-map if (OPTDENSE::nMinViewsTrustPoint < 2) { // compute depth range and initialize known depths - const int nPixelArea(3); // half windows size around a pixel to be initialize with the known depth + const int nPixelArea(2); // half windows size around a pixel to be initialize with the known depth const Camera& camera = depthData.images.First().camera; depthData.dMin = FLT_MAX; depthData.dMax = 0; @@ -676,9 +690,12 @@ bool DepthMapsData::EstimateDepthMap(IIndex idxImage) const float d((float)camX.z); const ImageRef sx(MAXF(x.x-nPixelArea,0), MAXF(x.y-nPixelArea,0)); const ImageRef ex(MINF(x.x+nPixelArea,size.width-1), MINF(x.y+nPixelArea,size.height-1)); - for (int y=sx.y; y<=ex.y; ++y) - for (int x=sx.x; x<=ex.x; ++x) + for (int y=sx.y; y<=ex.y; ++y) { + for (int x=sx.x; x<=ex.x; ++x) { depthData.depthMap(y,x) = d; + depthData.normalMap(y,x) = Normal::ZERO; + } + } if (depthData.dMin > d) depthData.dMin = d; if (depthData.dMax < d) @@ -700,8 +717,12 @@ bool DepthMapsData::EstimateDepthMap(IIndex idxImage) } // init integral images and index to image-ref map for the reference data + #if DENSE_NCC == DENSE_NCC_WEIGHTED + DepthEstimator::WeightMap weightMap0(size.area()-(size.width+1)*DepthEstimator::nSizeHalfWindow); + #else Image64F imageSum0; cv::integral(image.image, imageSum0, CV_64F); + #endif if (prevDepthMapSize != size) { prevDepthMapSize = size; BitMatrix mask; @@ -723,7 +744,13 @@ bool DepthMapsData::EstimateDepthMap(IIndex idxImage) idxPixel = -1; ASSERT(estimators.IsEmpty()); while (estimators.GetSize() < nMaxThreads) - estimators.AddConstruct(depthData, idxPixel, imageSum0, coords, DepthEstimator::RB2LT); + estimators.AddConstruct(0, depthData, idxPixel, + #if DENSE_NCC == DENSE_NCC_WEIGHTED + weightMap0, + #else + imageSum0, + #endif + coords); ASSERT(estimators.GetSize() == threads.GetSize()+1); FOREACH(i, threads) threads[i].start(ScoreDepthMapTmp, &estimators[i]); @@ -745,11 +772,16 @@ bool DepthMapsData::EstimateDepthMap(IIndex idxImage) // run propagation and random refinement cycles on the reference data for (unsigned iter=0; iter ProjArr; -typedef SEACAVE::cList ProjsArr; -void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateNormal) +void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateColor, bool bEstimateNormal) { TD_TIMER_STARTD(); + struct Proj { + union { + uint32_t idxPixel; + struct { + uint16_t x, y; // image pixel coordinates + }; + }; + inline Proj() {} + inline Proj(uint32_t _idxPixel) : idxPixel(_idxPixel) {} + inline Proj(const ImageRef& ir) : x(ir.x), y(ir.y) {} + inline ImageRef GetCoord() const { return ImageRef(x,y); } + }; + typedef SEACAVE::cList ProjArr; + typedef SEACAVE::cList ProjsArr; + // find best connected images IndexScoreArr connections(0, scene.images.GetSize()); size_t nPointsEstimate(0); @@ -1329,6 +1368,7 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateNormal) // fuse all depth-maps, processing the best connected images first const unsigned nMinViewsFuse(MINF(OPTDENSE::nMinViewsFuse, scene.images.GetSize())); + const float normalError(COS(FD2R(OPTDENSE::fNormalDiffThreshold))); CLISTDEF0(Depth*) invalidDepths(0, 32); size_t nDepths(0); typedef TImage DepthIndex; @@ -1338,6 +1378,10 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateNormal) pointcloud.points.Reserve(nPointsEstimate); pointcloud.pointViews.Reserve(nPointsEstimate); pointcloud.pointWeights.Reserve(nPointsEstimate); + if (bEstimateColor) + pointcloud.colors.Reserve(nPointsEstimate); + if (bEstimateNormal) + pointcloud.normals.Reserve(nPointsEstimate); Util::Progress progress(_T("Fused depth-maps"), connections.GetSize()); GET_LOGCONSOLE().Pause(); FOREACHPTR(pConnection, connections) { @@ -1346,12 +1390,14 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateNormal) const DepthData& depthData(arrDepthData[idxImage]); ASSERT(!depthData.images.IsEmpty() && !depthData.neighbors.IsEmpty()); for (const ViewScore& neighbor: depthData.neighbors) { - const Image& imageData = scene.images[neighbor.idx.ID]; - DepthIndex& depthIdxs = arrDepthIdx[&imageData-scene.images.Begin()]; - if (depthIdxs.empty()) { - depthIdxs.create(Image8U::Size(imageData.width, imageData.height)); - depthIdxs.memset((uint8_t)NO_ID); - } + DepthIndex& depthIdxs = arrDepthIdx[neighbor.idx.ID]; + if (!depthIdxs.empty()) + continue; + const DepthData& depthDataB(arrDepthData[neighbor.idx.ID]); + if (depthDataB.IsEmpty()) + continue; + depthIdxs.create(depthDataB.depthMap.size()); + depthIdxs.memset((uint8_t)NO_ID); } ASSERT(!depthData.IsEmpty()); const Image8U::Size sizeMap(depthData.depthMap.size()); @@ -1381,21 +1427,26 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateNormal) PointCloud::ViewArr& views = pointcloud.pointViews.AddEmpty(); views.Insert(idxImage); PointCloud::WeightArr& weights = pointcloud.pointWeights.AddEmpty(); - weights.Insert(depthData.confMap(x)); + REAL confidence(weights.emplace_back(Conf2Weight(depthData.confMap(x),depth))); ProjArr& pointProjs = projs.AddEmpty(); pointProjs.Insert(Proj(x)); + const PointCloud::Normal normal(imageData.camera.R.t()*Cast(depthData.normalMap(x))); + ASSERT(ISEQUAL(norm(normal), 1.f)); // check the projection in the neighbor depth-maps - REAL confidence(weights.First()); Point3 X(point*confidence); + Pixel32F C(Cast(imageData.image(x))*confidence); + PointCloud::Normal N(normal*confidence); invalidDepths.Empty(); FOREACHPTR(pNeighbor, depthData.neighbors) { const IIndex idxImageB(pNeighbor->idx.ID); + DepthData& depthDataB = arrDepthData[idxImageB]; + if (depthDataB.IsEmpty()) + continue; const Image& imageDataB = scene.images[idxImageB]; const Point3f pt(imageDataB.camera.ProjectPointP3(point)); if (pt.z <= 0) continue; const ImageRef xB(ROUND2INT(pt.x/pt.z), ROUND2INT(pt.y/pt.z)); - DepthData& depthDataB = arrDepthData[idxImageB]; DepthMap& depthMapB = depthDataB.depthMap; if (!depthMapB.isInside(xB)) continue; @@ -1406,16 +1457,26 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateNormal) if (idxPointB != NO_ID) continue; if (IsDepthSimilar(pt.z, depthB, OPTDENSE::fDepthDiffThreshold)) { - // add view to the 3D point - ASSERT(views.FindFirst(idxImageB) == PointCloud::ViewArr::NO_INDEX); - const float confidenceB(depthDataB.confMap(xB)); - const IIndex idx(views.InsertSort(idxImageB)); - weights.InsertAt(idx, confidenceB); - pointProjs.InsertAt(idx, Proj(xB)); - idxPointB = idxPoint; - X += imageDataB.camera.TransformPointI2W(Point3(Point2f(xB),depthB))*REAL(confidenceB); - confidence += confidenceB; - } else + // check if normals agree + const PointCloud::Normal normalB(imageDataB.camera.R.t()*Cast(depthDataB.normalMap(xB))); + ASSERT(ISEQUAL(norm(normalB), 1.f)); + if (normal.dot(normalB) > normalError) { + // add view to the 3D point + ASSERT(views.FindFirst(idxImageB) == PointCloud::ViewArr::NO_INDEX); + const float confidenceB(Conf2Weight(depthDataB.confMap(xB),depthB)); + const IIndex idx(views.InsertSort(idxImageB)); + weights.InsertAt(idx, confidenceB); + pointProjs.InsertAt(idx, Proj(xB)); + idxPointB = idxPoint; + X += imageDataB.camera.TransformPointI2W(Point3(Point2f(xB),depthB))*REAL(confidenceB); + if (bEstimateColor) + C += Cast(imageDataB.image(xB))*confidenceB; + if (bEstimateNormal) + N += normalB*confidenceB; + confidence += confidenceB; + continue; + } + } if (pt.z < depthB) { // discard depth invalidDepths.Insert(&depthB); @@ -1435,8 +1496,13 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateNormal) pointcloud.points.RemoveLast(); } else { // this point is valid, store it - point = X*(REAL(1)/confidence); + const REAL nrm(REAL(1)/confidence); + point = X*nrm; ASSERT(ISFINITE(point)); + if (bEstimateColor) + pointcloud.colors.AddConstruct((C*(float)nrm).cast()); + if (bEstimateNormal) + pointcloud.normals.AddConstruct(normalized(N*(float)nrm)); // invalidate all neighbor depths that do not agree with it for (Depth* pDepth: invalidDepths) *pDepth = 0; @@ -1453,7 +1519,7 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateNormal) DEBUG_EXTRA("Depth-maps fused and filtered: %u depth-maps, %u depths, %u points (%d%%%%) (%s)", connections.GetSize(), nDepths, pointcloud.points.GetSize(), ROUND2INT((100.f*pointcloud.points.GetSize())/nDepths), TD_TIMER_GET_FMT().c_str()); - if (bEstimateNormal && !pointcloud.points.IsEmpty()) { + if (bEstimateNormal && !pointcloud.points.IsEmpty() && pointcloud.normals.IsEmpty()) { // estimate normal also if requested (quite expensive if normal-maps not available) TD_TIMER_STARTD(); pointcloud.normals.Resize(pointcloud.points.GetSize()); @@ -1556,7 +1622,7 @@ bool Scene::DenseReconstruction() data.images.Insert(idxImage); } // reload image at the appropriate resolution - const unsigned nMaxResolution(imageData.RecomputeMaxResolution(OPTDENSE::nResolutionLevel, OPTDENSE::nMinResolution)); + const unsigned nMaxResolution(imageData.RecomputeMaxResolution(OPTDENSE::nResolutionLevel, OPTDENSE::nMinResolution, OPTDENSE::nMaxResolution)); if (!imageData.ReloadImage(nMaxResolution)) { #ifdef DENSE_USE_OPENMP bAbort = true; @@ -1673,7 +1739,7 @@ bool Scene::DenseReconstruction() // fuse all depth-maps pointcloud.Release(); - data.detphMaps.FuseDepthMaps(pointcloud, OPTDENSE::nEstimateNormals == 2); + data.detphMaps.FuseDepthMaps(pointcloud, OPTDENSE::nEstimateColors == 2, OPTDENSE::nEstimateNormals == 2); #if TD_VERBOSE != TD_VERBOSE_OFF if (g_nVerbosityLevel > 2) { // print number of points with 3+ views @@ -1703,7 +1769,7 @@ bool Scene::DenseReconstruction() EstimatePointNormals(images, pointcloud); } return true; -} // DenseReconstructionDepthMap +} // DenseReconstruction /*----------------------------------------------------------------*/ void* DenseReconstructionEstimateTmp(void* arg) { @@ -1739,14 +1805,14 @@ void Scene::DenseReconstructionEstimate(void* pData) break; } // try to load already compute depth-map for this image - if (depthData.Load(ComposeDepthFilePath(idx, "dmap"))) { - if (OPTDENSE::nOptimize & (OPTDENSE::OPTIMIZE)) { + if (File::access(ComposeDepthFilePath(idx, "dmap"))) { + if (OPTDENSE::nOptimize & OPTDENSE::OPTIMIZE) { + if (!depthData.Load(ComposeDepthFilePath(idx, "dmap"))) { + VERBOSE("error: invalid depth-map '%s'", ComposeDepthFilePath(idx, "dmap").c_str()); + exit(EXIT_FAILURE); + } // optimize depth-map data.events.AddEventFirst(new EVTOptimizeDepthMap(evtImage.idxImage)); - } else { - // release image data - depthData.ReleaseImages(); - depthData.Release(); } // process next image data.events.AddEvent(new EVTProcessImage((uint32_t)Thread::safeInc(data.idxImage))); @@ -1925,3 +1991,138 @@ void Scene::DenseReconstructionFilter(void* pData) } } // DenseReconstructionFilter /*----------------------------------------------------------------*/ + +// filter point-cloud based on camera-point visibility intersections +void Scene::PointCloudFilter(int thRemove) +{ + TD_TIMER_STARTD(); + + typedef TOctree Octree; + struct Collector { + typedef Octree::IDX_TYPE IDX; + typedef PointCloud::Point::Type Real; + typedef TCone Cone; + typedef TSphere Sphere; + typedef TConeIntersect ConeIntersect; + + Cone cone; + const ConeIntersect coneIntersect; + const PointCloud& pointcloud; + IntArr& visibility; + PointCloud::Index idxPoint; + Real distance; + int weight; + #ifdef DENSE_USE_OPENMP + uint8_t pcs[sizeof(CriticalSection)]; + #endif + + Collector(const Cone::RAY& ray, Real angle, const PointCloud& _pointcloud, IntArr& _visibility) + : cone(ray, angle), coneIntersect(cone), pointcloud(_pointcloud), visibility(_visibility) + #ifdef DENSE_USE_OPENMP + { new(pcs) CriticalSection; } + ~Collector() { reinterpret_cast(pcs)->~CriticalSection(); } + inline CriticalSection& GetCS() { return *reinterpret_cast(pcs); } + #else + {} + #endif + inline void Init(PointCloud::Index _idxPoint, const PointCloud::Point& X, int _weight) { + const Real thMaxDepth(1.02f); + idxPoint =_idxPoint; + const PointCloud::Point::EVec D((PointCloud::Point::EVec&)X-cone.ray.m_pOrig); + distance = D.norm(); + cone.ray.m_vDir = D/distance; + cone.maxHeight = MaxDepthDifference(distance, thMaxDepth); + weight = _weight; + } + inline bool Intersects(const Octree::POINT_TYPE& center, Octree::Type radius) const { + return coneIntersect(Sphere(center, radius*Real(SQRT_3))); + } + inline void operator () (const IDX* idices, IDX size) { + const Real thSimilar(0.01f); + Real dist; + FOREACHRAWPTR(pIdx, idices, size) { + const PointCloud::Index idx(*pIdx); + if (coneIntersect.Classify(pointcloud.points[idx], dist) == VISIBLE && !IsDepthSimilar(distance, dist, thSimilar)) { + if (dist > distance) + visibility[idx] += pointcloud.pointViews[idx].size(); + else + visibility[idx] -= weight; + } + } + } + }; + typedef CLISTDEF2(Collector) Collectors; + + // create octree to speed-up search + Octree octree(pointcloud.points); + IntArr visibility(pointcloud.GetSize()); visibility.Memset(0); + Collectors collectors; collectors.reserve(images.size()); + FOREACH(idxView, images) { + const Image& image = images[idxView]; + const Ray3f ray(Cast(image.camera.C), Cast(image.camera.Direction())); + const float angle(float(image.ComputeFOV(0)/image.width)); + collectors.emplace_back(ray, angle, pointcloud, visibility); + } + + // run all camera-point visibility intersections + Util::Progress progress(_T("Point visibility checks"), pointcloud.GetSize()); + #ifdef DENSE_USE_OPENMP + #pragma omp parallel for //schedule(dynamic) + for (int64_t i=0; i<(int64_t)pointcloud.GetSize(); ++i) { + const PointCloud::Index idxPoint((PointCloud::Index)i); + #else + FOREACH(idxPoint, pointcloud.points) { + #endif + const PointCloud::Point& X = pointcloud.points[idxPoint]; + const PointCloud::ViewArr& views = pointcloud.pointViews[idxPoint]; + for (PointCloud::View idxView: views) { + Collector& collector = collectors[idxView]; + #ifdef DENSE_USE_OPENMP + Lock l(collector.GetCS()); + #endif + collector.Init(idxPoint, X, (int)views.size()); + octree.Collect(collector, collector); + } + ++progress; + } + progress.close(); + + #if TD_VERBOSE != TD_VERBOSE_OFF + if (g_nVerbosityLevel > 2) { + // print visibility stats + UnsignedArr counts(0, 64); + for (int views: visibility) { + if (views > 0) + continue; + while (counts.size() <= -views) + counts.push_back(0); + ++counts[-views]; + } + String msg; + msg.reserve(64*counts.size()); + FOREACH(c, counts) + if (counts[c]) + msg += String::FormatString("\n\t% 3u - % 9u", c, counts[c]); + VERBOSE("Visibility lengths (%u points):%s", pointcloud.GetSize(), msg.c_str()); + // save outlier points + PointCloud pc; + RFOREACH(idxPoint, pointcloud.points) { + if (visibility[idxPoint] <= thRemove) { + pc.points.push_back(pointcloud.points[idxPoint]); + pc.colors.push_back(pointcloud.colors[idxPoint]); + } + } + pc.Save(MAKE_PATH("scene_dense_outliers.ply")); + } + #endif + + // filter points + const size_t numInitPoints(pointcloud.GetSize()); + RFOREACH(idxPoint, pointcloud.points) { + if (visibility[idxPoint] <= thRemove) + pointcloud.RemovePoint(idxPoint); + } + + DEBUG_EXTRA("Point-cloud filtered: %u/%u points (%d%%%%) (%s)", pointcloud.points.size(), numInitPoints, ROUND2INT((100.f*pointcloud.points.GetSize())/numInitPoints), TD_TIMER_GET_FMT().c_str()); +} // PointCloudFilter +/*----------------------------------------------------------------*/