├── .DS_Store ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── makefile ├── panorama.cpp └── samples └── equirectangular_panorama.jpg /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peakvisor/panorama/d51bc6551ad4ec6e7a20d2b4bd51053aee91ff03/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | photo 2 | 3 | # Compiled Object files 4 | *.slo 5 | *.lo 6 | *.o 7 | *.obj 8 | 9 | # Precompiled Headers 10 | *.gch 11 | *.pch 12 | 13 | # Compiled Dynamic libraries 14 | *.so 15 | *.dylib 16 | *.dll 17 | 18 | # Fortran module files 19 | *.mod 20 | *.smod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | panorama 33 | 34 | # Test images 35 | *.jpg 36 | *.png 37 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "CImg"] 2 | path = CImg 3 | url = https://github.com/dtschump/CImg.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 PeakVisor 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 | # Photo Panorama Converter 2 | 3 | This application converts an equirectangular panorama image into 6 cube faces images. The tool might be very handy when you need to prepare a cube faces panorama for some of web panorama viewers (a.e. Panellum). 4 | 5 | This tool was developed as part of the [PeakVisor](http://peakvisor.com "PeakVisor") service. Please check it out, it is fantastic! Here is the webpage which renders [mountain panoramas](http://peakvisor.com/panorama.html "Mountain Panoramas") 6 | 7 | # How to convert an equirectangular panorama? 8 | 9 | Launch the panorama utility with following parameters: 10 | -i - equirectangular panorama source 11 | -o - output cube faces names 12 | -r - edge length of a cube face (optional) 13 | 14 | ``` 15 | panorama -i ./samples/equirectangular_panorama.jpg -o cube_faces -r 4096 16 | ``` 17 | 18 | For a test you might take the panoramic photo from the samples directory. It was taken with a Panono 360-degrees camera (dimensions 16384x8192) on the way to the [Monte Bregagnino](https://peakvisor.com/peak/bregagnino.html)'s summit (Lake Como, Italy). Depending on your CPU performance it might take from several seconds to a minute. 19 | 20 | Here is a sample webGL panorama viewer based on threeJS (LINK) 21 | 22 | # How does the conversion work? 23 | 24 | For a detailed description of the algorithm (and geometry) behind the tool please refer to this [StackOverflow thread](http://stackoverflow.com/questions/29678510/convert-21-equirectangular-panorama-to-cube-map). Basically it goes through all the pixels of the target image and calculates the related pixel (or it's approximation) in the source panorama. 25 | 26 | Obviously, it decreases the panorama's quality and you'd better avoid this transformation. 27 | 28 | # Requirements 29 | 30 | The program requires the Intel [Threading Building Blocks](https://www.threadingbuildingblocks.org/) (TBB), [libpng](http://www.libpng.org/pub/png/libpng.html), and [libjpeg](http://libjpeg.sourceforge.net/) libraries. 31 | 32 | On ubuntu these can be installed with 33 | ``` 34 | sudo apt-get install libtbb-dev libpng-dev libjpeg-dev 35 | ``` 36 | 37 | # How to build the panorama converter 38 | 39 | Clone the repository with: 40 | ``` 41 | git clone 42 | ``` 43 | 44 | Initialize dependencies: 45 | ``` 46 | git submodule init 47 | git submodule update 48 | ``` 49 | 50 | Check other dependencies (JPG, PNG, Intel TBB). Update paths in the makefile if needed. 51 | 52 | Make the panorama tool 53 | ``` 54 | make 55 | ``` 56 | 57 | That's it! 58 | 59 | # How to use panorama converter as a web service 60 | 61 | We're running this panorama converter on some of our servers and if you are interested in using it as a web service then please let me know: denis@denivip.ru 62 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # the compiler: gcc for C program, define as g++ for C++ 2 | CC = g++ 3 | 4 | # compiler flags: 5 | # -g adds debugging information to the executable file 6 | # -Wall turns on most, but not all, compiler warnings 7 | # -O3 optimizes the application at a maximum level 8 | CFLAGS = -v -Wall -std=c++11 -ffast-math -O3 9 | 10 | # the build target executable: 11 | TARGET = panorama 12 | 13 | # define any directories containing header files other than /usr/include 14 | # 15 | INCLUDES = -I/usr/include -I/usr/local/include -I/usr/X11/include -I/usr/local/Cellar/jpeg/8d/include -I/usr/local/include/log4cplus -I/opt/local/include 16 | 17 | # define the C source files 18 | SRCS = panorama.cpp 19 | 20 | export LIBRARY_PATH=/usr/local/lib 21 | 22 | export DYLD_LIBRARY_PATH=/usr/local/lib/ 23 | 24 | # define the C object files 25 | # 26 | # This uses Suffix Replacement within a macro: 27 | # $(name:string1=string2) 28 | # For each word in 'name' replace 'string1' with 'string2' 29 | # Below we are replacing the suffix .c of all words in the macro SRCS 30 | # with the .o suffix 31 | # 32 | OBJS = $(SRCS:.cpp=.o) 33 | 34 | # define library paths in addition to /usr/lib 35 | # if I wanted to include libraries not in /usr/lib I'd specify 36 | # their path using -Lpath, something like: 37 | LFLAGS = -L/usr/X11/lib -L/usr/local/Cellar/jpeg/8d/lib -L/opt/local/lib 38 | 39 | # define any libraries to link into executable: 40 | # if I want to link in libraries (libx.so or libx.a) I use the -llibname 41 | # option, something like (this will link in libmylib.so and libm.so: 42 | LIBS = -lpthread -lpng -ljpeg -ltbb 43 | 44 | # 45 | # The following part of the makefile is generic; it can be used to 46 | # build any executable just by changing the definitions above and by 47 | # deleting dependencies appended to the file from 'make depend' 48 | # 49 | .PHONY: depend clean 50 | 51 | # typing 'make' will invoke the first target entry in the file 52 | # (in this case the default target entry) 53 | # you can name this target entry anything, but "default" or "all" 54 | # are the most commonly used names by convention 55 | # 56 | all: $(TARGET) 57 | @echo Panorama converter has been compiled 58 | 59 | $(TARGET): $(OBJS) 60 | $(CC) $(CFLAGS) $(INCLUDES) -o $(TARGET) $(OBJS) $(LFLAGS) $(LIBS) 61 | 62 | # this is a suffix replacement rule for building .o's from .c's 63 | # it uses automatic variables $<: the name of the prerequisite of 64 | # the rule(a .c file) and $@: the name of the target of the rule (a .o file) 65 | # (see the gnu make manual section about automatic variables) 66 | .cpp.o: 67 | $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ 68 | 69 | clean: 70 | $(RM) *~ *.o *.png *.jpg *.log $(TARGET) 71 | 72 | depend: $(SRCS) 73 | makedepend $(INCLUDES) $^ 74 | 75 | # DO NOT DELETE THIS LINE -- make depend needs it 76 | -------------------------------------------------------------------------------- /panorama.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // CImg library 8 | #define cimg_display 0 9 | #define cimg_use_jpeg 10 | #define cimg_use_png 11 | #include "CImg/CImg.h" 12 | using namespace cimg_library; 13 | 14 | //#include 15 | //#include 16 | 17 | // Multithreading 18 | #include 19 | #include 20 | using namespace tbb; 21 | 22 | // Input parameters 23 | int iflag, oflag, hflag, rflag; 24 | char *ivalue, *ovalue; 25 | int rvalue=4096; 26 | 27 | /** 28 | ** Parse input parameters 29 | **/ 30 | int parseParameters(int argc, char *argv[]) { 31 | iflag = oflag = hflag = rflag = 0; 32 | ivalue = ovalue = NULL; 33 | int c; 34 | opterr = 0; 35 | 36 | while ((c = getopt (argc, argv, "i:o:r:")) != -1) 37 | switch (c) { 38 | case 'i': 39 | // input file 40 | iflag = 1; 41 | ivalue = optarg; 42 | break; 43 | case 'o': 44 | oflag = 1; 45 | ovalue = optarg; 46 | break; 47 | case 'r': 48 | rflag = 1; 49 | rvalue = std::stoi(optarg); 50 | break; 51 | case '?': 52 | if (optopt == 'i' || optopt == 'o' || optopt == 'r') 53 | fprintf (stderr, "Option -%c requires an argument.\n", optopt); 54 | else if (isprint (optopt)) 55 | fprintf (stderr, "Unknown option `-%c'.\n", optopt); 56 | else 57 | fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt); 58 | return 1; 59 | default: 60 | abort (); 61 | } 62 | 63 | if (iflag==0 || oflag == 0) { 64 | std::cout << "No inputs or outputs specified: "<< iflag << "/" << oflag <<"\n"; 65 | abort (); 66 | return 1; 67 | } 68 | return 0; 69 | } 70 | 71 | template 72 | struct Vec3_ { 73 | Coordinate x, y, z; 74 | 75 | Vec3_(Coordinate x = {}, Coordinate y = {}, Coordinate z = {}) 76 | : x{x}, y{y}, z{z} 77 | {} 78 | 79 | template 80 | Vec3_(const Vec3_ &other) 81 | : x{static_cast(other.x)} 82 | , y{static_cast(other.y)} 83 | , z{static_cast(other.z)} 84 | {} 85 | 86 | inline Vec3_ operator +(const Vec3_ &other) const { 87 | return {x + other.x, y + other.y, z + other.z}; 88 | } 89 | inline Vec3_ operator -(const Vec3_ &other) const { 90 | return {x - other.x, y - other.y, z - other.z}; 91 | } 92 | inline Vec3_ operator *(Coordinate c) const { 93 | return {x * c, y * c, z * c}; 94 | } 95 | }; 96 | using Vec3fa = Vec3_; // though not f(float) and not a(aligned) 97 | using Vec3uc = Vec3_; 98 | struct PixelRange {int start, end; }; 99 | 100 | /** get x,y,z coords from out image pixels coords 101 | ** i,j are pixel coords 102 | ** face is face number 103 | ** edge is edge length 104 | **/ 105 | Vec3fa outImgToXYZ(int, int, int, int); 106 | Vec3uc interpolateXYZtoColor(Vec3fa, CImg&); 107 | /** 108 | ** Convert panorama using an inverse pixel transformation 109 | **/ 110 | void convertBack(CImg&, CImg **); 111 | 112 | int main (int argc, char *argv[]) { 113 | std::cout << "PeakVisor panorama translator...\n"; 114 | 115 | parseParameters(argc, argv); 116 | 117 | std::cout << " convert equirectangular panorama: [" << ivalue << "] into cube faces: ["<< ovalue << "] of " << rvalue <<" pixels in dimension\n"; 118 | 119 | // Input image 120 | CImg imgIn(ivalue); 121 | 122 | // Create output images 123 | CImg* imgOut[6]; 124 | for (int i=0; i<6; ++i){ 125 | imgOut[i] = new CImg(rvalue, rvalue, 1, 4, 255); 126 | } 127 | 128 | // Convert panorama 129 | convertBack(imgIn, imgOut); 130 | 131 | // Write output images 132 | for (int i=0; i<6; ++i){ 133 | std::string fname = std::string(ovalue) + "_" + std::to_string(i) + ".jpg";//".jpg"; 134 | imgOut[i]->save_jpeg( fname.c_str(), 85); 135 | } 136 | 137 | std::cout << " convertation finished successfully\n"; 138 | return 0; 139 | } 140 | 141 | 142 | 143 | /** 144 | ** Convert panorama using an inverse pixel transformation 145 | **/ 146 | void convertBack(CImg& imgIn, CImg **imgOut){ 147 | int _dw = rvalue*6; 148 | int edge = rvalue; // the length of each edge in pixels 149 | 150 | // Look around cube faces 151 | tbb::parallel_for(blocked_range(0, _dw, 1), 152 | [&](const blocked_range& range) { 153 | for (size_t k=range.begin(); kdraw_point(i, j, 0, color); 162 | } 163 | } 164 | }); 165 | } 166 | 167 | // Given i,j pixel coordinates on a given face in range (0,edge), 168 | // find the corresponding x,y,z coords in range (-1.0,1.0) 169 | Vec3fa outImgToXYZ(int i, int j, int face, int edge) { 170 | float a = (2.0f*i)/edge - 1.0f; 171 | float b = (2.0f*j)/edge - 1.0f; 172 | Vec3fa res; 173 | if (face==0) { // back 174 | res = {-1.0f, -a, -b}; 175 | } else if (face==1) { // left 176 | res = {a, -1.0f, -b}; 177 | } else if (face==2) { // front 178 | res = {1.0f, a, -b}; 179 | } else if (face==3) { // right 180 | res = {-a, 1.0f, -b}; 181 | } else if (face==4) { // top 182 | res = {b, a, 1.0f}; 183 | } else if (face==5) { // bottom 184 | res = {-b, a, -1.0f}; 185 | } 186 | else { 187 | printf("face %d\n",face); 188 | } 189 | return res; 190 | } 191 | 192 | template 193 | static inline T clamp(const T &n, const T &lower, const T &upper) { 194 | return std::min(std::max(n, lower), upper); 195 | } 196 | 197 | template 198 | static inline T safeIndex(const T n, const T size) { 199 | return clamp(n, {}, size - 1); 200 | } 201 | 202 | template 203 | static inline T mix(const T &one, const T &other, const Scalar &c) { 204 | return one + (other - one) * c; 205 | } 206 | 207 | Vec3uc interpolateXYZtoColor(Vec3fa xyz, CImg& imgIn) { 208 | auto _sw = imgIn.width(), _sh = imgIn.height(); 209 | 210 | auto theta = std::atan2(xyz.y, xyz.x), r = std::hypot(xyz.x, xyz.y);// # range -pi to pi 211 | auto phi = std::atan2(xyz.z, r);// # range -pi/2 to pi/2 212 | 213 | // source img coords 214 | auto uf = (theta + M_PI) / M_PI * _sh; 215 | auto vf = (M_PI_2 - phi) / M_PI * _sh; // implicit assumption: _sh == _sw / 2 216 | // Use bilinear interpolation between the four surrounding pixels 217 | auto ui = safeIndex(static_cast(std::floor(uf)), _sw); 218 | auto vi = safeIndex(static_cast(std::floor(vf)), _sh); //# coord of pixel to bottom left 219 | auto u2 = safeIndex(ui + 1, _sw); 220 | auto v2 = safeIndex(vi + 1, _sh); //# coords of pixel to top right 221 | double mu = uf - ui, nu = vf - vi; //# fraction of way across pixel 222 | 223 | // Pixel values of four nearest corners 224 | auto read = [&](int x, int y) { return Vec3fa{Vec3uc{*imgIn.data(x, y, 0, 0), *imgIn.data(x, y, 0, 1), *imgIn.data(x, y, 0, 2)}}; }; 225 | auto A = read(ui, vi), B = read(u2, vi), C = read(ui, v2), D = read(u2, v2); 226 | 227 | // Interpolate color 228 | auto value = mix(mix(A, B, mu), mix(C, D, mu), nu); 229 | return Vec3uc{value}; 230 | } 231 | -------------------------------------------------------------------------------- /samples/equirectangular_panorama.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peakvisor/panorama/d51bc6551ad4ec6e7a20d2b4bd51053aee91ff03/samples/equirectangular_panorama.jpg --------------------------------------------------------------------------------