├── .gitignore ├── .gitmodules ├── 3rd_party └── CMakeLists.txt ├── CMakeLists.txt ├── LICENSE ├── README.md ├── TODO.md ├── app ├── test_occupancyGrid.cpp ├── test_occupancyGridWithColor.cpp └── test_sdf.cpp ├── cmake └── FindEigen3.cmake ├── data ├── Lucy100k.ply ├── Lucy5k.ply ├── README.md └── crab_full.ply ├── images ├── SDF.gif ├── colored_voxelizer.png └── voxelizer.png ├── include ├── colorPalette.h ├── occupancyGrid.h ├── occupancyGridWithColor.h └── sdf.h └── utils ├── EigenTools ├── getMinMax.h ├── nanoflann.hpp └── nanoflannWrapper.h ├── IO ├── process_folder.h ├── readPLY.h ├── stb_image_write.h ├── structures.h ├── tinyply.cpp ├── tinyply.h ├── writePLY.h └── writePNG.h ├── mesh ├── computeFacesCentroids.h └── computeNormals.h ├── sgn.h └── visualization ├── plotCloud.h ├── plotMesh.h └── plotTwoMeshes.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build 35 | trash 36 | .vscode 37 | data/sdf 38 | data/occupancy_grid 39 | data/*.yaml -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rd_party/polyscope"] 2 | path = 3rd_party/polyscope 3 | url = https://github.com/nmwsharp/polyscope 4 | -------------------------------------------------------------------------------- /3rd_party/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.9) 2 | 3 | ## polyscope (visualization) 4 | add_subdirectory(polyscope) 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(SDF) 4 | 5 | ### automatically download submodules 6 | find_package(Git QUIET) 7 | if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") 8 | # Update submodules as needed 9 | option(GIT_SUBMODULE "Check submodules during build" ON) 10 | if(GIT_SUBMODULE) 11 | message(STATUS "Submodule update") 12 | execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive 13 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 14 | RESULT_VARIABLE GIT_SUBMOD_RESULT) 15 | if(NOT GIT_SUBMOD_RESULT EQUAL "0") 16 | message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") 17 | endif() 18 | endif() 19 | endif() 20 | 21 | if(NOT EXISTS "${PROJECT_SOURCE_DIR}/3rd_party/polyscope/CMakeLists.txt") 22 | message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") 23 | endif() 24 | 25 | add_subdirectory(3rd_party) 26 | include_directories(utils) 27 | include_directories(include) 28 | 29 | set(CMAKE_BUILD_TYPE Release) 30 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 31 | 32 | # specific version of eigen needed 33 | find_package(Eigen3 REQUIRED) 34 | include_directories(${EIGEN3_INCLUDE_DIR}) 35 | 36 | 37 | find_package(OpenMP) 38 | 39 | file(GLOB_RECURSE my_c_list RELATIVE ${CMAKE_SOURCE_DIR} "app/*.cpp") 40 | 41 | foreach(file_path ${my_c_list}) 42 | string( REPLACE ".cpp" "" new_name ${file_path} ) 43 | get_filename_component(filename ${new_name} NAME) 44 | add_executable( ${filename} ${file_path} ) 45 | set_target_properties(${filename} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED YES) 46 | include_directories(${filename} 47 | ${PROJECT_SOURCE_DIR}/3rd_party/ 48 | ${EIGEN3_INCLUDE_DIR} 49 | ) 50 | 51 | target_link_libraries( ${filename} 52 | polyscope 53 | OpenMP::OpenMP_CXX 54 | ) 55 | 56 | endforeach() 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Raphael Falque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Voxelizer and SDF 2 | 3 | This project produces an occupancy 3D grid and a signed distance function (SDF) 3D grid from a mesh. This project uses [polyscope](http://polyscope.run/) for visualization, [tinyply](https://github.com/ddiakopoulos/tinyply) for the mesh IO, [stb](https://github.com/nothings/stb) for images IO, [openMP](https://www.openmp.org/) for parallelization, and [nanoflann](https://github.com/jlblancoc/nanoflann) for kNN search. 4 | 5 | ## Getting Started 6 | 7 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 8 | 9 | ### Prerequisites 10 | The only dependency is [Eigen](https://eigen.tuxfamily.org/), which can be installed as follows: 11 | 12 | ```bash 13 | sudo apt-get update 14 | sudo apt-get install libeigen3-dev 15 | ``` 16 | 17 | ### Installing 18 | 19 | The project can be compiled using the following sequence: 20 | 21 | ```bash 22 | git clone https://github.com/rFalque/voxelization_and_sdf.git 23 | cd voxelization_and_sdf 24 | mkdir build 25 | cd build 26 | cmake ../src 27 | make 28 | ``` 29 | 30 | Additionally, we use OMP for parallelizing the computation. The number of threads can be set with 31 | ```bash 32 | export OMP_NUM_THREADS= 33 | ``` 34 | 35 | ## Running the demo 36 | 37 | To compute the SDF 38 | ```bash 39 | ./test_SDF 40 | ``` 41 | 42 | To run the voxelizer 43 | ```bash 44 | ./test_occupancyGrid 45 | ``` 46 | 47 | To run the colored voxelizer 48 | ```bash 49 | ./test_occupancyGridWithColor 50 | ``` 51 | 52 | ## examples 53 | 54 | SDF computation: 55 | 56 | ![Graph visualization](./images/SDF.gif "Lucy SDF") 57 | 58 | Voxelized Lucy: 59 | 60 | ![Voxelizer](./images/voxelizer.png "Lucy voxelized") 61 | 62 | Color voxelizer: 63 | 64 | ![Voxelizer](./images/colored_voxelizer.png "Lucy voxelized") 65 | 66 | 67 | ## Authors 68 | 69 | * **Raphael Falque** - *Initial work* - [GitHub](https://github.com/rFalque) / [personal page](https://rfalque.github.io/) 70 | 71 | ## License 72 | 73 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 74 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO file 2 | 3 | create a color database (as a yaml) 4 | store the color information into a kdtree 5 | 6 | create a class that: 7 | 8 | init: 9 | read a yaml file 10 | create a matrice n by 3 with the color info 11 | store this matrice into a kd-tree 12 | 13 | return block_id(r,g,b) {closest point in the kdtree} 14 | 15 | print the block info into the yaml file 16 | 17 | -------------------------------------------------------------------------------- /app/test_occupancyGrid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "IO/readPLY.h" 6 | #include "IO/writePLY.h" 7 | 8 | #include "visualization/plotMesh.h" 9 | #include "visualization/plotTwoMeshes.h" 10 | 11 | #include "mesh/computeNormals.h" 12 | #include "mesh/computeFacesCentroids.h" 13 | #include "occupancyGrid.h" 14 | 15 | int main() { 16 | bool visualization = true; 17 | int grid_resolution = 100; // grid_resolution is used to define the grid resolution in the maximum direction 18 | double bounding_box_scale = 1; 19 | 20 | // IO: load files 21 | std::cout << "Progress: load data\n"; 22 | Eigen::MatrixXd V, cubes_V, faces_V; 23 | Eigen::MatrixXi F, cubes_F; 24 | Eigen::MatrixXd N, faces_N; 25 | Eigen::MatrixXi RGB; 26 | 27 | readPLY("../data/Lucy100k.ply", V, F, N, RGB); 28 | 29 | std::cout << "size of V: " << V.rows() << ", " << V.cols() << "\n"; 30 | 31 | faces_V = compute_faces_centroids(V,F); 32 | faces_N = compute_faces_normals(V,F); 33 | 34 | if (visualization) 35 | plot_mesh(V,F); 36 | 37 | OccupancyGrid occupancy_grid(faces_V, faces_N, grid_resolution, bounding_box_scale); 38 | 39 | Eigen::MatrixXd graph_V; 40 | Eigen::MatrixXi graph_E; 41 | occupancy_grid.generate_graph(graph_V, graph_E); 42 | occupancy_grid.print_to_folder("../data/occupancy_grid/"); 43 | occupancy_grid.print_to_yaml("../data/lucy"); 44 | 45 | occupancy_grid.generate_mesh(cubes_V, cubes_F); 46 | 47 | if (visualization) 48 | plot_two_meshes(V,F,cubes_V,cubes_F); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/test_occupancyGridWithColor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "IO/readPLY.h" 6 | #include "IO/writePLY.h" 7 | 8 | #include "visualization/plotMesh.h" 9 | #include "visualization/plotTwoMeshes.h" 10 | #include "visualization/plotCloud.h" 11 | 12 | #include "mesh/computeNormals.h" 13 | #include "mesh/computeFacesCentroids.h" 14 | #include "occupancyGridWithColor.h" 15 | 16 | int main() { 17 | bool visualization = true; 18 | int grid_resolution = 100; // grid_resolution is used to define the grid resolution in the maximum direction 19 | double bounding_box_scale = 1; 20 | 21 | // IO: load files 22 | std::cout << "Progress: load data\n"; 23 | Eigen::MatrixXd V, cubes_V, faces_V; 24 | Eigen::MatrixXi F, cubes_F; 25 | Eigen::MatrixXd N, faces_N; 26 | Eigen::MatrixXi RGB; 27 | 28 | readPLY("../data/crab_full.ply", V, F, N, RGB); 29 | 30 | faces_V = compute_faces_centroids(V,F); 31 | faces_N = compute_faces_normals(V,F); 32 | 33 | Eigen::MatrixXd faces_RGB, cubes_RGB; 34 | Eigen::MatrixXd RGB_double = RGB.cast (); 35 | RGB_double /=256; 36 | 37 | faces_RGB = compute_faces_centroids(RGB_double,F); 38 | 39 | if (visualization) 40 | plot_mesh(V,F, RGB_double); 41 | 42 | if (visualization) 43 | plot_cloud(faces_V, faces_RGB); 44 | 45 | OccupancyGridWithColor occupancy_grid(faces_V, faces_N, faces_RGB, grid_resolution, bounding_box_scale); 46 | 47 | Eigen::MatrixXd graph_V; 48 | Eigen::MatrixXi graph_E; 49 | occupancy_grid.generate_graph(graph_V, graph_E); 50 | occupancy_grid.print_to_folder("../data/occupancy_grid/"); 51 | occupancy_grid.print_to_yaml("../data/crab.yaml"); 52 | 53 | occupancy_grid.generate_mesh(cubes_V, cubes_F, cubes_RGB); 54 | 55 | if (visualization) 56 | plot_mesh(cubes_V,cubes_F, cubes_RGB); 57 | //plot_two_meshes(V,F,cubes_V,cubes_F, cubes_RGB); 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/test_sdf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "IO/readPLY.h" 6 | #include "IO/writePLY.h" 7 | 8 | #include "visualization/plotMesh.h" 9 | #include "visualization/plotTwoMeshes.h" 10 | 11 | #include "mesh/computeNormals.h" 12 | #include "mesh/computeFacesCentroids.h" 13 | #include "sdf.h" 14 | 15 | int main() { 16 | bool visualization = true; 17 | int grid_resolution = 100; 18 | double bounding_box_scale = 1; 19 | 20 | // IO: load files 21 | std::cout << "Progress: load data\n"; 22 | Eigen::MatrixXd V, faces_V; 23 | Eigen::MatrixXi F; 24 | Eigen::MatrixXd N, faces_N; 25 | Eigen::MatrixXi RGB; 26 | 27 | readPLY("../data/Lucy100k.ply", V, F, N, RGB); 28 | 29 | faces_V = compute_faces_centroids(V,F); 30 | faces_N = compute_faces_normals(V,F); 31 | 32 | if (visualization) 33 | plot_mesh(V,F); 34 | 35 | // grid_resolution is used to define the number of grids 36 | 37 | SDF sdf(faces_V, faces_N, grid_resolution, bounding_box_scale); 38 | 39 | Eigen::MatrixXd graph_V; 40 | Eigen::MatrixXi graph_E; 41 | sdf.generate_graph(graph_V, graph_E); 42 | sdf.print_to_folder("../data/sdf/"); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /cmake/FindEigen3.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Eigen3 lib 2 | # 3 | # This module supports requiring a minimum version, e.g. you can do 4 | # find_package(Eigen3 3.1.2) 5 | # to require version 3.1.2 or newer of Eigen3. 6 | # 7 | # Once done this will define 8 | # 9 | # EIGEN3_FOUND - system has eigen lib with correct version 10 | # EIGEN3_INCLUDE_DIR - the eigen include directory 11 | # EIGEN3_VERSION - eigen version 12 | 13 | # Copyright (c) 2006, 2007 Montel Laurent, 14 | # Copyright (c) 2008, 2009 Gael Guennebaud, 15 | # Copyright (c) 2009 Benoit Jacob 16 | # Redistribution and use is allowed according to the terms of the 2-clause BSD license. 17 | 18 | if(NOT Eigen3_FIND_VERSION) 19 | if(NOT Eigen3_FIND_VERSION_MAJOR) 20 | set(Eigen3_FIND_VERSION_MAJOR 2) 21 | endif(NOT Eigen3_FIND_VERSION_MAJOR) 22 | if(NOT Eigen3_FIND_VERSION_MINOR) 23 | set(Eigen3_FIND_VERSION_MINOR 91) 24 | endif(NOT Eigen3_FIND_VERSION_MINOR) 25 | if(NOT Eigen3_FIND_VERSION_PATCH) 26 | set(Eigen3_FIND_VERSION_PATCH 0) 27 | endif(NOT Eigen3_FIND_VERSION_PATCH) 28 | 29 | set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") 30 | endif(NOT Eigen3_FIND_VERSION) 31 | 32 | macro(_eigen3_check_version) 33 | file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) 34 | 35 | string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") 36 | set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") 37 | string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") 38 | set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") 39 | string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") 40 | set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") 41 | 42 | set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) 43 | if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 44 | set(EIGEN3_VERSION_OK FALSE) 45 | else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 46 | set(EIGEN3_VERSION_OK TRUE) 47 | endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 48 | 49 | if(NOT EIGEN3_VERSION_OK) 50 | 51 | message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " 52 | "but at least version ${Eigen3_FIND_VERSION} is required") 53 | endif(NOT EIGEN3_VERSION_OK) 54 | endmacro(_eigen3_check_version) 55 | 56 | if (EIGEN3_INCLUDE_DIR) 57 | 58 | # in cache already 59 | _eigen3_check_version() 60 | set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) 61 | 62 | else (EIGEN3_INCLUDE_DIR) 63 | 64 | find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library 65 | PATHS 66 | ${CMAKE_INSTALL_PREFIX}/include 67 | ${KDE4_INCLUDE_DIR} 68 | PATH_SUFFIXES eigen3 eigen 69 | ) 70 | 71 | if(EIGEN3_INCLUDE_DIR) 72 | _eigen3_check_version() 73 | endif(EIGEN3_INCLUDE_DIR) 74 | 75 | include(FindPackageHandleStandardArgs) 76 | find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) 77 | 78 | mark_as_advanced(EIGEN3_INCLUDE_DIR) 79 | 80 | endif(EIGEN3_INCLUDE_DIR) 81 | 82 | -------------------------------------------------------------------------------- /data/Lucy100k.ply: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFalque/voxelization_and_sdf/6ae111412f2383244b7caf04affd561f64ce9a4f/data/Lucy100k.ply -------------------------------------------------------------------------------- /data/Lucy5k.ply: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFalque/voxelization_and_sdf/6ae111412f2383244b7caf04affd561f64ce9a4f/data/Lucy5k.ply -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Instruction to inspect pictures 2 | (from https://askubuntu.com/a/790411/474398) 3 | 4 | Geeqie is a lightweight image viewer 5 | 6 | ## to instal: 7 | 8 | ```bash 9 | sudo apt-get install geeqie 10 | ``` 11 | 12 | ## aliasing 13 | In order to disable anti-aliasing go to Edit -> Preferences -> Preferences -> Image -> Change zoom quality to nearest and disable two pass rendering. 14 | 15 | In order to show pixel coordinates and RGB value go to View -> check Pixel Info 16 | 17 | ## to turn the yaml file into an nbt: 18 | follow the instructions at: https://pypi.org/project/nbt2yaml/ 19 | 20 | -------------------------------------------------------------------------------- /data/crab_full.ply: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFalque/voxelization_and_sdf/6ae111412f2383244b7caf04affd561f64ce9a4f/data/crab_full.ply -------------------------------------------------------------------------------- /images/SDF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFalque/voxelization_and_sdf/6ae111412f2383244b7caf04affd561f64ce9a4f/images/SDF.gif -------------------------------------------------------------------------------- /images/colored_voxelizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFalque/voxelization_and_sdf/6ae111412f2383244b7caf04affd561f64ce9a4f/images/colored_voxelizer.png -------------------------------------------------------------------------------- /images/voxelizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFalque/voxelization_and_sdf/6ae111412f2383244b7caf04affd561f64ce9a4f/images/voxelizer.png -------------------------------------------------------------------------------- /include/colorPalette.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: R. Falque 3 | * 4 | * create a color palette for minecraft export: https://minecraft.gamepedia.com/Map_item_format 5 | * by R. Falque 6 | * 26/09/2019 7 | **/ 8 | 9 | #ifndef COLOR_PALETTE_H 10 | #define COLOR_PALETTE_H 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "EigenTools/nanoflannWrapper.h" 21 | 22 | // polyscope wrapper 23 | class ColorPalette 24 | { 25 | private: 26 | Eigen::MatrixXd available_colors_; 27 | nanoflann_wrapper *color_kdtree; 28 | 29 | public: 30 | 31 | ColorPalette() 32 | { 33 | init(); 34 | } 35 | 36 | // destructor 37 | ~ColorPalette() 38 | { 39 | } 40 | 41 | void init() { 42 | 43 | // store variables in private variables 44 | available_colors_.resize(3,16); 45 | available_colors_.col( 0) << 255, 255, 255; 46 | available_colors_.col( 1) << 216, 127, 51; 47 | available_colors_.col( 2) << 178, 76, 216; 48 | available_colors_.col( 3) << 102, 153, 216; 49 | available_colors_.col( 4) << 229, 229, 51; 50 | available_colors_.col( 5) << 127, 204, 25; 51 | available_colors_.col( 6) << 242, 127, 165; 52 | available_colors_.col( 7) << 76, 76, 76; 53 | available_colors_.col( 8) << 153, 153, 153; 54 | available_colors_.col( 9) << 76, 127, 153; 55 | available_colors_.col(10) << 127, 63, 178; 56 | available_colors_.col(11) << 51, 76, 178; 57 | available_colors_.col(12) << 102, 76, 51; 58 | available_colors_.col(13) << 102, 127, 51; 59 | available_colors_.col(14) << 153, 51, 51; 60 | available_colors_.col(15) << 25, 25, 25; 61 | color_kdtree = new nanoflann_wrapper(available_colors_); 62 | 63 | } 64 | 65 | int get_closest_color_id(Eigen::Vector3d queried_color) { 66 | std::vector color_id; 67 | color_id = color_kdtree->return_k_closest_points(queried_color, 1); 68 | return color_id[0]; 69 | } 70 | 71 | // print the occupancy grid into a yaml file 72 | inline bool print_palette(std::ofstream & out_file) { 73 | 74 | out_file << " - palette: !list_compound\n"; 75 | out_file << " - - Name: minecraft:white_concrete\n"; 76 | out_file << " - - Name: minecraft:orange_concrete\n"; 77 | out_file << " - - Name: minecraft:magenta_concrete\n"; 78 | out_file << " - - Name: minecraft:light_blue_concrete\n"; 79 | out_file << " - - Name: minecraft:yellow_concrete\n"; 80 | out_file << " - - Name: minecraft:lime_concrete\n"; 81 | out_file << " - - Name: minecraft:pink_concrete\n"; 82 | out_file << " - - Name: minecraft:gray_concrete\n"; 83 | out_file << " - - Name: minecraft:light_gray_concrete\n"; 84 | out_file << " - - Name: minecraft:cyan_concrete\n"; 85 | out_file << " - - Name: minecraft:purple_concrete\n"; 86 | out_file << " - - Name: minecraft:blue_concrete\n"; 87 | out_file << " - - Name: minecraft:brown_concrete\n"; 88 | out_file << " - - Name: minecraft:green_concrete\n"; 89 | out_file << " - - Name: minecraft:red_concrete\n"; 90 | out_file << " - - Name: minecraft:black_concrete\n"; 91 | 92 | return true; 93 | } 94 | }; 95 | 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /include/occupancyGrid.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generate 3D occupancy grid from a 3D closed mesh 3 | * by R. Falque 4 | * 07/02/2020 5 | */ 6 | 7 | #ifndef OCCUPANCY_GRID_H 8 | #define OCCUPANCY_GRID_H 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "EigenTools/getMinMax.h" 20 | #include "EigenTools/nanoflannWrapper.h" 21 | #include "sgn.h" 22 | #include "IO/writePNG.h" 23 | #include "IO/process_folder.h" 24 | 25 | // polyscope wrapper 26 | class OccupancyGrid 27 | { 28 | private: 29 | Eigen::MatrixXd vertices_; 30 | Eigen::MatrixXd normals_; 31 | int grid_resolution_; 32 | double bounding_box_scale_; 33 | Eigen::Tensor occupancy_grid_; 34 | double grid_size_; 35 | Eigen::Vector3d source_; 36 | 37 | public: 38 | 39 | OccupancyGrid(Eigen::MatrixXd & vertices, Eigen::MatrixXd & normals, int grid_resolution, double bounding_box_scale) 40 | { 41 | // store variables in private variables 42 | vertices_ = vertices; 43 | normals_ = normals; 44 | grid_resolution_ = grid_resolution; 45 | bounding_box_scale_ = bounding_box_scale; 46 | 47 | // create the occupancy grid 48 | init(); 49 | } 50 | 51 | // destructor 52 | ~OccupancyGrid() 53 | { 54 | } 55 | 56 | //accessors 57 | inline Eigen::Tensor get_occupancy_grid(){return occupancy_grid_;}; 58 | inline double get_grid_size(){return grid_size_;}; 59 | inline Eigen::Vector3d get_source(){return source_;}; 60 | 61 | // Class functions 62 | 63 | // create the occupancy grid 64 | void init() { 65 | Eigen::Vector3d min_point, max_point; 66 | getMinMax(vertices_, min_point, max_point); 67 | 68 | //double bounding_box_size = (max_point - min_point).norm() * bounding_box_scale; 69 | double bounding_box_size = (max_point - min_point).maxCoeff() * bounding_box_scale_; // diagonal versus max direction 70 | double leaf_size = bounding_box_size/(grid_resolution_-1); 71 | double inv_leaf_size = 1.0/leaf_size; 72 | 73 | Eigen::Vector3i min_box, max_box, number_of_bins; 74 | min_box << floor(min_point(0)*inv_leaf_size), floor(min_point(1)*inv_leaf_size) , floor(min_point(2)*inv_leaf_size); 75 | max_box << floor(max_point(0)*inv_leaf_size), floor(max_point(1)*inv_leaf_size) , floor(max_point(2)*inv_leaf_size); 76 | number_of_bins << max_box(0) - min_box(0) + 1, max_box(1) - min_box(1) + 1, max_box(2) - min_box(2) + 1; 77 | 78 | occupancy_grid_.resize(number_of_bins(0), number_of_bins(1), number_of_bins(2)); 79 | 80 | nanoflann_wrapper tree(vertices_); 81 | for (int x = 0; x < number_of_bins(0); ++x) 82 | for (int y = 0; y < number_of_bins(1); ++y) 83 | { 84 | #pragma omp parallel for 85 | for (int z = 0; z < number_of_bins(2); ++z) 86 | { 87 | std::vector< int > closest_point; 88 | Eigen::Vector3d point; 89 | point << x, y, z; 90 | point *= leaf_size; 91 | point += min_point; 92 | closest_point = tree.return_k_closest_points(point, 1); 93 | 94 | /* produce the outer shell only remove the next line 95 | if ( (point - vertices.row(closest_point[0]).transpose()).norm() < leaf_size(0)*2 ) 96 | grid(x, y, z) = true; 97 | else 98 | grid(x, y, z) = false; 99 | */ 100 | // here is the key function 101 | occupancy_grid_(x, y, z) = is_positive( ( vertices_.col(closest_point[0]) - point ).dot( normals_.col(closest_point[0]) ) ); 102 | } 103 | } 104 | 105 | grid_size_ = leaf_size; 106 | source_ = min_point; 107 | } 108 | 109 | // build a graph from the occupied space (there is no garanty of connectivity) 110 | inline bool generate_graph(Eigen::MatrixXd & vertices, Eigen::MatrixXi & edges) { 111 | 112 | std::vector< Eigen::Vector3d > vertices_vector; 113 | std::vector< Eigen::Vector2i > edges_vector; 114 | 115 | Eigen::Vector3d centroid; 116 | Eigen::Tensor grid_indices(occupancy_grid_.dimension(0), occupancy_grid_.dimension(1), occupancy_grid_.dimension(2)); 117 | 118 | // build vertices 119 | for (int x = 0; x < occupancy_grid_.dimension(0); ++x) 120 | for (int y = 0; y < occupancy_grid_.dimension(1); ++y) 121 | for (int z = 0; z < occupancy_grid_.dimension(2); ++z) { 122 | grid_indices(x, y, z) = -1; 123 | if (occupancy_grid_(x, y, z) == 1) { 124 | centroid << x, y, z; 125 | centroid *= grid_size_; 126 | centroid += source_; 127 | vertices_vector.push_back(centroid); 128 | grid_indices(x, y, z) = vertices_vector.size(); 129 | } 130 | } 131 | 132 | // build edges 133 | for (int x = 0; x < occupancy_grid_.dimension(0); ++x) 134 | for (int y = 0; y < occupancy_grid_.dimension(1); ++y) 135 | for (int z = 0; z < occupancy_grid_.dimension(2); ++z) 136 | if (occupancy_grid_(x, y, z) == 1) { 137 | // case on x 138 | if (x-1>=0) 139 | if (occupancy_grid_(x-1, y, z)==1) { 140 | Eigen::Vector2i edge_temp; 141 | edge_temp << grid_indices(x, y, z), grid_indices(x-1, y, z); 142 | edges_vector.push_back(edge_temp); 143 | } 144 | if (x+1=0) 153 | if (occupancy_grid_(x, y, z-1)==1) { 154 | Eigen::Vector2i edge_temp; 155 | edge_temp << grid_indices(x, y, z), grid_indices(x, y-1, z); 156 | edges_vector.push_back(edge_temp); 157 | } 158 | if (y+1=0) 167 | if (occupancy_grid_(x, y, z-1)==1) { 168 | Eigen::Vector2i edge_temp; 169 | edge_temp << grid_indices(x, y, z), grid_indices(x, y, z-1); 170 | edges_vector.push_back(edge_temp); 171 | } 172 | if (z+1 slice; 206 | Eigen::Tensor tensor_slice; 207 | 208 | Eigen::array offset = {0,0,i}; //Starting point 209 | Eigen::array extent = {occupancy_grid_.dimension(0),occupancy_grid_.dimension(1),0}; //Finish point 210 | tensor_slice = occupancy_grid_.slice(offset, extent).reshape(Eigen::array{occupancy_grid_.dimension(0),occupancy_grid_.dimension(1)}); 211 | slice = Eigen::Map> (tensor_slice.data(), tensor_slice.dimension(0),tensor_slice.dimension(1)); 212 | 213 | std::stringstream ss; 214 | ss << std::setw(3) << std::setfill('0') << i; 215 | std::string s = ss.str(); 216 | 217 | std::string file_name = folder_name + s + ".png"; 218 | writePNG(slice, file_name); 219 | } 220 | 221 | std::cout << "Progress: Stack of images written in :" << folder_name << std::endl; 222 | 223 | return true; 224 | }; 225 | 226 | // print the occupancy grid into a yaml file 227 | inline bool print_to_yaml(std::string filename) { 228 | 229 | std::string chunk_name = filename + ".yaml"; 230 | std::ofstream out_file(chunk_name); 231 | 232 | out_file << "? ''\n"; 233 | out_file << ": - size: !list_int\n"; 234 | out_file << " - " + std::to_string(32) + "\n"; 235 | out_file << " - " + std::to_string(32) + "\n"; 236 | out_file << " - " + std::to_string(32) + "\n"; 237 | out_file << " - entities: !list_end []\n"; 238 | out_file << " - blocks: !list_compound\n"; 239 | for (int x = 0; x < occupancy_grid_.dimension(0); ++x) 240 | for (int y = 0; y < occupancy_grid_.dimension(1); ++y) 241 | for (int z = 0; z < occupancy_grid_.dimension(2); ++z) 242 | { 243 | if (occupancy_grid_(x, y, z) == 1) { 244 | out_file << " - - pos: !list_int\n"; 245 | out_file << " - " + std::to_string(x-round(occupancy_grid_.dimension(0)/2)) + "\n"; 246 | out_file << " - " + std::to_string(z) + "\n"; 247 | out_file << " - " + std::to_string(-y+round(occupancy_grid_.dimension(1)/2)) + "\n"; 248 | out_file << " - state: 0\n"; 249 | } 250 | 251 | } 252 | out_file << " - author: rFalque\n"; 253 | out_file << " - palette: !list_compound\n"; 254 | out_file << " - - Properties:\n"; 255 | out_file << " - variant: smooth_andesite\n"; 256 | out_file << " - Name: minecraft:stone\n"; 257 | out_file << " - DataVersion: 2227\n"; 258 | 259 | out_file.close(); 260 | 261 | return true; 262 | }; 263 | 264 | inline bool generate_mesh(Eigen::MatrixXd & vertices, Eigen::MatrixXi & faces) { 265 | std::vector< Eigen::Vector3d > vertices_vector; 266 | std::vector< Eigen::Vector3i > faces_vector; 267 | 268 | Eigen::Vector3d centroid; 269 | for (int x = 0; x < occupancy_grid_.dimension(0); ++x) 270 | for (int y = 0; y < occupancy_grid_.dimension(1); ++y) 271 | for (int z = 0; z < occupancy_grid_.dimension(2); ++z) 272 | { 273 | if (occupancy_grid_(x, y, z) == 1) { 274 | centroid << x, y, z; 275 | centroid *= grid_size_; 276 | centroid += source_; 277 | 278 | make_cube(centroid, grid_size_, vertices_vector, faces_vector); 279 | } 280 | } 281 | 282 | vertices.resize(3, vertices_vector.size()); 283 | for (int i=0; i< vertices_vector.size(); i++) 284 | vertices.col(i) = vertices_vector[i]; 285 | 286 | faces.resize(3, faces_vector.size()); 287 | for (int i=0; i< faces_vector.size(); i++) 288 | faces.col(i) = faces_vector[i]; 289 | 290 | return true; 291 | }; 292 | 293 | inline bool make_cube(Eigen::Vector3d centroid, double size, std::vector< Eigen::Vector3d > & vertices_vector, std::vector< Eigen::Vector3i > & faces_vector) { 294 | 295 | Eigen::Vector3i vertices_offset = Eigen::Vector3i::Constant(vertices_vector.size()); 296 | 297 | Eigen::Vector3d v_0 ( 0.5, 0.5, 0.5); 298 | Eigen::Vector3d v_1 ( 0.5, 0.5,-0.5); 299 | Eigen::Vector3d v_2 ( 0.5,-0.5, 0.5); 300 | Eigen::Vector3d v_3 ( 0.5,-0.5,-0.5); 301 | Eigen::Vector3d v_4 (-0.5, 0.5, 0.5); 302 | Eigen::Vector3d v_5 (-0.5, 0.5,-0.5); 303 | Eigen::Vector3d v_6 (-0.5,-0.5, 0.5); 304 | Eigen::Vector3d v_7 (-0.5,-0.5,-0.5); 305 | 306 | vertices_vector.push_back( v_0*size + centroid ); 307 | vertices_vector.push_back( v_1*size + centroid ); 308 | vertices_vector.push_back( v_2*size + centroid ); 309 | vertices_vector.push_back( v_3*size + centroid ); 310 | vertices_vector.push_back( v_4*size + centroid ); 311 | vertices_vector.push_back( v_5*size + centroid ); 312 | vertices_vector.push_back( v_6*size + centroid ); 313 | vertices_vector.push_back( v_7*size + centroid ); 314 | 315 | Eigen::Vector3i f_0 (0, 3, 1); 316 | Eigen::Vector3i f_1 (0, 2, 3); 317 | Eigen::Vector3i f_2 (0, 1, 5); 318 | Eigen::Vector3i f_3 (0, 5, 4); 319 | Eigen::Vector3i f_4 (4, 5, 7); 320 | Eigen::Vector3i f_5 (4, 7, 6); 321 | Eigen::Vector3i f_6 (2, 6, 7); 322 | Eigen::Vector3i f_7 (2, 7, 3); 323 | Eigen::Vector3i f_8 (1, 3, 7); 324 | Eigen::Vector3i f_9 (1, 7, 5); 325 | Eigen::Vector3i f_10(0, 4, 2); 326 | Eigen::Vector3i f_11(2, 4, 6); 327 | 328 | faces_vector.push_back(f_0 + vertices_offset); 329 | faces_vector.push_back(f_1 + vertices_offset); 330 | faces_vector.push_back(f_2 + vertices_offset); 331 | faces_vector.push_back(f_3 + vertices_offset); 332 | faces_vector.push_back(f_4 + vertices_offset); 333 | faces_vector.push_back(f_5 + vertices_offset); 334 | faces_vector.push_back(f_6 + vertices_offset); 335 | faces_vector.push_back(f_7 + vertices_offset); 336 | faces_vector.push_back(f_8 + vertices_offset); 337 | faces_vector.push_back(f_9 + vertices_offset); 338 | faces_vector.push_back(f_10 + vertices_offset); 339 | faces_vector.push_back(f_11 + vertices_offset); 340 | 341 | return true; 342 | }; 343 | 344 | }; 345 | 346 | #endif -------------------------------------------------------------------------------- /include/occupancyGridWithColor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generate 3D occupancy grid from a 3D closed mesh 3 | * by R. Falque 4 | * 07/02/2020 5 | */ 6 | 7 | #ifndef OCCUPANCY_GRID_H 8 | #define OCCUPANCY_GRID_H 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "EigenTools/getMinMax.h" 21 | #include "EigenTools/nanoflannWrapper.h" 22 | #include "sgn.h" 23 | #include "IO/writePNG.h" 24 | #include "IO/process_folder.h" 25 | #include "colorPalette.h" 26 | 27 | // polyscope wrapper 28 | class OccupancyGridWithColor 29 | { 30 | private: 31 | Eigen::MatrixXd vertices_; 32 | Eigen::MatrixXd normals_; 33 | Eigen::MatrixXd RGB_; 34 | int grid_resolution_; 35 | double bounding_box_scale_; 36 | Eigen::Tensor occupancy_grid_; 37 | Eigen::Tensor R_; 38 | Eigen::Tensor G_; 39 | Eigen::Tensor B_; 40 | double grid_size_; 41 | Eigen::Vector3d source_; 42 | 43 | public: 44 | 45 | OccupancyGridWithColor(Eigen::MatrixXd & vertices, Eigen::MatrixXd & normals, int grid_resolution, double bounding_box_scale) 46 | { 47 | // store variables in private variables 48 | vertices_ = vertices; 49 | normals_ = normals; 50 | grid_resolution_ = grid_resolution; 51 | bounding_box_scale_ = bounding_box_scale; 52 | 53 | // create the occupancy grid 54 | init(); 55 | } 56 | 57 | OccupancyGridWithColor(Eigen::MatrixXd & vertices, Eigen::MatrixXd & normals, Eigen::MatrixXd & RGB, int grid_resolution, double bounding_box_scale) 58 | { 59 | // store variables in private variables 60 | vertices_ = vertices; 61 | normals_ = normals; 62 | RGB_ = RGB; 63 | grid_resolution_ = grid_resolution; 64 | bounding_box_scale_ = bounding_box_scale; 65 | 66 | // create the occupancy grid 67 | init(); 68 | } 69 | 70 | // destructor 71 | ~OccupancyGridWithColor() 72 | { 73 | } 74 | 75 | //accessors 76 | inline Eigen::Tensor get_occupancy_grid(){return occupancy_grid_;}; 77 | inline double get_grid_size(){return grid_size_;}; 78 | inline Eigen::Vector3d get_source(){return source_;}; 79 | 80 | // Class functions 81 | 82 | // create the occupancy grid 83 | void init() { 84 | Eigen::Vector3d min_point, max_point; 85 | getMinMax(vertices_, min_point, max_point); 86 | 87 | //double bounding_box_size = (max_point - min_point).norm() * bounding_box_scale; 88 | double bounding_box_size = (max_point - min_point).maxCoeff() * bounding_box_scale_; // diagonal versus max direction 89 | double leaf_size = bounding_box_size/(grid_resolution_-1); 90 | double inv_leaf_size = 1.0/leaf_size; 91 | 92 | Eigen::Vector3i min_box, max_box, number_of_bins; 93 | min_box << floor(min_point(0)*inv_leaf_size), floor(min_point(1)*inv_leaf_size) , floor(min_point(2)*inv_leaf_size); 94 | max_box << floor(max_point(0)*inv_leaf_size), floor(max_point(1)*inv_leaf_size) , floor(max_point(2)*inv_leaf_size); 95 | number_of_bins << max_box(0) - min_box(0) + 1, max_box(1) - min_box(1) + 1, max_box(2) - min_box(2) + 1; 96 | 97 | occupancy_grid_.resize(number_of_bins(0), number_of_bins(1), number_of_bins(2)); 98 | R_.resize(number_of_bins(0), number_of_bins(1), number_of_bins(2)); 99 | G_.resize(number_of_bins(0), number_of_bins(1), number_of_bins(2)); 100 | B_.resize(number_of_bins(0), number_of_bins(1), number_of_bins(2)); 101 | 102 | nanoflann_wrapper tree(vertices_); 103 | for (int x = 0; x < number_of_bins(0); ++x) 104 | for (int y = 0; y < number_of_bins(1); ++y) 105 | { 106 | #pragma omp parallel for 107 | for (int z = 0; z < number_of_bins(2); ++z) 108 | { 109 | std::vector< int > closest_point; 110 | Eigen::Vector3d point; 111 | point << x, y, z; 112 | point *= leaf_size; 113 | point += min_point; 114 | closest_point = tree.return_k_closest_points(point, 1); 115 | 116 | /* produce the outer shell only remove the next line 117 | if ( (point - vertices.row(closest_point[0]).transpose()).norm() < leaf_size(0)*2 ) 118 | grid(x, y, z) = true; 119 | else 120 | grid(x, y, z) = false; 121 | */ 122 | // here is the key function 123 | occupancy_grid_(x, y, z) = is_positive( ( vertices_.col(closest_point[0]) - point ).dot( normals_.col(closest_point[0]) ) ); 124 | R_(x, y, z) = RGB_(0, closest_point[0]); 125 | G_(x, y, z) = RGB_(1, closest_point[0]); 126 | B_(x, y, z) = RGB_(2, closest_point[0]); 127 | } 128 | } 129 | 130 | grid_size_ = leaf_size; 131 | source_ = min_point; 132 | } 133 | 134 | // build a graph from the occupied space (there is no garanty of connectivity) 135 | inline bool generate_graph(Eigen::MatrixXd & vertices, Eigen::MatrixXi & edges) { 136 | 137 | std::vector< Eigen::Vector3d > vertices_vector; 138 | std::vector< Eigen::Vector2i > edges_vector; 139 | 140 | Eigen::Vector3d centroid; 141 | Eigen::Tensor grid_indices(occupancy_grid_.dimension(0), occupancy_grid_.dimension(1), occupancy_grid_.dimension(2)); 142 | 143 | // build vertices 144 | for (int x = 0; x < occupancy_grid_.dimension(0); ++x) 145 | for (int y = 0; y < occupancy_grid_.dimension(1); ++y) 146 | for (int z = 0; z < occupancy_grid_.dimension(2); ++z) { 147 | grid_indices(x, y, z) = -1; 148 | if (occupancy_grid_(x, y, z) == 1) { 149 | centroid << x, y, z; 150 | centroid *= grid_size_; 151 | centroid += source_; 152 | vertices_vector.push_back(centroid); 153 | grid_indices(x, y, z) = vertices_vector.size(); 154 | } 155 | } 156 | 157 | // build edges 158 | for (int x = 0; x < occupancy_grid_.dimension(0); ++x) 159 | for (int y = 0; y < occupancy_grid_.dimension(1); ++y) 160 | for (int z = 0; z < occupancy_grid_.dimension(2); ++z) 161 | if (occupancy_grid_(x, y, z) == 1) { 162 | // case on x 163 | if (x-1>=0) 164 | if (occupancy_grid_(x-1, y, z)==1) { 165 | Eigen::Vector2i edge_temp; 166 | edge_temp << grid_indices(x, y, z), grid_indices(x-1, y, z); 167 | edges_vector.push_back(edge_temp); 168 | } 169 | if (x+1=0) 178 | if (occupancy_grid_(x, y, z-1)==1) { 179 | Eigen::Vector2i edge_temp; 180 | edge_temp << grid_indices(x, y, z), grid_indices(x, y-1, z); 181 | edges_vector.push_back(edge_temp); 182 | } 183 | if (y+1=0) 192 | if (occupancy_grid_(x, y, z-1)==1) { 193 | Eigen::Vector2i edge_temp; 194 | edge_temp << grid_indices(x, y, z), grid_indices(x, y, z-1); 195 | edges_vector.push_back(edge_temp); 196 | } 197 | if (z+1 slice; 231 | Eigen::Tensor tensor_slice; 232 | 233 | Eigen::array offset = {0,0,i}; //Starting point 234 | Eigen::array extent = {occupancy_grid_.dimension(0),occupancy_grid_.dimension(1),0}; //Finish point 235 | tensor_slice = occupancy_grid_.slice(offset, extent).reshape(Eigen::array{occupancy_grid_.dimension(0),occupancy_grid_.dimension(1)}); 236 | slice = Eigen::Map> (tensor_slice.data(), tensor_slice.dimension(0),tensor_slice.dimension(1)); 237 | 238 | std::stringstream ss; 239 | ss << std::setw(3) << std::setfill('0') << i; 240 | std::string s = ss.str(); 241 | 242 | std::string file_name = folder_name + s + ".png"; 243 | writePNG(slice, file_name); 244 | } 245 | 246 | std::cout << "Progress: Stack of images written in :" << folder_name << std::endl; 247 | 248 | return true; 249 | }; 250 | 251 | 252 | // print the occupancy grid into a yaml file 253 | inline bool print_to_yaml(std::string filename) { 254 | 255 | std::ofstream out_file(filename); 256 | ColorPalette color_palette; 257 | Eigen::Vector3d voxel_color; 258 | 259 | 260 | out_file << "? ''\n"; 261 | out_file << ": - size: !list_int\n"; 262 | out_file << " - " + std::to_string(32) + "\n"; 263 | out_file << " - " + std::to_string(32) + "\n"; 264 | out_file << " - " + std::to_string(32) + "\n"; 265 | out_file << " - entities: !list_end []\n"; 266 | out_file << " - blocks: !list_compound\n"; 267 | for (int x = 0; x < occupancy_grid_.dimension(0); ++x) 268 | for (int y = 0; y < occupancy_grid_.dimension(1); ++y) 269 | for (int z = 0; z < occupancy_grid_.dimension(2); ++z) 270 | { 271 | if (occupancy_grid_(x, y, z) == 1) { 272 | out_file << " - - pos: !list_int\n"; 273 | out_file << " - " + std::to_string(x-round(occupancy_grid_.dimension(0)/2)) + "\n"; 274 | out_file << " - " + std::to_string(z) + "\n"; 275 | out_file << " - " + std::to_string(-y+round(occupancy_grid_.dimension(1)/2)) + "\n"; 276 | voxel_color << R_(x, y, z)*255, G_(x, y, z)*255, B_(x, y, z)*255; 277 | out_file << " - state: " + std::to_string(color_palette.get_closest_color_id(voxel_color)) + "\n"; 278 | } 279 | 280 | } 281 | out_file << " - author: rFalque\n"; 282 | 283 | color_palette.print_palette(out_file); 284 | 285 | out_file << " - DataVersion: 2227\n"; 286 | 287 | out_file.close(); 288 | 289 | return true; 290 | }; 291 | 292 | 293 | inline bool generate_mesh(Eigen::MatrixXd & vertices, Eigen::MatrixXi & faces, Eigen::MatrixXd & colors) { 294 | std::vector< Eigen::Vector3d > vertices_vector; 295 | std::vector< Eigen::Vector3i > faces_vector; 296 | std::vector< Eigen::Vector3d > color_vector; 297 | 298 | Eigen::Vector3d centroid, color; 299 | for (int x = 0; x < occupancy_grid_.dimension(0); ++x) 300 | for (int y = 0; y < occupancy_grid_.dimension(1); ++y) 301 | for (int z = 0; z < occupancy_grid_.dimension(2); ++z) 302 | { 303 | if (occupancy_grid_(x, y, z) == 1) { 304 | centroid << x, y, z; 305 | centroid *= grid_size_; 306 | centroid += source_; 307 | color << R_(x,y,z), G_(x,y,z), B_(x,y,z); 308 | 309 | make_cube(centroid, color, grid_size_, vertices_vector, faces_vector, color_vector); 310 | } 311 | } 312 | 313 | vertices.resize(3, vertices_vector.size()); 314 | for (int i=0; i< vertices_vector.size(); i++) 315 | vertices.col(i) = vertices_vector[i]; 316 | 317 | colors.resize(3, color_vector.size()); 318 | for (int i=0; i< color_vector.size(); i++) 319 | colors.col(i) = color_vector[i]; 320 | 321 | faces.resize(3, faces_vector.size()); 322 | for (int i=0; i< faces_vector.size(); i++) 323 | faces.col(i) = faces_vector[i]; 324 | 325 | return true; 326 | }; 327 | 328 | inline bool make_cube( 329 | Eigen::Vector3d centroid, 330 | Eigen::Vector3d color, 331 | double size, 332 | std::vector< Eigen::Vector3d > & vertices_vector, 333 | std::vector< Eigen::Vector3i > & faces_vector, 334 | std::vector< Eigen::Vector3d > & color_vector 335 | ) { 336 | 337 | Eigen::Vector3i vertices_offset = Eigen::Vector3i::Constant(vertices_vector.size()); 338 | 339 | Eigen::Vector3d v_0 ( 0.5, 0.5, 0.5); 340 | Eigen::Vector3d v_1 ( 0.5, 0.5,-0.5); 341 | Eigen::Vector3d v_2 ( 0.5,-0.5, 0.5); 342 | Eigen::Vector3d v_3 ( 0.5,-0.5,-0.5); 343 | Eigen::Vector3d v_4 (-0.5, 0.5, 0.5); 344 | Eigen::Vector3d v_5 (-0.5, 0.5,-0.5); 345 | Eigen::Vector3d v_6 (-0.5,-0.5, 0.5); 346 | Eigen::Vector3d v_7 (-0.5,-0.5,-0.5); 347 | 348 | vertices_vector.push_back( v_0*size + centroid ); 349 | vertices_vector.push_back( v_1*size + centroid ); 350 | vertices_vector.push_back( v_2*size + centroid ); 351 | vertices_vector.push_back( v_3*size + centroid ); 352 | vertices_vector.push_back( v_4*size + centroid ); 353 | vertices_vector.push_back( v_5*size + centroid ); 354 | vertices_vector.push_back( v_6*size + centroid ); 355 | vertices_vector.push_back( v_7*size + centroid ); 356 | 357 | for (int i=0; i<8; i++) 358 | color_vector.push_back(color); 359 | 360 | Eigen::Vector3i f_0 (0, 3, 1); 361 | Eigen::Vector3i f_1 (0, 2, 3); 362 | Eigen::Vector3i f_2 (0, 1, 5); 363 | Eigen::Vector3i f_3 (0, 5, 4); 364 | Eigen::Vector3i f_4 (4, 5, 7); 365 | Eigen::Vector3i f_5 (4, 7, 6); 366 | Eigen::Vector3i f_6 (2, 6, 7); 367 | Eigen::Vector3i f_7 (2, 7, 3); 368 | Eigen::Vector3i f_8 (1, 3, 7); 369 | Eigen::Vector3i f_9 (1, 7, 5); 370 | Eigen::Vector3i f_10(0, 4, 2); 371 | Eigen::Vector3i f_11(2, 4, 6); 372 | 373 | faces_vector.push_back(f_0 + vertices_offset); 374 | faces_vector.push_back(f_1 + vertices_offset); 375 | faces_vector.push_back(f_2 + vertices_offset); 376 | faces_vector.push_back(f_3 + vertices_offset); 377 | faces_vector.push_back(f_4 + vertices_offset); 378 | faces_vector.push_back(f_5 + vertices_offset); 379 | faces_vector.push_back(f_6 + vertices_offset); 380 | faces_vector.push_back(f_7 + vertices_offset); 381 | faces_vector.push_back(f_8 + vertices_offset); 382 | faces_vector.push_back(f_9 + vertices_offset); 383 | faces_vector.push_back(f_10 + vertices_offset); 384 | faces_vector.push_back(f_11 + vertices_offset); 385 | 386 | return true; 387 | }; 388 | 389 | }; 390 | 391 | #endif -------------------------------------------------------------------------------- /include/sdf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generate SDF 3 | * by R. Falque 4 | * 11/02/2020 5 | */ 6 | 7 | #ifndef SDF_H 8 | #define SDF_H 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "EigenTools/getMinMax.h" 19 | #include "EigenTools/nanoflannWrapper.h" 20 | #include "sgn.h" 21 | #include "IO/writePNG.h" 22 | #include "IO/process_folder.h" 23 | 24 | // polyscope wrapper 25 | class SDF 26 | { 27 | private: 28 | Eigen::MatrixXd vertices_; 29 | Eigen::MatrixXd normals_; 30 | int grid_resolution_; 31 | double bounding_box_scale_; 32 | Eigen::Tensor SDF_; 33 | double grid_size_; 34 | Eigen::Vector3d source_; 35 | 36 | public: 37 | 38 | SDF(Eigen::MatrixXd & vertices, Eigen::MatrixXd & normals, int grid_resolution, double bounding_box_scale) 39 | { 40 | vertices_ = vertices; 41 | normals_ = normals; 42 | grid_resolution_ = grid_resolution; 43 | bounding_box_scale_ = bounding_box_scale; 44 | 45 | init(); 46 | } 47 | 48 | // destructor 49 | ~SDF() 50 | { 51 | } 52 | 53 | //accessors 54 | inline Eigen::Tensor get_SDF(){return SDF_;}; 55 | inline double get_grid_size(){return grid_size_;}; 56 | inline Eigen::Vector3d get_source(){return source_;}; 57 | 58 | void init() { 59 | Eigen::Vector3d min_point, max_point; 60 | getMinMax(vertices_, min_point, max_point); 61 | 62 | //double bounding_box_size = (max_point - min_point).norm() * bounding_box_scale; 63 | double bounding_box_size = (max_point - min_point).maxCoeff() * bounding_box_scale_; // diagonal versus max direction 64 | double leaf_size = bounding_box_size/(grid_resolution_-1); 65 | double inv_leaf_size = 1.0/leaf_size; 66 | 67 | Eigen::Vector3i min_box, max_box, number_of_bins; 68 | min_box << floor(min_point(0)*inv_leaf_size), floor(min_point(1)*inv_leaf_size) , floor(min_point(2)*inv_leaf_size); 69 | max_box << floor(max_point(0)*inv_leaf_size), floor(max_point(1)*inv_leaf_size) , floor(max_point(2)*inv_leaf_size); 70 | number_of_bins << max_box(0) - min_box(0) + 1, max_box(1) - min_box(1) + 1, max_box(2) - min_box(2) + 1; 71 | 72 | SDF_.resize(number_of_bins(0), number_of_bins(1), number_of_bins(2)); 73 | 74 | nanoflann_wrapper tree(vertices_); 75 | for (int x = 0; x < number_of_bins(0); ++x) 76 | for (int y = 0; y < number_of_bins(1); ++y) 77 | { 78 | #pragma omp parallel for 79 | for (int z = 0; z < number_of_bins(2); ++z) 80 | { 81 | std::vector< int > closest_point; 82 | Eigen::Vector3d point; 83 | point << x, y, z; 84 | point *= leaf_size; 85 | point += min_point; 86 | closest_point = tree.return_k_closest_points(point, 1); 87 | double sign = ( vertices_.col(closest_point[0]) - point ).dot( normals_.col(closest_point[0]) ); 88 | sign /= abs(sign); 89 | 90 | SDF_(x, y, z) = ( vertices_.col(closest_point[0]) - point ).norm() * sign; 91 | } 92 | } 93 | 94 | grid_size_ = leaf_size; 95 | source_ = min_point; 96 | } 97 | 98 | inline bool generate_graph(Eigen::MatrixXd & vertices, Eigen::MatrixXi & edges) 99 | { 100 | std::vector< Eigen::Vector3d > vertices_vector; 101 | std::vector< Eigen::Vector2i > edges_vector; 102 | 103 | Eigen::Vector3d centroid; 104 | Eigen::Tensor grid_indices(SDF_.dimension(0), SDF_.dimension(1), SDF_.dimension(2)); 105 | 106 | // build vertices 107 | for (int x = 0; x < SDF_.dimension(0); ++x) 108 | for (int y = 0; y < SDF_.dimension(1); ++y) 109 | for (int z = 0; z < SDF_.dimension(2); ++z) { 110 | centroid << x, y, z; 111 | centroid *= grid_size_; 112 | centroid += source_; 113 | vertices_vector.push_back(centroid); 114 | grid_indices(x, y, z) = vertices_vector.size(); 115 | } 116 | 117 | // build edges 118 | for (int x = 0; x < SDF_.dimension(0); ++x) 119 | for (int y = 0; y < SDF_.dimension(1); ++y) 120 | for (int z = 0; z < SDF_.dimension(2); ++z) 121 | { 122 | // case on x 123 | if (x-1>=0) { 124 | Eigen::Vector2i edge_temp; 125 | edge_temp << grid_indices(x, y, z), grid_indices(x-1, y, z); 126 | edges_vector.push_back(edge_temp); 127 | } 128 | if (x+1=0) { 136 | Eigen::Vector2i edge_temp; 137 | edge_temp << grid_indices(x, y, z), grid_indices(x, y-1, z); 138 | edges_vector.push_back(edge_temp); 139 | } 140 | if (y+1=0) { 148 | Eigen::Vector2i edge_temp; 149 | edge_temp << grid_indices(x, y, z), grid_indices(x, y, z-1); 150 | edges_vector.push_back(edge_temp); 151 | } 152 | if (z+1 slice; 185 | Eigen::Tensor tensor_slice; 186 | 187 | Eigen::array offset = {0,0,i}; //Starting point 188 | Eigen::array extent = {SDF_.dimension(0),SDF_.dimension(1),0}; //Finish point 189 | tensor_slice = SDF_.slice(offset, extent).reshape(Eigen::array{SDF_.dimension(0),SDF_.dimension(1)}); 190 | slice = Eigen::Map> (tensor_slice.data(), tensor_slice.dimension(0),tensor_slice.dimension(1)); 191 | 192 | //slice /= slice.maxCoeff() ; 193 | 194 | Eigen::MatrixXd R, G, B; 195 | R = slice; 196 | R = R.cwiseMax(0); 197 | R /= R.maxCoeff(); 198 | 199 | G = -slice; 200 | G = G.cwiseMax(0); 201 | G /= G.maxCoeff(); 202 | B = G; 203 | 204 | std::stringstream ss; 205 | ss << std::setw(3) << std::setfill('0') << i; 206 | std::string s = ss.str(); 207 | 208 | std::string file_name = folder_name + s + ".png"; 209 | writePNG(R, G, B, file_name); 210 | } 211 | 212 | std::cout << "Progress: Stack of images written in :" << folder_name << std::endl; 213 | 214 | return true; 215 | }; 216 | 217 | }; 218 | 219 | #endif -------------------------------------------------------------------------------- /utils/EigenTools/getMinMax.h: -------------------------------------------------------------------------------- 1 | #ifndef GETMINMAX_HPP 2 | #define GETMINMAX_HPP 3 | 4 | #include 5 | 6 | static inline void getMinMax(Eigen::MatrixXd & in_cloud, Eigen::Vector3d & min_point, Eigen::Vector3d & max_point){ 7 | max_point = in_cloud.rowwise().maxCoeff(); 8 | min_point = in_cloud.rowwise().minCoeff(); 9 | }; 10 | 11 | inline void getScale(Eigen::MatrixXd in_cloud, double & scale){ 12 | Eigen::Vector3d min_point; 13 | Eigen::Vector3d max_point; 14 | 15 | getMinMax(in_cloud, min_point, max_point); 16 | 17 | scale = (max_point - min_point).norm(); 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /utils/EigenTools/nanoflannWrapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * nanoflann wrapper 3 | * by R. Falque 4 | * 21/11/2018 5 | * 6 | * History: 7 | * 16/01/2020 : fix bug with target being passed by reference and not stored internally 8 | */ 9 | 10 | #ifndef NANOFLANN_WRAPPER 11 | #define NANOFLANN_WRAPPER 12 | 13 | #include 14 | #include 15 | 16 | #include "nanoflann.hpp" 17 | 18 | class nanoflann_wrapper 19 | { 20 | 21 | private: 22 | std::shared_ptr < nanoflann::KDTreeEigenMatrixAdaptor< Eigen::MatrixXd > > kd_tree_index; 23 | Eigen::MatrixXd pointcloud_; 24 | 25 | public: 26 | nanoflann_wrapper(Eigen::MatrixXd& target) 27 | { 28 | this->pointcloud_ = target.transpose(); 29 | 30 | if (target.rows() != 3) 31 | { 32 | std::cout << "Error: wrong input size\n"; 33 | exit(0); 34 | } 35 | 36 | // set up kdtree 37 | int leaf_size=10; 38 | int dimensionality=3; 39 | 40 | this->kd_tree_index = std::make_shared< nanoflann::KDTreeEigenMatrixAdaptor< Eigen::MatrixXd> >(dimensionality, this->pointcloud_, leaf_size); 41 | this->kd_tree_index->index->buildIndex(); 42 | } 43 | 44 | ~nanoflann_wrapper(){ 45 | } 46 | 47 | std::vector< int > return_k_closest_points(Eigen::Vector3d query_point, int k) 48 | { 49 | // Query point: 50 | std::vector query_pt; 51 | for (int d=0; d<3; d++) 52 | query_pt.push_back( query_point(d) ); 53 | 54 | // set wtf vectors 55 | std::vector ret_indexes(k); 56 | std::vector out_dists_sqr(k); 57 | nanoflann::KNNResultSet resultSet(k); 58 | resultSet.init( &ret_indexes.at(0), &out_dists_sqr.at(0) ); 59 | 60 | // knn search 61 | this->kd_tree_index->index->findNeighbors(resultSet, &query_pt.at(0), nanoflann::SearchParams(k)); 62 | 63 | // pack result into std::vector 64 | std::vector< int > indexes; 65 | for (int i = 0; i < k; i++) 66 | indexes.push_back( ret_indexes.at(i) ); 67 | 68 | return indexes; 69 | } 70 | 71 | 72 | bool return_k_closest_points(Eigen::Vector3d query_point, int k, std::vector & indexes, std::vector & distances) 73 | { 74 | // Query point: 75 | std::vector query_pt; 76 | for (int d=0; d<3; d++) 77 | query_pt.push_back( query_point(d) ); 78 | 79 | indexes.clear(); 80 | distances.clear(); 81 | 82 | // set wtf vectors 83 | std::vector ret_indexes(k); 84 | std::vector out_dists_sqr(k); 85 | nanoflann::KNNResultSet resultSet(k); 86 | resultSet.init( &ret_indexes.at(0), &out_dists_sqr.at(0) ); 87 | 88 | // knn search 89 | this->kd_tree_index->index->findNeighbors(resultSet, &query_pt.at(0), nanoflann::SearchParams(k)); 90 | 91 | // pack results back into std::vector 92 | for (int i = 0; i < ret_indexes.size(); i++) 93 | { 94 | indexes.push_back( ret_indexes.at(i) ); 95 | distances.push_back( out_dists_sqr.at(i) ); 96 | } 97 | 98 | return true; 99 | } 100 | 101 | 102 | 103 | 104 | std::vector< int > radius_search(Eigen::Vector3d query_point, double max_dist) 105 | { 106 | // Query point: 107 | std::vector query_pt; 108 | for (int d=0; d<3; d++) 109 | query_pt.push_back( query_point(d) ); 110 | 111 | //search 112 | nanoflann::SearchParams params; 113 | std::vector > matches; 114 | this->kd_tree_index->index->radiusSearch(&query_pt.at(0), max_dist, matches, params); 115 | 116 | // pack result into std::vector 117 | std::vector< int > indexes; 118 | for (int i = 0; i < matches.size(); i++) 119 | indexes.push_back( matches.at(i).first ); 120 | 121 | return indexes; 122 | } 123 | 124 | bool radius_search(Eigen::Vector3d query_point, double max_dist, std::vector & indexes, std::vector & distances) 125 | { 126 | // Query point: 127 | std::vector query_pt; 128 | for (int d=0; d<3; d++) 129 | query_pt.push_back( query_point(d) ); 130 | 131 | //search 132 | nanoflann::SearchParams params; 133 | std::vector > matches; 134 | this->kd_tree_index->index->radiusSearch(&query_pt.at(0), max_dist, matches, params); 135 | 136 | // pack result into std::vector 137 | for (int i = 0; i < matches.size(); i++) 138 | { 139 | indexes.push_back( matches.at(i).first ); 140 | distances.push_back( matches.at(i).second ); 141 | } 142 | 143 | return true; 144 | } 145 | 146 | }; 147 | 148 | #endif 149 | -------------------------------------------------------------------------------- /utils/IO/process_folder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * compute the normals using the faces 3 | * by R. Falque 4 | * 16/01/2020 5 | */ 6 | 7 | #ifndef PORCESS_FOLDER_H 8 | #define PORCESS_FOLDER_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | 19 | inline bool empty_folder(std::string folder_path) { 20 | 21 | // These are data types defined in the "dirent" header 22 | DIR *theFolder = opendir(folder_path.c_str()); 23 | struct dirent *next_file; 24 | std::string filepath; 25 | 26 | while ( (next_file = readdir(theFolder)) != NULL ) 27 | { 28 | // build the path for each file in the folder 29 | filepath = folder_path + next_file->d_name; 30 | if (filepath.substr(filepath.size() - 4) == ".png") 31 | remove(filepath.c_str()); 32 | } 33 | closedir(theFolder); 34 | 35 | return true; 36 | }; 37 | 38 | inline bool does_folder_exist(std::string folder_path) { 39 | bool folder_exist; 40 | 41 | DIR* dir = opendir(folder_path.c_str()); 42 | if (dir) { 43 | folder_exist = true; 44 | closedir(dir); 45 | } else if (ENOENT == errno) { 46 | folder_exist = false; 47 | } else { 48 | folder_exist = false; 49 | } 50 | 51 | return folder_exist; 52 | }; 53 | 54 | inline bool create_folder(std::string folder_path) { 55 | mkdir(folder_path.c_str(), 0755); 56 | std::cout << "Progress: create the folder:" << folder_path << std::endl; 57 | return true; 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /utils/IO/readPLY.h: -------------------------------------------------------------------------------- 1 | #ifndef IO_READPLY_H 2 | #define IO_READPLY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define TINYPLY_IMPLEMENTATION 10 | #include "tinyply.h" 11 | #include "structures.h" 12 | 13 | using namespace tinyply; 14 | 15 | void readPLY(const std::string & filepath, 16 | Eigen::MatrixXd &V, 17 | Eigen::MatrixXi &F, 18 | Eigen::MatrixXd &N, 19 | Eigen::MatrixXi &RGB) 20 | { 21 | bool verbose = false; 22 | 23 | try 24 | { 25 | std::ifstream ss(filepath, std::ios::binary); 26 | if (ss.fail()) throw std::runtime_error("failed to open " + filepath); 27 | 28 | PlyFile file; 29 | file.parse_header(ss); 30 | 31 | if (verbose) { 32 | std::cout << "........................................................................\n"; 33 | for (auto c : file.get_comments()) std::cout << "Comment: " << c << std::endl; 34 | for (auto e : file.get_elements()) 35 | { 36 | std::cout << "element - " << e.name << " (" << e.size << ")" << std::endl; 37 | for (auto p : e.properties) std::cout << "\tproperty - " << p.name << " (" << tinyply::PropertyTable[p.propertyType].str << ")" << std::endl; 38 | } 39 | std::cout << "........................................................................\n"; 40 | } 41 | // Tinyply treats parsed data as untyped byte buffers. See below for examples. 42 | std::shared_ptr vertices_handle, normals_handle, faces_handle, texcoords_handle, RGB_handle; 43 | 44 | // The header information can be used to programmatically extract properties on elements 45 | // known to exist in the header prior to reading the data. For brevity of this sample, properties 46 | // like vertex position are hard-coded: 47 | try { vertices_handle = file.request_properties_from_element("vertex", { "x", "y", "z" }); } 48 | catch (const std::exception & e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } 49 | 50 | try { normals_handle = file.request_properties_from_element("vertex", { "nx", "ny", "nz" }); } 51 | catch (const std::exception & e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } 52 | 53 | try { texcoords_handle = file.request_properties_from_element("vertex", { "u", "v" }); } 54 | catch (const std::exception & e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } 55 | 56 | try { RGB_handle = file.request_properties_from_element("vertex", { "red", "green" , "blue" }); } 57 | catch (const std::exception & e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } 58 | 59 | 60 | 61 | // Providing a list size hint (the last argument) is a 2x performance improvement. If you have 62 | // arbitrary ply files, it is best to leave this 0. 63 | try { faces_handle = file.request_properties_from_element("face", { "vertex_indices" }, 3); } 64 | catch (const std::exception & e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } 65 | 66 | file.read(ss); 67 | 68 | if (verbose) { 69 | if (vertices_handle) std::cout << "\tRead " << vertices_handle->count << " total vertices "<< std::endl; 70 | if (normals_handle) std::cout << "\tRead " << normals_handle->count << " total vertex normals " << std::endl; 71 | if (faces_handle) std::cout << "\tRead " << faces_handle->count << " total faces (triangles) " << std::endl; 72 | if (RGB_handle) std::cout << "\tRead " << RGB_handle->count << " total vertex RGB " << std::endl; 73 | } 74 | 75 | if (vertices_handle) 76 | { 77 | const size_t numVerticesBytes = vertices_handle->buffer.size_bytes(); 78 | std::vector vertices(vertices_handle->count); 79 | std::memcpy(vertices.data(), vertices_handle->buffer.get(), numVerticesBytes); 80 | 81 | V.resize(3, vertices.size()); 82 | for (int i=0; i(); 84 | } 85 | 86 | if (normals_handle) { 87 | const size_t numNormalsBytes = normals_handle->buffer.size_bytes(); 88 | std::vector normals(normals_handle->count); 89 | std::memcpy(normals.data(), normals_handle->buffer.get(), numNormalsBytes); 90 | 91 | N.resize(3, normals.size()); 92 | for (int i=0; i(); 94 | } 95 | 96 | if (faces_handle) { 97 | const size_t numFacesBytes = faces_handle->buffer.size_bytes(); 98 | std::vector faces(faces_handle->count); 99 | std::memcpy(faces.data(), faces_handle->buffer.get(), numFacesBytes); 100 | 101 | F.resize(3, faces.size()); 102 | for (int i=0; ibuffer.size_bytes(); 107 | std::vector rgb(RGB_handle->count); 108 | std::memcpy(rgb.data(), RGB_handle->buffer.get(), numRgbBytes); 109 | 110 | RGB.resize(3, rgb.size()); 111 | for (int i=0; i 154 | 155 | // if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' 156 | #ifndef STBIWDEF 157 | #ifdef STB_IMAGE_WRITE_STATIC 158 | #define STBIWDEF static 159 | #else 160 | #ifdef __cplusplus 161 | #define STBIWDEF extern "C" 162 | #else 163 | #define STBIWDEF extern 164 | #endif 165 | #endif 166 | #endif 167 | 168 | #ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations 169 | extern int stbi_write_tga_with_rle; 170 | extern int stbi_write_png_compression_level; 171 | extern int stbi_write_force_png_filter; 172 | #endif 173 | 174 | #ifndef STBI_WRITE_NO_STDIO 175 | STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); 176 | STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); 177 | STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); 178 | STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); 179 | STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); 180 | 181 | #ifdef STBI_WINDOWS_UTF8 182 | STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); 183 | #endif 184 | #endif 185 | 186 | typedef void stbi_write_func(void *context, void *data, int size); 187 | 188 | STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); 189 | STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 190 | STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 191 | STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); 192 | STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); 193 | 194 | STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); 195 | 196 | #endif//INCLUDE_STB_IMAGE_WRITE_H 197 | 198 | #ifdef STB_IMAGE_WRITE_IMPLEMENTATION 199 | 200 | #ifdef _WIN32 201 | #ifndef _CRT_SECURE_NO_WARNINGS 202 | #define _CRT_SECURE_NO_WARNINGS 203 | #endif 204 | #ifndef _CRT_NONSTDC_NO_DEPRECATE 205 | #define _CRT_NONSTDC_NO_DEPRECATE 206 | #endif 207 | #endif 208 | 209 | #ifndef STBI_WRITE_NO_STDIO 210 | #include 211 | #endif // STBI_WRITE_NO_STDIO 212 | 213 | #include 214 | #include 215 | #include 216 | #include 217 | 218 | #if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) 219 | // ok 220 | #elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) 221 | // ok 222 | #else 223 | #error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." 224 | #endif 225 | 226 | #ifndef STBIW_MALLOC 227 | #define STBIW_MALLOC(sz) malloc(sz) 228 | #define STBIW_REALLOC(p,newsz) realloc(p,newsz) 229 | #define STBIW_FREE(p) free(p) 230 | #endif 231 | 232 | #ifndef STBIW_REALLOC_SIZED 233 | #define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) 234 | #endif 235 | 236 | 237 | #ifndef STBIW_MEMMOVE 238 | #define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) 239 | #endif 240 | 241 | 242 | #ifndef STBIW_ASSERT 243 | #include 244 | #define STBIW_ASSERT(x) assert(x) 245 | #endif 246 | 247 | #define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) 248 | 249 | #ifdef STB_IMAGE_WRITE_STATIC 250 | static int stbi__flip_vertically_on_write=0; 251 | static int stbi_write_png_compression_level = 8; 252 | static int stbi_write_tga_with_rle = 1; 253 | static int stbi_write_force_png_filter = -1; 254 | #else 255 | int stbi_write_png_compression_level = 8; 256 | int stbi__flip_vertically_on_write=0; 257 | int stbi_write_tga_with_rle = 1; 258 | int stbi_write_force_png_filter = -1; 259 | #endif 260 | 261 | STBIWDEF void stbi_flip_vertically_on_write(int flag) 262 | { 263 | stbi__flip_vertically_on_write = flag; 264 | } 265 | 266 | typedef struct 267 | { 268 | stbi_write_func *func; 269 | void *context; 270 | } stbi__write_context; 271 | 272 | // initialize a callback-based context 273 | static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) 274 | { 275 | s->func = c; 276 | s->context = context; 277 | } 278 | 279 | #ifndef STBI_WRITE_NO_STDIO 280 | 281 | static void stbi__stdio_write(void *context, void *data, int size) 282 | { 283 | fwrite(data,1,size,(FILE*) context); 284 | } 285 | 286 | #if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) 287 | #ifdef __cplusplus 288 | #define STBIW_EXTERN extern "C" 289 | #else 290 | #define STBIW_EXTERN extern 291 | #endif 292 | STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); 293 | STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); 294 | 295 | STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) 296 | { 297 | return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); 298 | } 299 | #endif 300 | 301 | static FILE *stbiw__fopen(char const *filename, char const *mode) 302 | { 303 | FILE *f; 304 | #if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) 305 | wchar_t wMode[64]; 306 | wchar_t wFilename[1024]; 307 | if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) 308 | return 0; 309 | 310 | if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) 311 | return 0; 312 | 313 | #if _MSC_VER >= 1400 314 | if (0 != _wfopen_s(&f, wFilename, wMode)) 315 | f = 0; 316 | #else 317 | f = _wfopen(wFilename, wMode); 318 | #endif 319 | 320 | #elif defined(_MSC_VER) && _MSC_VER >= 1400 321 | if (0 != fopen_s(&f, filename, mode)) 322 | f=0; 323 | #else 324 | f = fopen(filename, mode); 325 | #endif 326 | return f; 327 | } 328 | 329 | static int stbi__start_write_file(stbi__write_context *s, const char *filename) 330 | { 331 | FILE *f = stbiw__fopen(filename, "wb"); 332 | stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); 333 | return f != NULL; 334 | } 335 | 336 | static void stbi__end_write_file(stbi__write_context *s) 337 | { 338 | fclose((FILE *)s->context); 339 | } 340 | 341 | #endif // !STBI_WRITE_NO_STDIO 342 | 343 | typedef unsigned int stbiw_uint32; 344 | typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; 345 | 346 | static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) 347 | { 348 | while (*fmt) { 349 | switch (*fmt++) { 350 | case ' ': break; 351 | case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); 352 | s->func(s->context,&x,1); 353 | break; } 354 | case '2': { int x = va_arg(v,int); 355 | unsigned char b[2]; 356 | b[0] = STBIW_UCHAR(x); 357 | b[1] = STBIW_UCHAR(x>>8); 358 | s->func(s->context,b,2); 359 | break; } 360 | case '4': { stbiw_uint32 x = va_arg(v,int); 361 | unsigned char b[4]; 362 | b[0]=STBIW_UCHAR(x); 363 | b[1]=STBIW_UCHAR(x>>8); 364 | b[2]=STBIW_UCHAR(x>>16); 365 | b[3]=STBIW_UCHAR(x>>24); 366 | s->func(s->context,b,4); 367 | break; } 368 | default: 369 | STBIW_ASSERT(0); 370 | return; 371 | } 372 | } 373 | } 374 | 375 | static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) 376 | { 377 | va_list v; 378 | va_start(v, fmt); 379 | stbiw__writefv(s, fmt, v); 380 | va_end(v); 381 | } 382 | 383 | static void stbiw__putc(stbi__write_context *s, unsigned char c) 384 | { 385 | s->func(s->context, &c, 1); 386 | } 387 | 388 | static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) 389 | { 390 | unsigned char arr[3]; 391 | arr[0] = a; arr[1] = b; arr[2] = c; 392 | s->func(s->context, arr, 3); 393 | } 394 | 395 | static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) 396 | { 397 | unsigned char bg[3] = { 255, 0, 255}, px[3]; 398 | int k; 399 | 400 | if (write_alpha < 0) 401 | s->func(s->context, &d[comp - 1], 1); 402 | 403 | switch (comp) { 404 | case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case 405 | case 1: 406 | if (expand_mono) 407 | stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp 408 | else 409 | s->func(s->context, d, 1); // monochrome TGA 410 | break; 411 | case 4: 412 | if (!write_alpha) { 413 | // composite against pink background 414 | for (k = 0; k < 3; ++k) 415 | px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; 416 | stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); 417 | break; 418 | } 419 | /* FALLTHROUGH */ 420 | case 3: 421 | stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); 422 | break; 423 | } 424 | if (write_alpha > 0) 425 | s->func(s->context, &d[comp - 1], 1); 426 | } 427 | 428 | static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) 429 | { 430 | stbiw_uint32 zero = 0; 431 | int i,j, j_end; 432 | 433 | if (y <= 0) 434 | return; 435 | 436 | if (stbi__flip_vertically_on_write) 437 | vdir *= -1; 438 | 439 | if (vdir < 0) { 440 | j_end = -1; j = y-1; 441 | } else { 442 | j_end = y; j = 0; 443 | } 444 | 445 | for (; j != j_end; j += vdir) { 446 | for (i=0; i < x; ++i) { 447 | unsigned char *d = (unsigned char *) data + (j*x+i)*comp; 448 | stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); 449 | } 450 | s->func(s->context, &zero, scanline_pad); 451 | } 452 | } 453 | 454 | static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) 455 | { 456 | if (y < 0 || x < 0) { 457 | return 0; 458 | } else { 459 | va_list v; 460 | va_start(v, fmt); 461 | stbiw__writefv(s, fmt, v); 462 | va_end(v); 463 | stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); 464 | return 1; 465 | } 466 | } 467 | 468 | static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) 469 | { 470 | int pad = (-x*3) & 3; 471 | return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, 472 | "11 4 22 4" "4 44 22 444444", 473 | 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header 474 | 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header 475 | } 476 | 477 | STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) 478 | { 479 | stbi__write_context s; 480 | stbi__start_write_callbacks(&s, func, context); 481 | return stbi_write_bmp_core(&s, x, y, comp, data); 482 | } 483 | 484 | #ifndef STBI_WRITE_NO_STDIO 485 | STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) 486 | { 487 | stbi__write_context s; 488 | if (stbi__start_write_file(&s,filename)) { 489 | int r = stbi_write_bmp_core(&s, x, y, comp, data); 490 | stbi__end_write_file(&s); 491 | return r; 492 | } else 493 | return 0; 494 | } 495 | #endif //!STBI_WRITE_NO_STDIO 496 | 497 | static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) 498 | { 499 | int has_alpha = (comp == 2 || comp == 4); 500 | int colorbytes = has_alpha ? comp-1 : comp; 501 | int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 502 | 503 | if (y < 0 || x < 0) 504 | return 0; 505 | 506 | if (!stbi_write_tga_with_rle) { 507 | return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, 508 | "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); 509 | } else { 510 | int i,j,k; 511 | int jend, jdir; 512 | 513 | stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); 514 | 515 | if (stbi__flip_vertically_on_write) { 516 | j = 0; 517 | jend = y; 518 | jdir = 1; 519 | } else { 520 | j = y-1; 521 | jend = -1; 522 | jdir = -1; 523 | } 524 | for (; j != jend; j += jdir) { 525 | unsigned char *row = (unsigned char *) data + j * x * comp; 526 | int len; 527 | 528 | for (i = 0; i < x; i += len) { 529 | unsigned char *begin = row + i * comp; 530 | int diff = 1; 531 | len = 1; 532 | 533 | if (i < x - 1) { 534 | ++len; 535 | diff = memcmp(begin, row + (i + 1) * comp, comp); 536 | if (diff) { 537 | const unsigned char *prev = begin; 538 | for (k = i + 2; k < x && len < 128; ++k) { 539 | if (memcmp(prev, row + k * comp, comp)) { 540 | prev += comp; 541 | ++len; 542 | } else { 543 | --len; 544 | break; 545 | } 546 | } 547 | } else { 548 | for (k = i + 2; k < x && len < 128; ++k) { 549 | if (!memcmp(begin, row + k * comp, comp)) { 550 | ++len; 551 | } else { 552 | break; 553 | } 554 | } 555 | } 556 | } 557 | 558 | if (diff) { 559 | unsigned char header = STBIW_UCHAR(len - 1); 560 | s->func(s->context, &header, 1); 561 | for (k = 0; k < len; ++k) { 562 | stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); 563 | } 564 | } else { 565 | unsigned char header = STBIW_UCHAR(len - 129); 566 | s->func(s->context, &header, 1); 567 | stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); 568 | } 569 | } 570 | } 571 | } 572 | return 1; 573 | } 574 | 575 | STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) 576 | { 577 | stbi__write_context s; 578 | stbi__start_write_callbacks(&s, func, context); 579 | return stbi_write_tga_core(&s, x, y, comp, (void *) data); 580 | } 581 | 582 | #ifndef STBI_WRITE_NO_STDIO 583 | STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) 584 | { 585 | stbi__write_context s; 586 | if (stbi__start_write_file(&s,filename)) { 587 | int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); 588 | stbi__end_write_file(&s); 589 | return r; 590 | } else 591 | return 0; 592 | } 593 | #endif 594 | 595 | // ************************************************************************************************* 596 | // Radiance RGBE HDR writer 597 | // by Baldur Karlsson 598 | 599 | #define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) 600 | 601 | static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) 602 | { 603 | int exponent; 604 | float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); 605 | 606 | if (maxcomp < 1e-32f) { 607 | rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; 608 | } else { 609 | float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; 610 | 611 | rgbe[0] = (unsigned char)(linear[0] * normalize); 612 | rgbe[1] = (unsigned char)(linear[1] * normalize); 613 | rgbe[2] = (unsigned char)(linear[2] * normalize); 614 | rgbe[3] = (unsigned char)(exponent + 128); 615 | } 616 | } 617 | 618 | static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) 619 | { 620 | unsigned char lengthbyte = STBIW_UCHAR(length+128); 621 | STBIW_ASSERT(length+128 <= 255); 622 | s->func(s->context, &lengthbyte, 1); 623 | s->func(s->context, &databyte, 1); 624 | } 625 | 626 | static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) 627 | { 628 | unsigned char lengthbyte = STBIW_UCHAR(length); 629 | STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code 630 | s->func(s->context, &lengthbyte, 1); 631 | s->func(s->context, data, length); 632 | } 633 | 634 | static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) 635 | { 636 | unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; 637 | unsigned char rgbe[4]; 638 | float linear[3]; 639 | int x; 640 | 641 | scanlineheader[2] = (width&0xff00)>>8; 642 | scanlineheader[3] = (width&0x00ff); 643 | 644 | /* skip RLE for images too small or large */ 645 | if (width < 8 || width >= 32768) { 646 | for (x=0; x < width; x++) { 647 | switch (ncomp) { 648 | case 4: /* fallthrough */ 649 | case 3: linear[2] = scanline[x*ncomp + 2]; 650 | linear[1] = scanline[x*ncomp + 1]; 651 | linear[0] = scanline[x*ncomp + 0]; 652 | break; 653 | default: 654 | linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; 655 | break; 656 | } 657 | stbiw__linear_to_rgbe(rgbe, linear); 658 | s->func(s->context, rgbe, 4); 659 | } 660 | } else { 661 | int c,r; 662 | /* encode into scratch buffer */ 663 | for (x=0; x < width; x++) { 664 | switch(ncomp) { 665 | case 4: /* fallthrough */ 666 | case 3: linear[2] = scanline[x*ncomp + 2]; 667 | linear[1] = scanline[x*ncomp + 1]; 668 | linear[0] = scanline[x*ncomp + 0]; 669 | break; 670 | default: 671 | linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; 672 | break; 673 | } 674 | stbiw__linear_to_rgbe(rgbe, linear); 675 | scratch[x + width*0] = rgbe[0]; 676 | scratch[x + width*1] = rgbe[1]; 677 | scratch[x + width*2] = rgbe[2]; 678 | scratch[x + width*3] = rgbe[3]; 679 | } 680 | 681 | s->func(s->context, scanlineheader, 4); 682 | 683 | /* RLE each component separately */ 684 | for (c=0; c < 4; c++) { 685 | unsigned char *comp = &scratch[width*c]; 686 | 687 | x = 0; 688 | while (x < width) { 689 | // find first run 690 | r = x; 691 | while (r+2 < width) { 692 | if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) 693 | break; 694 | ++r; 695 | } 696 | if (r+2 >= width) 697 | r = width; 698 | // dump up to first run 699 | while (x < r) { 700 | int len = r-x; 701 | if (len > 128) len = 128; 702 | stbiw__write_dump_data(s, len, &comp[x]); 703 | x += len; 704 | } 705 | // if there's a run, output it 706 | if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd 707 | // find next byte after run 708 | while (r < width && comp[r] == comp[x]) 709 | ++r; 710 | // output run up to r 711 | while (x < r) { 712 | int len = r-x; 713 | if (len > 127) len = 127; 714 | stbiw__write_run_data(s, len, comp[x]); 715 | x += len; 716 | } 717 | } 718 | } 719 | } 720 | } 721 | } 722 | 723 | static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) 724 | { 725 | if (y <= 0 || x <= 0 || data == NULL) 726 | return 0; 727 | else { 728 | // Each component is stored separately. Allocate scratch space for full output scanline. 729 | unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); 730 | int i, len; 731 | char buffer[128]; 732 | char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; 733 | s->func(s->context, header, sizeof(header)-1); 734 | 735 | #ifdef __STDC_WANT_SECURE_LIB__ 736 | len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); 737 | #else 738 | len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); 739 | #endif 740 | s->func(s->context, buffer, len); 741 | 742 | for(i=0; i < y; i++) 743 | stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); 744 | STBIW_FREE(scratch); 745 | return 1; 746 | } 747 | } 748 | 749 | STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) 750 | { 751 | stbi__write_context s; 752 | stbi__start_write_callbacks(&s, func, context); 753 | return stbi_write_hdr_core(&s, x, y, comp, (float *) data); 754 | } 755 | 756 | #ifndef STBI_WRITE_NO_STDIO 757 | STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) 758 | { 759 | stbi__write_context s; 760 | if (stbi__start_write_file(&s,filename)) { 761 | int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); 762 | stbi__end_write_file(&s); 763 | return r; 764 | } else 765 | return 0; 766 | } 767 | #endif // STBI_WRITE_NO_STDIO 768 | 769 | 770 | ////////////////////////////////////////////////////////////////////////////// 771 | // 772 | // PNG writer 773 | // 774 | 775 | #ifndef STBIW_ZLIB_COMPRESS 776 | // stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() 777 | #define stbiw__sbraw(a) ((int *) (a) - 2) 778 | #define stbiw__sbm(a) stbiw__sbraw(a)[0] 779 | #define stbiw__sbn(a) stbiw__sbraw(a)[1] 780 | 781 | #define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) 782 | #define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) 783 | #define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) 784 | 785 | #define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) 786 | #define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) 787 | #define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) 788 | 789 | static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) 790 | { 791 | int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; 792 | void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); 793 | STBIW_ASSERT(p); 794 | if (p) { 795 | if (!*arr) ((int *) p)[1] = 0; 796 | *arr = (void *) ((int *) p + 2); 797 | stbiw__sbm(*arr) = m; 798 | } 799 | return *arr; 800 | } 801 | 802 | static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) 803 | { 804 | while (*bitcount >= 8) { 805 | stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); 806 | *bitbuffer >>= 8; 807 | *bitcount -= 8; 808 | } 809 | return data; 810 | } 811 | 812 | static int stbiw__zlib_bitrev(int code, int codebits) 813 | { 814 | int res=0; 815 | while (codebits--) { 816 | res = (res << 1) | (code & 1); 817 | code >>= 1; 818 | } 819 | return res; 820 | } 821 | 822 | static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) 823 | { 824 | int i; 825 | for (i=0; i < limit && i < 258; ++i) 826 | if (a[i] != b[i]) break; 827 | return i; 828 | } 829 | 830 | static unsigned int stbiw__zhash(unsigned char *data) 831 | { 832 | stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); 833 | hash ^= hash << 3; 834 | hash += hash >> 5; 835 | hash ^= hash << 4; 836 | hash += hash >> 17; 837 | hash ^= hash << 25; 838 | hash += hash >> 6; 839 | return hash; 840 | } 841 | 842 | #define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) 843 | #define stbiw__zlib_add(code,codebits) \ 844 | (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) 845 | #define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) 846 | // default huffman tables 847 | #define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) 848 | #define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) 849 | #define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) 850 | #define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) 851 | #define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) 852 | #define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) 853 | 854 | #define stbiw__ZHASH 16384 855 | 856 | #endif // STBIW_ZLIB_COMPRESS 857 | 858 | STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) 859 | { 860 | #ifdef STBIW_ZLIB_COMPRESS 861 | // user provided a zlib compress implementation, use that 862 | return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); 863 | #else // use builtin 864 | static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; 865 | static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; 866 | static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; 867 | static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; 868 | unsigned int bitbuf=0; 869 | int i,j, bitcount=0; 870 | unsigned char *out = NULL; 871 | unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); 872 | if (hash_table == NULL) 873 | return NULL; 874 | if (quality < 5) quality = 5; 875 | 876 | stbiw__sbpush(out, 0x78); // DEFLATE 32K window 877 | stbiw__sbpush(out, 0x5e); // FLEVEL = 1 878 | stbiw__zlib_add(1,1); // BFINAL = 1 879 | stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman 880 | 881 | for (i=0; i < stbiw__ZHASH; ++i) 882 | hash_table[i] = NULL; 883 | 884 | i=0; 885 | while (i < data_len-3) { 886 | // hash next 3 bytes of data to be compressed 887 | int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; 888 | unsigned char *bestloc = 0; 889 | unsigned char **hlist = hash_table[h]; 890 | int n = stbiw__sbcount(hlist); 891 | for (j=0; j < n; ++j) { 892 | if (hlist[j]-data > i-32768) { // if entry lies within window 893 | int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); 894 | if (d >= best) { best=d; bestloc=hlist[j]; } 895 | } 896 | } 897 | // when hash table entry is too long, delete half the entries 898 | if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { 899 | STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); 900 | stbiw__sbn(hash_table[h]) = quality; 901 | } 902 | stbiw__sbpush(hash_table[h],data+i); 903 | 904 | if (bestloc) { 905 | // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal 906 | h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); 907 | hlist = hash_table[h]; 908 | n = stbiw__sbcount(hlist); 909 | for (j=0; j < n; ++j) { 910 | if (hlist[j]-data > i-32767) { 911 | int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); 912 | if (e > best) { // if next match is better, bail on current match 913 | bestloc = NULL; 914 | break; 915 | } 916 | } 917 | } 918 | } 919 | 920 | if (bestloc) { 921 | int d = (int) (data+i - bestloc); // distance back 922 | STBIW_ASSERT(d <= 32767 && best <= 258); 923 | for (j=0; best > lengthc[j+1]-1; ++j); 924 | stbiw__zlib_huff(j+257); 925 | if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); 926 | for (j=0; d > distc[j+1]-1; ++j); 927 | stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); 928 | if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); 929 | i += best; 930 | } else { 931 | stbiw__zlib_huffb(data[i]); 932 | ++i; 933 | } 934 | } 935 | // write out final bytes 936 | for (;i < data_len; ++i) 937 | stbiw__zlib_huffb(data[i]); 938 | stbiw__zlib_huff(256); // end of block 939 | // pad with 0 bits to byte boundary 940 | while (bitcount) 941 | stbiw__zlib_add(0,1); 942 | 943 | for (i=0; i < stbiw__ZHASH; ++i) 944 | (void) stbiw__sbfree(hash_table[i]); 945 | STBIW_FREE(hash_table); 946 | 947 | { 948 | // compute adler32 on input 949 | unsigned int s1=1, s2=0; 950 | int blocklen = (int) (data_len % 5552); 951 | j=0; 952 | while (j < data_len) { 953 | for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } 954 | s1 %= 65521; s2 %= 65521; 955 | j += blocklen; 956 | blocklen = 5552; 957 | } 958 | stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); 959 | stbiw__sbpush(out, STBIW_UCHAR(s2)); 960 | stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); 961 | stbiw__sbpush(out, STBIW_UCHAR(s1)); 962 | } 963 | *out_len = stbiw__sbn(out); 964 | // make returned pointer freeable 965 | STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); 966 | return (unsigned char *) stbiw__sbraw(out); 967 | #endif // STBIW_ZLIB_COMPRESS 968 | } 969 | 970 | static unsigned int stbiw__crc32(unsigned char *buffer, int len) 971 | { 972 | #ifdef STBIW_CRC32 973 | return STBIW_CRC32(buffer, len); 974 | #else 975 | static unsigned int crc_table[256] = 976 | { 977 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 978 | 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 979 | 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 980 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 981 | 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 982 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 983 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 984 | 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 985 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 986 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 987 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 988 | 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 989 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 990 | 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 991 | 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 992 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 993 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 994 | 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 995 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 996 | 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 997 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 998 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 999 | 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 1000 | 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 1001 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 1002 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 1003 | 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 1004 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 1005 | 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 1006 | 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 1007 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 1008 | 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D 1009 | }; 1010 | 1011 | unsigned int crc = ~0u; 1012 | int i; 1013 | for (i=0; i < len; ++i) 1014 | crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; 1015 | return ~crc; 1016 | #endif 1017 | } 1018 | 1019 | #define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) 1020 | #define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); 1021 | #define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) 1022 | 1023 | static void stbiw__wpcrc(unsigned char **data, int len) 1024 | { 1025 | unsigned int crc = stbiw__crc32(*data - len - 4, len+4); 1026 | stbiw__wp32(*data, crc); 1027 | } 1028 | 1029 | static unsigned char stbiw__paeth(int a, int b, int c) 1030 | { 1031 | int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); 1032 | if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); 1033 | if (pb <= pc) return STBIW_UCHAR(b); 1034 | return STBIW_UCHAR(c); 1035 | } 1036 | 1037 | // @OPTIMIZE: provide an option that always forces left-predict or paeth predict 1038 | static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) 1039 | { 1040 | static int mapping[] = { 0,1,2,3,4 }; 1041 | static int firstmap[] = { 0,1,0,5,6 }; 1042 | int *mymap = (y != 0) ? mapping : firstmap; 1043 | int i; 1044 | int type = mymap[filter_type]; 1045 | unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); 1046 | int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; 1047 | 1048 | if (type==0) { 1049 | memcpy(line_buffer, z, width*n); 1050 | return; 1051 | } 1052 | 1053 | // first loop isn't optimized since it's just one pixel 1054 | for (i = 0; i < n; ++i) { 1055 | switch (type) { 1056 | case 1: line_buffer[i] = z[i]; break; 1057 | case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; 1058 | case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; 1059 | case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; 1060 | case 5: line_buffer[i] = z[i]; break; 1061 | case 6: line_buffer[i] = z[i]; break; 1062 | } 1063 | } 1064 | switch (type) { 1065 | case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; 1066 | case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; 1067 | case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; 1068 | case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; 1069 | case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; 1070 | case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; 1071 | } 1072 | } 1073 | 1074 | STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) 1075 | { 1076 | int force_filter = stbi_write_force_png_filter; 1077 | int ctype[5] = { -1, 0, 4, 2, 6 }; 1078 | unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; 1079 | unsigned char *out,*o, *filt, *zlib; 1080 | signed char *line_buffer; 1081 | int j,zlen; 1082 | 1083 | if (stride_bytes == 0) 1084 | stride_bytes = x * n; 1085 | 1086 | if (force_filter >= 5) { 1087 | force_filter = -1; 1088 | } 1089 | 1090 | filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; 1091 | line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } 1092 | for (j=0; j < y; ++j) { 1093 | int filter_type; 1094 | if (force_filter > -1) { 1095 | filter_type = force_filter; 1096 | stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); 1097 | } else { // Estimate the best filter by running through all of them: 1098 | int best_filter = 0, best_filter_val = 0x7fffffff, est, i; 1099 | for (filter_type = 0; filter_type < 5; filter_type++) { 1100 | stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); 1101 | 1102 | // Estimate the entropy of the line using this filter; the less, the better. 1103 | est = 0; 1104 | for (i = 0; i < x*n; ++i) { 1105 | est += abs((signed char) line_buffer[i]); 1106 | } 1107 | if (est < best_filter_val) { 1108 | best_filter_val = est; 1109 | best_filter = filter_type; 1110 | } 1111 | } 1112 | if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it 1113 | stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); 1114 | filter_type = best_filter; 1115 | } 1116 | } 1117 | // when we get here, filter_type contains the filter type, and line_buffer contains the data 1118 | filt[j*(x*n+1)] = (unsigned char) filter_type; 1119 | STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); 1120 | } 1121 | STBIW_FREE(line_buffer); 1122 | zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); 1123 | STBIW_FREE(filt); 1124 | if (!zlib) return 0; 1125 | 1126 | // each tag requires 12 bytes of overhead 1127 | out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); 1128 | if (!out) return 0; 1129 | *out_len = 8 + 12+13 + 12+zlen + 12; 1130 | 1131 | o=out; 1132 | STBIW_MEMMOVE(o,sig,8); o+= 8; 1133 | stbiw__wp32(o, 13); // header length 1134 | stbiw__wptag(o, "IHDR"); 1135 | stbiw__wp32(o, x); 1136 | stbiw__wp32(o, y); 1137 | *o++ = 8; 1138 | *o++ = STBIW_UCHAR(ctype[n]); 1139 | *o++ = 0; 1140 | *o++ = 0; 1141 | *o++ = 0; 1142 | stbiw__wpcrc(&o,13); 1143 | 1144 | stbiw__wp32(o, zlen); 1145 | stbiw__wptag(o, "IDAT"); 1146 | STBIW_MEMMOVE(o, zlib, zlen); 1147 | o += zlen; 1148 | STBIW_FREE(zlib); 1149 | stbiw__wpcrc(&o, zlen); 1150 | 1151 | stbiw__wp32(o,0); 1152 | stbiw__wptag(o, "IEND"); 1153 | stbiw__wpcrc(&o,0); 1154 | 1155 | STBIW_ASSERT(o == out + *out_len); 1156 | 1157 | return out; 1158 | } 1159 | 1160 | #ifndef STBI_WRITE_NO_STDIO 1161 | STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) 1162 | { 1163 | FILE *f; 1164 | int len; 1165 | unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); 1166 | if (png == NULL) return 0; 1167 | 1168 | f = stbiw__fopen(filename, "wb"); 1169 | if (!f) { STBIW_FREE(png); return 0; } 1170 | fwrite(png, 1, len, f); 1171 | fclose(f); 1172 | STBIW_FREE(png); 1173 | return 1; 1174 | } 1175 | #endif 1176 | 1177 | STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) 1178 | { 1179 | int len; 1180 | unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); 1181 | if (png == NULL) return 0; 1182 | func(context, png, len); 1183 | STBIW_FREE(png); 1184 | return 1; 1185 | } 1186 | 1187 | 1188 | /* *************************************************************************** 1189 | * 1190 | * JPEG writer 1191 | * 1192 | * This is based on Jon Olick's jo_jpeg.cpp: 1193 | * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html 1194 | */ 1195 | 1196 | static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, 1197 | 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; 1198 | 1199 | static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { 1200 | int bitBuf = *bitBufP, bitCnt = *bitCntP; 1201 | bitCnt += bs[1]; 1202 | bitBuf |= bs[0] << (24 - bitCnt); 1203 | while(bitCnt >= 8) { 1204 | unsigned char c = (bitBuf >> 16) & 255; 1205 | stbiw__putc(s, c); 1206 | if(c == 255) { 1207 | stbiw__putc(s, 0); 1208 | } 1209 | bitBuf <<= 8; 1210 | bitCnt -= 8; 1211 | } 1212 | *bitBufP = bitBuf; 1213 | *bitCntP = bitCnt; 1214 | } 1215 | 1216 | static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { 1217 | float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; 1218 | float z1, z2, z3, z4, z5, z11, z13; 1219 | 1220 | float tmp0 = d0 + d7; 1221 | float tmp7 = d0 - d7; 1222 | float tmp1 = d1 + d6; 1223 | float tmp6 = d1 - d6; 1224 | float tmp2 = d2 + d5; 1225 | float tmp5 = d2 - d5; 1226 | float tmp3 = d3 + d4; 1227 | float tmp4 = d3 - d4; 1228 | 1229 | // Even part 1230 | float tmp10 = tmp0 + tmp3; // phase 2 1231 | float tmp13 = tmp0 - tmp3; 1232 | float tmp11 = tmp1 + tmp2; 1233 | float tmp12 = tmp1 - tmp2; 1234 | 1235 | d0 = tmp10 + tmp11; // phase 3 1236 | d4 = tmp10 - tmp11; 1237 | 1238 | z1 = (tmp12 + tmp13) * 0.707106781f; // c4 1239 | d2 = tmp13 + z1; // phase 5 1240 | d6 = tmp13 - z1; 1241 | 1242 | // Odd part 1243 | tmp10 = tmp4 + tmp5; // phase 2 1244 | tmp11 = tmp5 + tmp6; 1245 | tmp12 = tmp6 + tmp7; 1246 | 1247 | // The rotator is modified from fig 4-8 to avoid extra negations. 1248 | z5 = (tmp10 - tmp12) * 0.382683433f; // c6 1249 | z2 = tmp10 * 0.541196100f + z5; // c2-c6 1250 | z4 = tmp12 * 1.306562965f + z5; // c2+c6 1251 | z3 = tmp11 * 0.707106781f; // c4 1252 | 1253 | z11 = tmp7 + z3; // phase 5 1254 | z13 = tmp7 - z3; 1255 | 1256 | *d5p = z13 + z2; // phase 6 1257 | *d3p = z13 - z2; 1258 | *d1p = z11 + z4; 1259 | *d7p = z11 - z4; 1260 | 1261 | *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; 1262 | } 1263 | 1264 | static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { 1265 | int tmp1 = val < 0 ? -val : val; 1266 | val = val < 0 ? val-1 : val; 1267 | bits[1] = 1; 1268 | while(tmp1 >>= 1) { 1269 | ++bits[1]; 1270 | } 1271 | bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { 1309 | } 1310 | // end0pos = first element in reverse order !=0 1311 | if(end0pos == 0) { 1312 | stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); 1313 | return DU[0]; 1314 | } 1315 | for(i = 1; i <= end0pos; ++i) { 1316 | int startpos = i; 1317 | int nrzeroes; 1318 | unsigned short bits[2]; 1319 | for (; DU[i]==0 && i<=end0pos; ++i) { 1320 | } 1321 | nrzeroes = i-startpos; 1322 | if ( nrzeroes >= 16 ) { 1323 | int lng = nrzeroes>>4; 1324 | int nrmarker; 1325 | for (nrmarker=1; nrmarker <= lng; ++nrmarker) 1326 | stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); 1327 | nrzeroes &= 15; 1328 | } 1329 | stbiw__jpg_calcBits(DU[i], bits); 1330 | stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); 1331 | stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); 1332 | } 1333 | if(end0pos != 63) { 1334 | stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); 1335 | } 1336 | return DU[0]; 1337 | } 1338 | 1339 | static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { 1340 | // Constants that don't pollute global namespace 1341 | static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; 1342 | static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; 1343 | static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; 1344 | static const unsigned char std_ac_luminance_values[] = { 1345 | 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, 1346 | 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, 1347 | 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, 1348 | 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, 1349 | 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, 1350 | 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, 1351 | 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa 1352 | }; 1353 | static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; 1354 | static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; 1355 | static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; 1356 | static const unsigned char std_ac_chrominance_values[] = { 1357 | 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, 1358 | 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, 1359 | 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, 1360 | 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, 1361 | 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, 1362 | 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, 1363 | 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa 1364 | }; 1365 | // Huffman tables 1366 | static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; 1367 | static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; 1368 | static const unsigned short YAC_HT[256][2] = { 1369 | {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1370 | {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1371 | {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1372 | {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1373 | {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1374 | {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1375 | {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1376 | {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1377 | {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1378 | {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1379 | {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1380 | {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1381 | {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1382 | {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1383 | {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, 1384 | {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} 1385 | }; 1386 | static const unsigned short UVAC_HT[256][2] = { 1387 | {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1388 | {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1389 | {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1390 | {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1391 | {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1392 | {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1393 | {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1394 | {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1395 | {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1396 | {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1397 | {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1398 | {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1399 | {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1400 | {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 1401 | {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, 1402 | {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} 1403 | }; 1404 | static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, 1405 | 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; 1406 | static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, 1407 | 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; 1408 | static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, 1409 | 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; 1410 | 1411 | int row, col, i, k; 1412 | float fdtbl_Y[64], fdtbl_UV[64]; 1413 | unsigned char YTable[64], UVTable[64]; 1414 | 1415 | if(!data || !width || !height || comp > 4 || comp < 1) { 1416 | return 0; 1417 | } 1418 | 1419 | quality = quality ? quality : 90; 1420 | quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; 1421 | quality = quality < 50 ? 5000 / quality : 200 - quality * 2; 1422 | 1423 | for(i = 0; i < 64; ++i) { 1424 | int uvti, yti = (YQT[i]*quality+50)/100; 1425 | YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); 1426 | uvti = (UVQT[i]*quality+50)/100; 1427 | UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); 1428 | } 1429 | 1430 | for(row = 0, k = 0; row < 8; ++row) { 1431 | for(col = 0; col < 8; ++col, ++k) { 1432 | fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); 1433 | fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); 1434 | } 1435 | } 1436 | 1437 | // Write Headers 1438 | { 1439 | static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; 1440 | static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; 1441 | const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), 1442 | 3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; 1443 | s->func(s->context, (void*)head0, sizeof(head0)); 1444 | s->func(s->context, (void*)YTable, sizeof(YTable)); 1445 | stbiw__putc(s, 1); 1446 | s->func(s->context, UVTable, sizeof(UVTable)); 1447 | s->func(s->context, (void*)head1, sizeof(head1)); 1448 | s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); 1449 | s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); 1450 | stbiw__putc(s, 0x10); // HTYACinfo 1451 | s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); 1452 | s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); 1453 | stbiw__putc(s, 1); // HTUDCinfo 1454 | s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); 1455 | s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); 1456 | stbiw__putc(s, 0x11); // HTUACinfo 1457 | s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); 1458 | s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); 1459 | s->func(s->context, (void*)head2, sizeof(head2)); 1460 | } 1461 | 1462 | // Encode 8x8 macroblocks 1463 | { 1464 | static const unsigned short fillBits[] = {0x7F, 7}; 1465 | const unsigned char *imageData = (const unsigned char *)data; 1466 | int DCY=0, DCU=0, DCV=0; 1467 | int bitBuf=0, bitCnt=0; 1468 | // comp == 2 is grey+alpha (alpha is ignored) 1469 | int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; 1470 | int x, y, pos; 1471 | for(y = 0; y < height; y += 8) { 1472 | for(x = 0; x < width; x += 8) { 1473 | float YDU[64], UDU[64], VDU[64]; 1474 | for(row = y, pos = 0; row < y+8; ++row) { 1475 | // row >= height => use last input row 1476 | int clamped_row = (row < height) ? row : height - 1; 1477 | int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; 1478 | for(col = x; col < x+8; ++col, ++pos) { 1479 | float r, g, b; 1480 | // if col >= width => use pixel from last input column 1481 | int p = base_p + ((col < width) ? col : (width-1))*comp; 1482 | 1483 | r = imageData[p+0]; 1484 | g = imageData[p+ofsG]; 1485 | b = imageData[p+ofsB]; 1486 | YDU[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128; 1487 | UDU[pos]=-0.16874f*r-0.33126f*g+0.50000f*b; 1488 | VDU[pos]=+0.50000f*r-0.41869f*g-0.08131f*b; 1489 | } 1490 | } 1491 | 1492 | DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); 1493 | DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); 1494 | DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); 1495 | } 1496 | } 1497 | 1498 | // Do the bit alignment of the EOI marker 1499 | stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); 1500 | } 1501 | 1502 | // EOI 1503 | stbiw__putc(s, 0xFF); 1504 | stbiw__putc(s, 0xD9); 1505 | 1506 | return 1; 1507 | } 1508 | 1509 | STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) 1510 | { 1511 | stbi__write_context s; 1512 | stbi__start_write_callbacks(&s, func, context); 1513 | return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); 1514 | } 1515 | 1516 | 1517 | #ifndef STBI_WRITE_NO_STDIO 1518 | STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) 1519 | { 1520 | stbi__write_context s; 1521 | if (stbi__start_write_file(&s,filename)) { 1522 | int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); 1523 | stbi__end_write_file(&s); 1524 | return r; 1525 | } else 1526 | return 0; 1527 | } 1528 | #endif 1529 | 1530 | #endif // STB_IMAGE_WRITE_IMPLEMENTATION 1531 | 1532 | /* Revision history 1533 | 1.11 (2019-08-11) 1534 | 1535 | 1.10 (2019-02-07) 1536 | support utf8 filenames in Windows; fix warnings and platform ifdefs 1537 | 1.09 (2018-02-11) 1538 | fix typo in zlib quality API, improve STB_I_W_STATIC in C++ 1539 | 1.08 (2018-01-29) 1540 | add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter 1541 | 1.07 (2017-07-24) 1542 | doc fix 1543 | 1.06 (2017-07-23) 1544 | writing JPEG (using Jon Olick's code) 1545 | 1.05 ??? 1546 | 1.04 (2017-03-03) 1547 | monochrome BMP expansion 1548 | 1.03 ??? 1549 | 1.02 (2016-04-02) 1550 | avoid allocating large structures on the stack 1551 | 1.01 (2016-01-16) 1552 | STBIW_REALLOC_SIZED: support allocators with no realloc support 1553 | avoid race-condition in crc initialization 1554 | minor compile issues 1555 | 1.00 (2015-09-14) 1556 | installable file IO function 1557 | 0.99 (2015-09-13) 1558 | warning fixes; TGA rle support 1559 | 0.98 (2015-04-08) 1560 | added STBIW_MALLOC, STBIW_ASSERT etc 1561 | 0.97 (2015-01-18) 1562 | fixed HDR asserts, rewrote HDR rle logic 1563 | 0.96 (2015-01-17) 1564 | add HDR output 1565 | fix monochrome BMP 1566 | 0.95 (2014-08-17) 1567 | add monochrome TGA output 1568 | 0.94 (2014-05-31) 1569 | rename private functions to avoid conflicts with stb_image.h 1570 | 0.93 (2014-05-27) 1571 | warning fixes 1572 | 0.92 (2010-08-01) 1573 | casts to unsigned char to fix warnings 1574 | 0.91 (2010-07-17) 1575 | first public release 1576 | 0.90 first internal release 1577 | */ 1578 | 1579 | /* 1580 | ------------------------------------------------------------------------------ 1581 | This software is available under 2 licenses -- choose whichever you prefer. 1582 | ------------------------------------------------------------------------------ 1583 | ALTERNATIVE A - MIT License 1584 | Copyright (c) 2017 Sean Barrett 1585 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1586 | this software and associated documentation files (the "Software"), to deal in 1587 | the Software without restriction, including without limitation the rights to 1588 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1589 | of the Software, and to permit persons to whom the Software is furnished to do 1590 | so, subject to the following conditions: 1591 | The above copyright notice and this permission notice shall be included in all 1592 | copies or substantial portions of the Software. 1593 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1594 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1595 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1596 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1597 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1598 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1599 | SOFTWARE. 1600 | ------------------------------------------------------------------------------ 1601 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1602 | This is free and unencumbered software released into the public domain. 1603 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1604 | software, either in source code form or as a compiled binary, for any purpose, 1605 | commercial or non-commercial, and by any means. 1606 | In jurisdictions that recognize copyright laws, the author or authors of this 1607 | software dedicate any and all copyright interest in the software to the public 1608 | domain. We make this dedication for the benefit of the public at large and to 1609 | the detriment of our heirs and successors. We intend this dedication to be an 1610 | overt act of relinquishment in perpetuity of all present and future rights to 1611 | this software under copyright law. 1612 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1613 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1614 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1615 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1616 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1617 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1618 | ------------------------------------------------------------------------------ 1619 | */ 1620 | -------------------------------------------------------------------------------- /utils/IO/structures.h: -------------------------------------------------------------------------------- 1 | #ifndef IO_STRUCTURES_H 2 | #define IO_STRUCTURES_H 3 | 4 | #include 5 | #include 6 | 7 | struct float2 { float x, y; }; 8 | struct float3 { float x, y, z; }; 9 | struct double3 { double x, y, z; }; 10 | struct uint3 { uint32_t x, y, z; }; 11 | struct uint4 { uint32_t x, y, z, w; }; 12 | struct uchar3 { uint8_t r, g, b;}; 13 | 14 | struct geometry 15 | { 16 | std::vector vertices; 17 | std::vector normals; 18 | std::vector texcoords; 19 | std::vector rgb; 20 | std::vector triangles; 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /utils/IO/tinyply.cpp: -------------------------------------------------------------------------------- 1 | // This file exists to create a nice static or shared library via cmake 2 | // but can otherwise be omitted if you prefer to compile tinyply 3 | // directly into your own project. 4 | #define TINYPLY_IMPLEMENTATION 5 | #include "tinyply.h" 6 | -------------------------------------------------------------------------------- /utils/IO/tinyply.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tinyply 2.2 (https://github.com/ddiakopoulos/tinyply) 3 | * 4 | * A single-header, zero-dependency (except the C++ STL) public domain implementation 5 | * of the PLY mesh file format. Requires C++11; errors are handled through exceptions. 6 | * 7 | * This software is in the public domain. Where that dedication is not 8 | * recognized, you are granted a perpetual, irrevocable license to copy, 9 | * distribute, and modify this file as you see fit. 10 | * 11 | * Authored by Dimitri Diakopoulos (http://www.dimitridiakopoulos.com) 12 | * 13 | * tinyply.h may be included in many files, however in a single compiled file, 14 | * the implementation must be created with the following defined 15 | * before including the header. 16 | * #define TINYPLY_IMPLEMENTATION 17 | */ 18 | 19 | //////////////////////// 20 | // tinyply header // 21 | //////////////////////// 22 | 23 | #pragma once 24 | 25 | #ifndef tinyply_h 26 | #define tinyply_h 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace tinyply 37 | { 38 | 39 | enum class Type : uint8_t 40 | { 41 | INVALID, 42 | INT8, 43 | UINT8, 44 | INT16, 45 | UINT16, 46 | INT32, 47 | UINT32, 48 | FLOAT32, 49 | FLOAT64 50 | }; 51 | 52 | struct PropertyInfo 53 | { 54 | int stride; 55 | std::string str; 56 | }; 57 | 58 | static std::map PropertyTable 59 | { 60 | { Type::INT8, { 1, "char" } }, 61 | { Type::UINT8, { 1, "uchar" } }, 62 | { Type::INT16, { 2, "short" } }, 63 | { Type::UINT16, { 2, "ushort" } }, 64 | { Type::INT32, { 4, "int" } }, 65 | { Type::UINT32, { 4, "uint" } }, 66 | { Type::FLOAT32, { 4, "float" } }, 67 | { Type::FLOAT64, { 8, "double" } }, 68 | { Type::INVALID, { 0, "INVALID" } } 69 | }; 70 | 71 | class Buffer 72 | { 73 | uint8_t * alias{ nullptr }; 74 | struct delete_array { void operator()(uint8_t * p) { delete[] p; } }; 75 | std::unique_ptr data; 76 | size_t size; 77 | public: 78 | Buffer() {}; 79 | Buffer(const size_t size) : data(new uint8_t[size], delete_array()), size(size) { alias = data.get(); } // allocating 80 | Buffer(uint8_t * ptr) { alias = ptr; } // non-allocating, todo: set size? 81 | uint8_t * get() { return alias; } 82 | size_t size_bytes() const { return size; } 83 | }; 84 | 85 | struct PlyData 86 | { 87 | Type t; 88 | size_t count; 89 | Buffer buffer; 90 | bool isList; 91 | }; 92 | 93 | struct PlyProperty 94 | { 95 | PlyProperty(std::istream & is); 96 | PlyProperty(Type type, std::string & _name) : name(_name), propertyType(type) {} 97 | PlyProperty(Type list_type, Type prop_type, std::string & _name, size_t list_count) 98 | : name(_name), propertyType(prop_type), isList(true), listType(list_type), listCount(list_count) {} 99 | std::string name; 100 | Type propertyType; 101 | bool isList{ false }; 102 | Type listType{ Type::INVALID }; 103 | size_t listCount{ 0 }; 104 | }; 105 | 106 | struct PlyElement 107 | { 108 | PlyElement(std::istream & istream); 109 | PlyElement(const std::string & _name, size_t count) : name(_name), size(count) {} 110 | std::string name; 111 | size_t size; 112 | std::vector properties; 113 | }; 114 | 115 | struct PlyFile 116 | { 117 | struct PlyFileImpl; 118 | std::unique_ptr impl; 119 | 120 | PlyFile(); 121 | ~PlyFile(); 122 | 123 | /* 124 | * The ply format requires an ascii header. This can be used to determine at 125 | * runtime which properties or elements exist in the file. Limited validation of the 126 | * header is performed; it is assumed the header correctly reflects the contents of the 127 | * payload. This function may throw. Returns true on success, false on failure. 128 | */ 129 | bool parse_header(std::istream & is); 130 | 131 | /* 132 | * Execute a read operation. Data must be requested via `request_properties_from_element(...)` 133 | * prior to calling this function. 134 | */ 135 | void read(std::istream & is); 136 | 137 | /* 138 | * `write` performs no validation and assumes that the data passed into 139 | * `add_properties_to_element` is well-formed. 140 | */ 141 | void write(std::ostream & os, bool isBinary); 142 | 143 | /* 144 | * These functions are valid after a call to `parse_header(...)`. In the case of 145 | * writing, get_comments() may also be used to add new comments to the ply header. 146 | */ 147 | std::vector get_elements() const; 148 | std::vector get_info() const; 149 | std::vector & get_comments(); 150 | 151 | /* 152 | * In the general case where |list_size_hint| is zero, `read` performs a two-pass 153 | * parse to support variable length lists. The most general use of the 154 | * ply format is storing triangle meshes. When this fact is known a-priori, we can pass 155 | * an expected list length that will apply to this element. Doing so results in an up-front 156 | * memory allocation and a single-pass import, a 2x performance optimization. 157 | */ 158 | std::shared_ptr request_properties_from_element(const std::string & elementKey, 159 | const std::initializer_list propertyKeys, const uint32_t list_size_hint = 0); 160 | 161 | void add_properties_to_element(const std::string & elementKey, 162 | const std::initializer_list propertyKeys, 163 | const Type type, 164 | const size_t count, 165 | uint8_t * data, 166 | const Type listType, 167 | const size_t listCount); 168 | }; 169 | 170 | } // end namespace tinyply 171 | 172 | #endif // end tinyply_h 173 | 174 | //////////////////////////////// 175 | // tinyply implementation // 176 | //////////////////////////////// 177 | 178 | #ifdef TINYPLY_IMPLEMENTATION 179 | 180 | #include 181 | #include 182 | #include 183 | #include 184 | #include 185 | 186 | using namespace tinyply; 187 | using namespace std; 188 | 189 | template inline T2 endian_swap(const T & v) { return v; } 190 | template<> inline uint16_t endian_swap(const uint16_t & v) { return (v << 8) | (v >> 8); } 191 | template<> inline uint32_t endian_swap(const uint32_t & v) { return (v << 24) | ((v << 8) & 0x00ff0000) | ((v >> 8) & 0x0000ff00) | (v >> 24); } 192 | template<> inline uint64_t endian_swap(const uint64_t & v) 193 | { 194 | return (((v & 0x00000000000000ffLL) << 56) | 195 | ((v & 0x000000000000ff00LL) << 40) | 196 | ((v & 0x0000000000ff0000LL) << 24) | 197 | ((v & 0x00000000ff000000LL) << 8) | 198 | ((v & 0x000000ff00000000LL) >> 8) | 199 | ((v & 0x0000ff0000000000LL) >> 24) | 200 | ((v & 0x00ff000000000000LL) >> 40) | 201 | ((v & 0xff00000000000000LL) >> 56)); 202 | } 203 | template<> inline int16_t endian_swap(const int16_t & v) { uint16_t r = endian_swap(*(uint16_t*)&v); return *(int16_t*)&r; } 204 | template<> inline int32_t endian_swap(const int32_t & v) { uint32_t r = endian_swap(*(uint32_t*)&v); return *(int32_t*)&r; } 205 | template<> inline int64_t endian_swap(const int64_t & v) { uint64_t r = endian_swap(*(uint64_t*)&v); return *(int64_t*)&r; } 206 | template<> inline float endian_swap(const uint32_t & v) { union { float f; uint32_t i; }; i = endian_swap(v); return f; } 207 | template<> inline double endian_swap(const uint64_t & v) { union { double d; uint64_t i; }; i = endian_swap(v); return d; } 208 | 209 | inline uint32_t hash_fnv1a(const std::string & str) 210 | { 211 | static const uint32_t fnv1aBase32 = 0x811C9DC5u; 212 | static const uint32_t fnv1aPrime32 = 0x01000193u; 213 | uint32_t result = fnv1aBase32; 214 | for (auto & c : str) { result ^= static_cast(c); result *= fnv1aPrime32; } 215 | return result; 216 | } 217 | 218 | inline Type property_type_from_string(const std::string & t) 219 | { 220 | if (t == "int8" || t == "char") return Type::INT8; 221 | else if (t == "uint8" || t == "uchar") return Type::UINT8; 222 | else if (t == "int16" || t == "short") return Type::INT16; 223 | else if (t == "uint16" || t == "ushort") return Type::UINT16; 224 | else if (t == "int32" || t == "int") return Type::INT32; 225 | else if (t == "uint32" || t == "uint") return Type::UINT32; 226 | else if (t == "float32" || t == "float") return Type::FLOAT32; 227 | else if (t == "float64" || t == "double") return Type::FLOAT64; 228 | return Type::INVALID; 229 | } 230 | 231 | typedef std::function cast_t; 232 | 233 | struct PlyFile::PlyFileImpl 234 | { 235 | struct PlyDataCursor 236 | { 237 | size_t byteOffset{ 0 }; 238 | size_t totalSizeBytes{ 0 }; 239 | }; 240 | 241 | struct ParsingHelper 242 | { 243 | std::shared_ptr data; 244 | std::shared_ptr cursor; 245 | uint32_t list_size_hint; 246 | }; 247 | 248 | struct PropertyLookup 249 | { 250 | ParsingHelper * helper{ nullptr }; 251 | bool skip{ false }; 252 | size_t prop_stride{ 0 }; // precomputed 253 | size_t list_stride{ 0 }; // precomputed 254 | }; 255 | 256 | std::unordered_map userData; 257 | 258 | bool isBinary = false; 259 | bool isBigEndian = false; 260 | std::vector elements; 261 | std::vector comments; 262 | std::vector objInfo; 263 | uint8_t scratch[64]; // large enough for max list size 264 | 265 | void read(std::istream & is); 266 | void write(std::ostream & os, bool isBinary); 267 | 268 | std::shared_ptr request_properties_from_element(const std::string & elementKey, 269 | const std::initializer_list propertyKeys, 270 | const uint32_t list_size_hint); 271 | 272 | void add_properties_to_element(const std::string & elementKey, 273 | const std::initializer_list propertyKeys, 274 | const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount); 275 | 276 | size_t read_property_binary(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is); 277 | size_t read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is); 278 | 279 | std::vector> make_property_lookup_table() 280 | { 281 | std::vector> element_property_lookup; 282 | 283 | for (auto & element : elements) 284 | { 285 | std::vector lookups; 286 | 287 | for (auto & property : element.properties) 288 | { 289 | PropertyLookup f; 290 | 291 | auto cursorIt = userData.find(hash_fnv1a(element.name + property.name)); 292 | if (cursorIt != userData.end()) f.helper = &cursorIt->second; 293 | else f.skip = true; 294 | 295 | f.prop_stride = PropertyTable[property.propertyType].stride; 296 | if (property.isList) f.list_stride = PropertyTable[property.listType].stride; 297 | 298 | lookups.push_back(f); 299 | } 300 | 301 | element_property_lookup.push_back(lookups); 302 | } 303 | 304 | return element_property_lookup; 305 | } 306 | 307 | bool parse_header(std::istream & is); 308 | void parse_data(std::istream & is, bool firstPass); 309 | void read_header_format(std::istream & is); 310 | void read_header_element(std::istream & is); 311 | void read_header_property(std::istream & is); 312 | void read_header_text(std::string line, std::istream & is, std::vector & place, int erase = 0); 313 | 314 | void write_header(std::ostream & os); 315 | void write_ascii_internal(std::ostream & os); 316 | void write_binary_internal(std::ostream & os); 317 | void write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset); 318 | void write_property_binary(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset, const size_t & stride); 319 | }; 320 | 321 | PlyProperty::PlyProperty(std::istream & is) : isList(false) 322 | { 323 | std::string type; 324 | is >> type; 325 | if (type == "list") 326 | { 327 | std::string countType; 328 | is >> countType >> type; 329 | listType = property_type_from_string(countType); 330 | isList = true; 331 | } 332 | propertyType = property_type_from_string(type); 333 | is >> name; 334 | } 335 | 336 | PlyElement::PlyElement(std::istream & is) 337 | { 338 | is >> name >> size; 339 | } 340 | 341 | template inline T ply_read_ascii(std::istream & is) 342 | { 343 | T data; 344 | is >> data; 345 | return data; 346 | } 347 | 348 | template 349 | inline void endian_swap_buffer(uint8_t * data_ptr, const size_t num_bytes, const size_t stride) 350 | { 351 | for (size_t count = 0; count < num_bytes; count += stride) 352 | { 353 | *(reinterpret_cast(data_ptr)) = endian_swap(*(reinterpret_cast(data_ptr))); 354 | data_ptr += stride; 355 | } 356 | } 357 | 358 | template void ply_cast_ascii(void * dest, std::istream & is) 359 | { 360 | *(static_cast(dest)) = ply_read_ascii(is); 361 | } 362 | 363 | int64_t find_element(const std::string & key, const std::vector & list) 364 | { 365 | for (size_t i = 0; i < list.size(); i++) if (list[i].name == key) return i; 366 | return -1; 367 | } 368 | 369 | int64_t find_property(const std::string & key, const std::vector & list) 370 | { 371 | for (size_t i = 0; i < list.size(); ++i) if (list[i].name == key) return i; 372 | return -1; 373 | } 374 | 375 | bool PlyFile::PlyFileImpl::parse_header(std::istream & is) 376 | { 377 | std::string line; 378 | while (std::getline(is, line)) 379 | { 380 | std::istringstream ls(line); 381 | std::string token; 382 | ls >> token; 383 | if (token == "ply" || token == "PLY" || token == "") continue; 384 | else if (token == "comment") read_header_text(line, ls, comments, 8); 385 | else if (token == "format") read_header_format(ls); 386 | else if (token == "element") read_header_element(ls); 387 | else if (token == "property") read_header_property(ls); 388 | else if (token == "obj_info") read_header_text(line, ls, objInfo, 9); 389 | else if (token == "end_header") break; 390 | else return false; // unexpected header field 391 | } 392 | return true; 393 | } 394 | 395 | void PlyFile::PlyFileImpl::read_header_text(std::string line, std::istream & is, std::vector& place, int erase) 396 | { 397 | place.push_back((erase > 0) ? line.erase(0, erase) : line); 398 | } 399 | 400 | void PlyFile::PlyFileImpl::read_header_format(std::istream & is) 401 | { 402 | std::string s; 403 | (is >> s); 404 | if (s == "binary_little_endian") isBinary = true; 405 | else if (s == "binary_big_endian") isBinary = isBigEndian = true; 406 | } 407 | 408 | void PlyFile::PlyFileImpl::read_header_element(std::istream & is) 409 | { 410 | elements.emplace_back(is); 411 | } 412 | 413 | void PlyFile::PlyFileImpl::read_header_property(std::istream & is) 414 | { 415 | if (!elements.size()) throw std::runtime_error("no elements defined; file is malformed"); 416 | elements.back().properties.emplace_back(is); 417 | } 418 | 419 | size_t PlyFile::PlyFileImpl::read_property_binary(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is) 420 | { 421 | destOffset += stride; 422 | is.read((char*)dest, stride); 423 | return stride; 424 | } 425 | 426 | size_t PlyFile::PlyFileImpl::read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is) 427 | { 428 | destOffset += stride; 429 | switch (t) 430 | { 431 | case Type::INT8: *((int8_t *)dest) = ply_read_ascii(is); break; 432 | case Type::UINT8: *((uint8_t *)dest) = ply_read_ascii(is); break; 433 | case Type::INT16: ply_cast_ascii(dest, is); break; 434 | case Type::UINT16: ply_cast_ascii(dest, is); break; 435 | case Type::INT32: ply_cast_ascii(dest, is); break; 436 | case Type::UINT32: ply_cast_ascii(dest, is); break; 437 | case Type::FLOAT32: ply_cast_ascii(dest, is); break; 438 | case Type::FLOAT64: ply_cast_ascii(dest, is); break; 439 | case Type::INVALID: throw std::invalid_argument("invalid ply property"); 440 | } 441 | return stride; 442 | } 443 | 444 | void PlyFile::PlyFileImpl::write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset) 445 | { 446 | switch (t) 447 | { 448 | case Type::INT8: os << static_cast(*reinterpret_cast(src)); break; 449 | case Type::UINT8: os << static_cast(*reinterpret_cast(src)); break; 450 | case Type::INT16: os << *reinterpret_cast(src); break; 451 | case Type::UINT16: os << *reinterpret_cast(src); break; 452 | case Type::INT32: os << *reinterpret_cast(src); break; 453 | case Type::UINT32: os << *reinterpret_cast(src); break; 454 | case Type::FLOAT32: os << *reinterpret_cast(src); break; 455 | case Type::FLOAT64: os << *reinterpret_cast(src); break; 456 | case Type::INVALID: throw std::invalid_argument("invalid ply property"); 457 | } 458 | os << " "; 459 | srcOffset += PropertyTable[t].stride; 460 | } 461 | 462 | void PlyFile::PlyFileImpl::write_property_binary(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset, const size_t & stride) 463 | { 464 | os.write((char *)src, stride); 465 | srcOffset += stride; 466 | } 467 | 468 | void PlyFile::PlyFileImpl::read(std::istream & is) 469 | { 470 | std::vector> buffers; 471 | for (auto & entry : userData) buffers.push_back(entry.second.data); 472 | 473 | // Discover if we can allocate up front without parsing the file twice 474 | uint32_t list_hints = 0; 475 | for (auto & b : buffers) for (auto & entry : userData) list_hints += entry.second.list_size_hint; 476 | 477 | // No list hints? Then we need to calculate how much memory to allocate 478 | if (list_hints == 0) parse_data(is, true); 479 | 480 | // Count the number of properties (required for allocation) 481 | // e.g. if we have properties x y and z requested, we ensure 482 | // that their buffer points to the same PlyData 483 | std::unordered_map unique_data_count; 484 | for (auto & ptr : buffers) unique_data_count[ptr.get()] += 1; 485 | 486 | // Since group-requested properties share the same cursor, 487 | // we need to find unique cursors so we only allocate once 488 | std::sort(buffers.begin(), buffers.end()); 489 | buffers.erase(std::unique(buffers.begin(), buffers.end()), buffers.end()); 490 | 491 | // We sorted by ptrs on PlyData, need to remap back onto its cursor in the userData table 492 | for (auto & b : buffers) 493 | { 494 | for (auto & entry : userData) 495 | { 496 | if (entry.second.data == b && b->buffer.get() == nullptr) 497 | { 498 | // If we didn't receive any list hints, it means we did two passes over the 499 | // file to compute the total length of all (potentially) variable-length lists 500 | if (list_hints == 0) 501 | { 502 | b->buffer = Buffer(entry.second.cursor->totalSizeBytes); 503 | } 504 | else 505 | { 506 | // otherwise, we can allocate up front, skipping the first pass. 507 | const size_t list_size_multiplier = (entry.second.data->isList ? entry.second.list_size_hint : 1); 508 | auto bytes_per_property = entry.second.data->count * PropertyTable[entry.second.data->t].stride * list_size_multiplier; 509 | bytes_per_property *= unique_data_count[b.get()]; 510 | b->buffer = Buffer(bytes_per_property); 511 | } 512 | 513 | } 514 | } 515 | } 516 | 517 | // Populate the data 518 | parse_data(is, false); 519 | 520 | if (isBigEndian) 521 | { 522 | for (auto & b : buffers) 523 | { 524 | uint8_t * data_ptr = b->buffer.get(); 525 | const size_t stride = PropertyTable[b->t].stride; 526 | const size_t buffer_size_bytes = b->buffer.size_bytes(); 527 | 528 | switch (b->t) 529 | { 530 | case Type::INT16: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 531 | case Type::UINT16: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 532 | case Type::INT32: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 533 | case Type::UINT32: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 534 | case Type::FLOAT32: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 535 | case Type::FLOAT64: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 536 | } 537 | } 538 | } 539 | } 540 | 541 | void PlyFile::PlyFileImpl::write(std::ostream & os, bool _isBinary) 542 | { 543 | // reset cursors 544 | for (auto & d : userData) { d.second.cursor->byteOffset = 0; } 545 | if (_isBinary) write_binary_internal(os); 546 | else write_ascii_internal(os); 547 | } 548 | 549 | void PlyFile::PlyFileImpl::write_binary_internal(std::ostream & os) 550 | { 551 | isBinary = true; 552 | write_header(os); 553 | 554 | uint8_t listSize[4] = { 0, 0, 0, 0 }; 555 | size_t dummyCount = 0; 556 | 557 | auto element_property_lookup = make_property_lookup_table(); 558 | 559 | size_t element_idx = 0; 560 | for (auto & e : elements) 561 | { 562 | for (size_t i = 0; i < e.size; ++i) 563 | { 564 | size_t property_index = 0; 565 | for (auto & p : e.properties) 566 | { 567 | auto & f = element_property_lookup[element_idx][property_index]; 568 | auto * helper = f.helper; 569 | 570 | if (p.isList) 571 | { 572 | std::memcpy(listSize, &p.listCount, sizeof(uint32_t)); 573 | write_property_binary(p.listType, os, listSize, dummyCount, f.list_stride); 574 | write_property_binary(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride * p.listCount); 575 | } 576 | else 577 | { 578 | write_property_binary(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride); 579 | } 580 | property_index++; 581 | } 582 | } 583 | element_idx++; 584 | } 585 | } 586 | 587 | void PlyFile::PlyFileImpl::write_ascii_internal(std::ostream & os) 588 | { 589 | write_header(os); 590 | 591 | for (auto & e : elements) 592 | { 593 | for (size_t i = 0; i < e.size; ++i) 594 | { 595 | for (auto & p : e.properties) 596 | { 597 | auto & helper = userData[hash_fnv1a(e.name + p.name)]; 598 | if (p.isList) 599 | { 600 | os << p.listCount << " "; 601 | for (int j = 0; j < p.listCount; ++j) 602 | { 603 | write_property_ascii(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset); 604 | } 605 | } 606 | else 607 | { 608 | write_property_ascii(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset); 609 | } 610 | } 611 | os << "\n"; 612 | } 613 | } 614 | } 615 | 616 | void PlyFile::PlyFileImpl::write_header(std::ostream & os) 617 | { 618 | const std::locale & fixLoc = std::locale("C"); 619 | os.imbue(fixLoc); 620 | 621 | os << "ply\n"; 622 | if (isBinary) os << ((isBigEndian) ? "format binary_big_endian 1.0" : "format binary_little_endian 1.0") << "\n"; 623 | else os << "format ascii 1.0\n"; 624 | 625 | for (const auto & comment : comments) os << "comment " << comment << "\n"; 626 | 627 | for (auto & e : elements) 628 | { 629 | os << "element " << e.name << " " << e.size << "\n"; 630 | for (const auto & p : e.properties) 631 | { 632 | if (p.isList) 633 | { 634 | os << "property list " << PropertyTable[p.listType].str << " " 635 | << PropertyTable[p.propertyType].str << " " << p.name << "\n"; 636 | } 637 | else 638 | { 639 | os << "property " << PropertyTable[p.propertyType].str << " " << p.name << "\n"; 640 | } 641 | } 642 | } 643 | os << "end_header\n"; 644 | } 645 | 646 | std::shared_ptr PlyFile::PlyFileImpl::request_properties_from_element(const std::string & elementKey, 647 | const std::initializer_list propertyKeys, 648 | const uint32_t list_size_hint) 649 | { 650 | // Each key in `propertyKey` gets an entry into the userData map (keyed by a hash of 651 | // element name and property name), but groups of properties (requested from the 652 | // public api through this function) all share the same `ParsingHelper`. When it comes 653 | // time to .read(), we check the number of unique PlyData shared pointers 654 | // and allocate a single buffer that will be used by each individual property. 655 | ParsingHelper helper; 656 | helper.data = std::make_shared(); 657 | helper.data->count = 0; 658 | helper.data->isList = false; 659 | helper.data->t = Type::INVALID; 660 | helper.cursor = std::make_shared(); 661 | helper.list_size_hint = list_size_hint; 662 | 663 | if (elements.empty()) throw std::runtime_error("header had no elements defined. malformed file?"); 664 | if (elementKey.empty()) throw std::invalid_argument("`elementKey` argument is empty"); 665 | if (!propertyKeys.size()) throw std::invalid_argument("`propertyKeys` argument is empty"); 666 | 667 | const int64_t elementIndex = find_element(elementKey, elements); 668 | 669 | std::vector keys_not_found; 670 | 671 | // Sanity check if the user requested element is in the pre-parsed header 672 | if (elementIndex >= 0) 673 | { 674 | // We found the element 675 | const PlyElement & element = elements[elementIndex]; 676 | 677 | helper.data->count = element.size; 678 | 679 | // Find each of the keys 680 | for (auto key : propertyKeys) 681 | { 682 | const int64_t propertyIndex = find_property(key, element.properties); 683 | if (propertyIndex >= 0) 684 | { 685 | // We found the property 686 | const PlyProperty & property = element.properties[propertyIndex]; 687 | helper.data->t = property.propertyType; 688 | helper.data->isList = property.isList; 689 | auto result = userData.insert(std::pair(hash_fnv1a(element.name + property.name), helper)); 690 | if (result.second == false) 691 | { 692 | throw std::invalid_argument("element-property key has already been requested: " + hash_fnv1a(element.name + property.name)); 693 | } 694 | } 695 | else keys_not_found.push_back(key); 696 | } 697 | } 698 | else throw std::invalid_argument("the element key was not found in the header: " + elementKey); 699 | 700 | if (keys_not_found.size()) 701 | { 702 | std::stringstream ss; 703 | for (auto & str : keys_not_found) ss << str << ", "; 704 | throw std::invalid_argument("the following property keys were not found in the header: " + ss.str()); 705 | } 706 | return helper.data; 707 | } 708 | 709 | void PlyFile::PlyFileImpl::add_properties_to_element(const std::string & elementKey, 710 | const std::initializer_list propertyKeys, 711 | const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount) 712 | { 713 | ParsingHelper helper; 714 | helper.data = std::make_shared(); 715 | helper.data->count = count; 716 | helper.data->t = type; 717 | helper.data->buffer = Buffer(data); 718 | helper.cursor = std::make_shared(); 719 | 720 | auto create_property_on_element = [&](PlyElement & e) 721 | { 722 | for (auto key : propertyKeys) 723 | { 724 | PlyProperty newProp = (listType == Type::INVALID) ? PlyProperty(type, key) : PlyProperty(listType, type, key, listCount); 725 | userData.insert(std::pair(hash_fnv1a(elementKey + key), helper)); 726 | e.properties.push_back(newProp); 727 | } 728 | }; 729 | 730 | const int64_t idx = find_element(elementKey, elements); 731 | if (idx >= 0) 732 | { 733 | PlyElement & e = elements[idx]; 734 | create_property_on_element(e); 735 | } 736 | else 737 | { 738 | PlyElement newElement = (listType == Type::INVALID) ? PlyElement(elementKey, count) : PlyElement(elementKey, count); 739 | create_property_on_element(newElement); 740 | elements.push_back(newElement); 741 | } 742 | } 743 | 744 | void PlyFile::PlyFileImpl::parse_data(std::istream & is, bool firstPass) 745 | { 746 | std::function read; 747 | std::function skip; 748 | 749 | const auto start = is.tellg(); 750 | 751 | size_t listSize = 0; 752 | size_t dummyCount = 0; 753 | std::string skip_ascii_buffer; 754 | 755 | // Special case mirroring read_property_binary but for list types; this 756 | // has an additional big endian check to flip the data in place immediately 757 | // after reading. We do this as a performance optimization; endian flipping is 758 | // done on regular properties as a post-process after reading (also for optimization) 759 | // but we need the correct little-endian list count as we read the file. 760 | auto read_list_binary = [this](const Type & t, void * dst, size_t & destOffset, std::istream & _is) 761 | { 762 | const size_t stride = PropertyTable[t].stride; // @todo - this is already precomputed 763 | destOffset += stride; 764 | _is.read((char*)dst, stride); 765 | 766 | if (isBigEndian) 767 | { 768 | switch (t) 769 | { 770 | case Type::INT16: endian_swap(*(int16_t*)dst); break; 771 | case Type::UINT16: endian_swap(*(uint16_t*)dst); break; 772 | case Type::INT32: endian_swap(*(int32_t*)dst); break; 773 | case Type::UINT32: endian_swap(*(uint32_t*)dst); break; 774 | } 775 | } 776 | 777 | return stride; 778 | }; 779 | 780 | if (isBinary) 781 | { 782 | read = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) 783 | { 784 | if (!p.isList) 785 | { 786 | read_property_binary(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is); 787 | } 788 | else 789 | { 790 | read_list_binary(p.listType, &listSize, dummyCount, _is); // the list size 791 | read_property_binary(p.propertyType, f.prop_stride * listSize, dest + destOffset, destOffset, _is); // properties in list 792 | } 793 | }; 794 | skip = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, std::istream & _is) 795 | { 796 | if (!p.isList) 797 | { 798 | _is.read((char*)scratch, f.prop_stride); 799 | return f.prop_stride; 800 | } 801 | read_list_binary(p.listType, &listSize, dummyCount, _is); // the list size (does not count for memory alloc) 802 | return read_property_binary(p.propertyType, f.prop_stride * listSize, scratch, dummyCount, _is); 803 | }; 804 | } 805 | else 806 | { 807 | read = [this, &listSize, &dummyCount](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) 808 | { 809 | if (!p.isList) 810 | { 811 | read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is); 812 | } 813 | else 814 | { 815 | read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size 816 | for (size_t i = 0; i < listSize; ++i) 817 | { 818 | read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is); 819 | } 820 | } 821 | }; 822 | skip = [this, &listSize, &dummyCount, &skip_ascii_buffer](PropertyLookup & f, const PlyProperty & p, std::istream & _is) 823 | { 824 | skip_ascii_buffer.clear(); 825 | if (p.isList) 826 | { 827 | read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size 828 | for (size_t i = 0; i < listSize; ++i) _is >> skip_ascii_buffer; // properties in list 829 | return listSize * f.prop_stride; 830 | } 831 | _is >> skip_ascii_buffer; 832 | return f.prop_stride; 833 | }; 834 | } 835 | 836 | auto element_property_lookup = make_property_lookup_table(); 837 | 838 | size_t element_idx = 0; 839 | size_t property_index = 0; 840 | for (auto & element : elements) 841 | { 842 | for (size_t count = 0; count < element.size; ++count) 843 | { 844 | property_index = 0; 845 | for (auto & property : element.properties) 846 | { 847 | auto & f = element_property_lookup[element_idx][property_index]; 848 | if (!f.skip) 849 | { 850 | auto * helper = f.helper; 851 | if (firstPass) helper->cursor->totalSizeBytes += skip(f, property, is); 852 | else read(f, property, helper->data->buffer.get(), helper->cursor->byteOffset, is); 853 | } 854 | else skip(f, property, is); 855 | property_index++; 856 | } 857 | } 858 | element_idx++; 859 | } 860 | 861 | // Reset istream reader to the beginning 862 | if (firstPass) is.seekg(start, is.beg); 863 | } 864 | 865 | // Wrap the public interface: 866 | 867 | PlyFile::PlyFile() { impl.reset(new PlyFileImpl()); }; 868 | PlyFile::~PlyFile() { }; 869 | bool PlyFile::parse_header(std::istream & is) { return impl->parse_header(is); } 870 | void PlyFile::read(std::istream & is) { return impl->read(is); } 871 | void PlyFile::write(std::ostream & os, bool isBinary) { return impl->write(os, isBinary); } 872 | std::vector PlyFile::get_elements() const { return impl->elements; } 873 | std::vector & PlyFile::get_comments() { return impl->comments; } 874 | std::vector PlyFile::get_info() const { return impl->objInfo; } 875 | std::shared_ptr PlyFile::request_properties_from_element(const std::string & elementKey, 876 | const std::initializer_list propertyKeys, 877 | const uint32_t list_size_hint) 878 | { 879 | return impl->request_properties_from_element(elementKey, propertyKeys, list_size_hint); 880 | } 881 | void PlyFile::add_properties_to_element(const std::string & elementKey, 882 | const std::initializer_list propertyKeys, 883 | const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount) 884 | { 885 | return impl->add_properties_to_element(elementKey, propertyKeys, type, count, data, listType, listCount); 886 | } 887 | 888 | #endif // end TINYPLY_IMPLEMENTATION -------------------------------------------------------------------------------- /utils/IO/writePLY.h: -------------------------------------------------------------------------------- 1 | #ifndef IO_WRITEPLY_H 2 | #define IO_WRITEPLY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define TINYPLY_IMPLEMENTATION 10 | #include "tinyply.h" 11 | #include "structures.h" 12 | 13 | using namespace tinyply; 14 | 15 | void writePLY(const std::string & filepath, 16 | Eigen::MatrixXd &V, 17 | Eigen::MatrixXi &F, 18 | Eigen::MatrixXd &N, 19 | Eigen::MatrixXi &RGB, 20 | bool write_in_ascii) 21 | { 22 | bool verbose = false; 23 | 24 | try 25 | { 26 | tinyply:PlyFile object_to_write; 27 | geometry cube; 28 | 29 | if (V.cols() != 0) { 30 | for (int i=0; i(cube.vertices.data()), Type::INVALID, 0); 34 | } else { 35 | throw std::invalid_argument( "asked to write a set of empty vertices" ); 36 | } 37 | 38 | // add normals 39 | if (N.cols() == V.cols()) { 40 | for (int i=0; i(cube.normals.data()), Type::INVALID, 0); 45 | } 46 | 47 | // add color on vertices 48 | if (RGB.cols() == V.cols()) { 49 | for (int i=0; i(cube.rgb.data()), Type::INVALID, 0); 54 | } 55 | 56 | // add faces as an output 57 | if (F.cols() != 0) { 58 | for (int i=0; i(cube.triangles.data()), Type::UINT8, 3); 63 | } 64 | 65 | // add comment 66 | object_to_write.get_comments().push_back("generated by tinyply 2.2"); 67 | 68 | if (write_in_ascii) { 69 | std::filebuf fb_ascii; 70 | fb_ascii.open(filepath, std::ios::out); 71 | std::ostream outstream_ascii(&fb_ascii); 72 | if (outstream_ascii.fail()) throw std::runtime_error("failed to open " + filepath); 73 | 74 | object_to_write.write(outstream_ascii, false); 75 | } else { 76 | std::filebuf fb_binary; 77 | fb_binary.open(filepath, std::ios::out | std::ios::binary); 78 | std::ostream outstream_binary(&fb_binary); 79 | if (outstream_binary.fail()) throw std::runtime_error("failed to open " + filepath); 80 | 81 | object_to_write.write(outstream_binary, true); 82 | } 83 | 84 | } 85 | catch (const std::exception & e) 86 | { 87 | std::cerr << "Caught tinyply exception: " << e.what() << std::endl; 88 | } 89 | } 90 | 91 | void writePLY(const std::string & filepath, 92 | Eigen::MatrixXd &V, 93 | Eigen::MatrixXi &F, 94 | Eigen::MatrixXd &N, 95 | bool write_in_ascii) 96 | { 97 | Eigen::MatrixXi RGB; 98 | writePLY(filepath, V, F, N, RGB, write_in_ascii); 99 | } 100 | 101 | void writePLY(const std::string & filepath, 102 | Eigen::MatrixXd &V, 103 | Eigen::MatrixXi &F, 104 | bool write_in_ascii) 105 | { 106 | Eigen::MatrixXd N; 107 | Eigen::MatrixXi RGB; 108 | writePLY(filepath, V, F, N, RGB, write_in_ascii); 109 | } 110 | 111 | void writePLY(const std::string & filepath, 112 | Eigen::MatrixXd &V, 113 | Eigen::MatrixXd &N, 114 | bool write_in_ascii) 115 | { 116 | Eigen::MatrixXi F; 117 | Eigen::MatrixXi RGB; 118 | writePLY(filepath, V, F, N, RGB,write_in_ascii); 119 | } 120 | 121 | #endif -------------------------------------------------------------------------------- /utils/IO/writePNG.h: -------------------------------------------------------------------------------- 1 | // This file is part of libigl, a simple c++ geometry processing library. 2 | // 3 | // Copyright (C) 2016 Daniele Panozzo 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public License 6 | // v. 2.0. If a copy of the MPL was not distributed with this file, You can 7 | // obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | #ifndef WRITE_PNG_H 10 | #define WRITE_PNG_H 11 | 12 | // Explicit template instantiation 13 | // generated by autoexplicit.sh 14 | 15 | #include "stb_image_write.h" 16 | #include 17 | #include 18 | 19 | inline bool writePNG( 20 | const Eigen::Matrix& R, 21 | const Eigen::Matrix& G, 22 | const Eigen::Matrix& B, 23 | const Eigen::Matrix& A, 24 | const std::string png_file 25 | ) 26 | { 27 | assert((R.rows() == G.rows()) && (G.rows() == B.rows()) && (B.rows() == A.rows())); 28 | assert((R.cols() == G.cols()) && (G.cols() == B.cols()) && (B.cols() == A.cols())); 29 | 30 | const int comp = 3; // 4 Channels Red, Green, Blue, Alpha 31 | const int stride_in_bytes = R.cols()*comp; // Length of one row in bytes 32 | std::vector data(R.size()*comp,0); // The image itself; 33 | 34 | for (unsigned i = 0; i R_char; 59 | Eigen::Matrix G_char; 60 | Eigen::Matrix B_char; 61 | Eigen::Matrix A_char; 62 | 63 | R_char = (R*255.9).cast (); 64 | G_char = (G*255.9).cast (); 65 | B_char = (B*255.9).cast (); 66 | A_char = (A*255.9).cast (); 67 | 68 | return writePNG(R_char, G_char, B_char, A_char, png_file); 69 | }; 70 | 71 | // overload the functions 72 | inline bool writePNG( 73 | const Eigen::MatrixXd& R, 74 | const Eigen::MatrixXd& G, 75 | const Eigen::MatrixXd& B, 76 | const std::string png_file 77 | ) 78 | { 79 | Eigen::MatrixXd A = Eigen::MatrixXd::Ones(R.rows(), R.cols()); 80 | 81 | return writePNG(R, G, B, A, png_file); 82 | }; 83 | 84 | inline bool writePNG( 85 | const Eigen::MatrixXd& gray_map, 86 | const std::string png_file 87 | ) 88 | { 89 | Eigen::MatrixXd R = gray_map; 90 | Eigen::MatrixXd G = gray_map; 91 | Eigen::MatrixXd B = gray_map; 92 | Eigen::MatrixXd A = Eigen::MatrixXd::Zero(gray_map.rows(), gray_map.cols()); 93 | 94 | return writePNG(R, G, B, A, png_file); 95 | }; 96 | 97 | inline bool writePNG( 98 | const Eigen::Matrix& binary_map, 99 | const std::string png_file 100 | ) 101 | { 102 | Eigen::MatrixXd R = binary_map.cast (); 103 | Eigen::MatrixXd G = binary_map.cast (); 104 | Eigen::MatrixXd B = binary_map.cast (); 105 | Eigen::MatrixXd A = Eigen::MatrixXd::Ones(binary_map.rows(), binary_map.cols()); 106 | 107 | return writePNG(R, G, B, A, png_file); 108 | }; 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /utils/mesh/computeFacesCentroids.h: -------------------------------------------------------------------------------- 1 | /* 2 | * compute the faces' centroids 3 | * by R. Falque 4 | * 12/02/2020 5 | */ 6 | 7 | #ifndef COMPUTE_CENTROIDS_H 8 | #define COMPUTE_CENTROIDS_H 9 | 10 | #include 11 | #include 12 | 13 | inline Eigen::MatrixXd compute_faces_centroids(Eigen::MatrixXd V, Eigen::MatrixXi F) { 14 | Eigen::MatrixXd F_centroids = Eigen::MatrixXd::Zero(3, F.cols()); 15 | 16 | for (int i=0; i 11 | #include 12 | 13 | inline Eigen::MatrixXd compute_vertices_normals(Eigen::MatrixXd V, Eigen::MatrixXi F) { 14 | Eigen::MatrixXd normals = Eigen::MatrixXd::Zero(3, V.cols()); 15 | Eigen::Vector3d normal_temp, v1, v2; 16 | 17 | for (int i=0; i 11 | 12 | template int sgn(T val) { 13 | return (T(0) < val) - (val < T(0)); 14 | }; 15 | 16 | template bool is_positive(T val) { 17 | return (T(0) < val); 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /utils/visualization/plotCloud.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: R. Falque 3 | * 4 | * plot cloud in polyscope 5 | * by R. Falque 6 | * 26/09/2019 7 | **/ 8 | 9 | #ifndef PLOT_CLOUD_HPP 10 | #define PLOT_CLOUD_HPP 11 | 12 | #include 13 | 14 | #include 15 | 16 | #include "polyscope/point_cloud.h" 17 | #include "polyscope/view.h" 18 | 19 | #ifndef POLYSCOPE_IS_INITIALIZED 20 | #define POLYSCOPE_IS_INITIALIZED 21 | int polyscope_is_initialized { 0 }; 22 | #endif 23 | 24 | // polyscope wrapper 25 | class CloudVisualization 26 | { 27 | private: 28 | std::string cloud_object_name_ = "cloud"; 29 | 30 | public: 31 | 32 | CloudVisualization() 33 | { 34 | init(); 35 | } 36 | 37 | // destructor 38 | ~CloudVisualization() 39 | { 40 | polyscope::removeAllStructures(); 41 | } 42 | 43 | void init() { 44 | if (!polyscope_is_initialized) { 45 | polyscope::init(); 46 | polyscope_is_initialized = 1; 47 | } 48 | 49 | // Options 50 | polyscope::options::autocenterStructures = false; 51 | polyscope::view::windowWidth = 1024; 52 | polyscope::view::windowHeight = 1024; 53 | } 54 | 55 | void add_cloud(const Eigen::MatrixXd & cloud) { 56 | polyscope::registerPointCloud(cloud_object_name_, cloud.transpose()); 57 | polyscope::getPointCloud(cloud_object_name_)->setPointColor(glm::vec3{0.1, 0.1, 1}); 58 | polyscope::view::resetCameraToHomeView(); 59 | } 60 | 61 | void add_color(const Eigen::MatrixXd & colors, std::string color_name) { 62 | if (colors.rows() != 0){ 63 | polyscope::getPointCloud(cloud_object_name_)->addColorQuantity(color_name, colors.transpose()); 64 | polyscope::getPointCloud(cloud_object_name_)->getQuantity(color_name)->setEnabled(true); 65 | } 66 | } 67 | 68 | void screenshot(std::string screenshot_path) { 69 | polyscope::screenshot(screenshot_path, false); 70 | } 71 | 72 | void show() { 73 | polyscope::show(); 74 | //polyscope::removeAllStructures(); // move to destructor? 75 | } 76 | }; 77 | 78 | inline bool plot_cloud (const Eigen::MatrixXd& V) { 79 | CloudVisualization viz; 80 | viz.add_cloud(V); 81 | viz.show(); 82 | return true; 83 | }; 84 | 85 | inline bool plot_cloud (const Eigen::MatrixXd& V, const Eigen::MatrixXd& color) { 86 | CloudVisualization viz; 87 | viz.add_cloud(V); 88 | viz.add_color(color, "highlight"); 89 | viz.show(); 90 | return true; 91 | }; 92 | 93 | inline bool plot_cloud (const Eigen::MatrixXd& V, const Eigen::MatrixXd& color, std::string screenshot_path) { 94 | CloudVisualization viz; 95 | viz.add_cloud(V); 96 | viz.add_color(color, "highlight"); 97 | viz.screenshot(screenshot_path); 98 | viz.show(); 99 | return true; 100 | }; 101 | 102 | inline bool screenshot_cloud(const Eigen::MatrixXd& V, const Eigen::MatrixXd& color, std::string screenshot_path) { 103 | CloudVisualization viz; 104 | viz.add_cloud(V); 105 | viz.add_color(color, "highlight"); 106 | viz.screenshot(screenshot_path); 107 | return true; 108 | }; 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /utils/visualization/plotMesh.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: R. Falque 3 | * 4 | * plot mesh in polyscope 5 | * by R. Falque 6 | * 26/09/2019 7 | **/ 8 | 9 | #ifndef PLOT_MESH_HPP 10 | #define PLOT_MESH_HPP 11 | 12 | #include 13 | 14 | #include 15 | 16 | #include "polyscope/surface_mesh.h" 17 | #include "polyscope/view.h" 18 | 19 | #ifndef POLYSCOPE_IS_INITIALIZED 20 | #define POLYSCOPE_IS_INITIALIZED 21 | int polyscope_is_initialized { 0 }; 22 | #endif 23 | 24 | // polyscope wrapper 25 | class MeshVisualization 26 | { 27 | private: 28 | std::string mesh_object_name_ = "Mesh"; 29 | 30 | public: 31 | 32 | MeshVisualization() 33 | { 34 | init(); 35 | } 36 | 37 | // destructor 38 | ~MeshVisualization() 39 | { 40 | polyscope::removeAllStructures(); 41 | } 42 | 43 | void init() { 44 | 45 | if (!polyscope_is_initialized) { 46 | polyscope::init(); 47 | polyscope_is_initialized = 1; 48 | } 49 | 50 | // Options 51 | polyscope::options::autocenterStructures = true; 52 | polyscope::view::windowWidth = 1024; 53 | polyscope::view::windowHeight = 1024; 54 | 55 | 56 | } 57 | 58 | void add_mesh(const Eigen::MatrixXd& vertices, const Eigen::MatrixXi& faces) { 59 | polyscope::registerSurfaceMesh(mesh_object_name_, vertices.transpose(), faces.transpose()); 60 | polyscope::getSurfaceMesh(mesh_object_name_)->setSurfaceColor(glm::vec3{0.1, 0.1, 1}); 61 | polyscope::view::resetCameraToHomeView(); 62 | //polyscope::view::upDir = polyscope::view::UpDir::ZUp; 63 | } 64 | 65 | void add_color(const Eigen::MatrixXd & color, std::string color_name) { 66 | if (color.cols() != 0){ 67 | polyscope::getSurfaceMesh(mesh_object_name_)->addVertexColorQuantity(color_name, color.transpose()); 68 | polyscope::getSurfaceMesh(mesh_object_name_)->getQuantity(color_name)->setEnabled(true); 69 | } 70 | } 71 | 72 | void screenshot(std::string screenshot_path) { 73 | polyscope::screenshot(screenshot_path, false); 74 | } 75 | 76 | void show() { 77 | polyscope::show(); 78 | } 79 | }; 80 | 81 | 82 | inline bool plot_mesh (const Eigen::MatrixXd& vertices, const Eigen::MatrixXi& faces) { 83 | MeshVisualization viz; 84 | viz.add_mesh(vertices, faces); 85 | viz.show(); 86 | return true; 87 | }; 88 | 89 | inline bool plot_mesh (const Eigen::MatrixXd& vertices, const Eigen::MatrixXi& faces, const Eigen::MatrixXd& color) { 90 | MeshVisualization viz; 91 | viz.add_mesh(vertices, faces); 92 | viz.add_color(color, "highlight"); 93 | viz.show(); 94 | return true; 95 | }; 96 | 97 | inline bool plot_mesh (const Eigen::MatrixXd& vertices, const Eigen::MatrixXi& faces, const Eigen::MatrixXd& color, std::string screenshot_path) { 98 | MeshVisualization viz; 99 | viz.add_mesh(vertices, faces); 100 | viz.add_color(color, "highlight"); 101 | viz.screenshot(screenshot_path); 102 | viz.show(); 103 | return true; 104 | }; 105 | 106 | inline bool screenshot_mesh(const Eigen::MatrixXd& vertices, const Eigen::MatrixXi& faces, const Eigen::MatrixXd& color, std::string screenshot_path) { 107 | MeshVisualization viz; 108 | viz.add_mesh(vertices, faces); 109 | viz.add_color(color, "highlight"); 110 | viz.screenshot(screenshot_path); 111 | return true; 112 | }; 113 | 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /utils/visualization/plotTwoMeshes.h: -------------------------------------------------------------------------------- 1 | /** 2 | * visualize correspondences 3 | * 4 | * by R. Falque 5 | * 17/01/2020 6 | **/ 7 | 8 | #ifndef PLOT_TWO_MESH_H 9 | #define PLOT_TWO_MESH_H 10 | 11 | #include 12 | #include 13 | 14 | 15 | #include "polyscope/polyscope.h" 16 | #include "polyscope/messages.h" 17 | #include "polyscope/surface_mesh.h" 18 | 19 | #ifndef POLYSCOPE_IS_INITIALIZED 20 | #define POLYSCOPE_IS_INITIALIZED 21 | int polyscope_is_initialized { 0 }; 22 | #endif 23 | 24 | inline bool plot_two_meshes(const Eigen::MatrixXd & source_V, 25 | const Eigen::MatrixXi & source_F, 26 | const Eigen::MatrixXd & target_V, 27 | const Eigen::MatrixXi & target_F) 28 | { 29 | 30 | if (!polyscope_is_initialized) { 31 | polyscope::init(); 32 | polyscope_is_initialized = 1; 33 | } 34 | 35 | polyscope::options::autocenterStructures = false; 36 | polyscope::view::style = polyscope::view::NavigateStyle::Free; 37 | polyscope::view::upDir = polyscope::view::UpDir::ZUp; 38 | 39 | polyscope::view::windowWidth = 1024; 40 | polyscope::view::windowHeight = 1024; 41 | 42 | polyscope::registerSurfaceMesh("first mesh", source_V.transpose(), source_F.transpose()); 43 | polyscope::getSurfaceMesh("first mesh")->setSurfaceColor(glm::vec3{0.1, 0.1, 1}); 44 | 45 | polyscope::registerSurfaceMesh("second mesh", target_V.transpose(), target_F.transpose()); 46 | polyscope::getSurfaceMesh("second mesh")->setSurfaceColor(glm::vec3{1, 0.1, 0.1}); 47 | 48 | polyscope::show(); 49 | 50 | polyscope::removeAllStructures(); 51 | return true; 52 | }; 53 | 54 | 55 | #endif 56 | --------------------------------------------------------------------------------