├── back.png ├── lake.jpg ├── EMARALD.jpg ├── example.PNG ├── fishey32.PNG ├── lake-wallpapers.jpg ├── FISHEYE.cp36-win_amd64.pyd ├── setup_FishEye.py ├── LICENSE ├── FishEyeAlgorithm.py ├── .gitignore ├── README.md ├── FISH_EYE.py └── fisheye.pyx /back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoyoberenguer/Fisheye/HEAD/back.png -------------------------------------------------------------------------------- /lake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoyoberenguer/Fisheye/HEAD/lake.jpg -------------------------------------------------------------------------------- /EMARALD.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoyoberenguer/Fisheye/HEAD/EMARALD.jpg -------------------------------------------------------------------------------- /example.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoyoberenguer/Fisheye/HEAD/example.PNG -------------------------------------------------------------------------------- /fishey32.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoyoberenguer/Fisheye/HEAD/fishey32.PNG -------------------------------------------------------------------------------- /lake-wallpapers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoyoberenguer/Fisheye/HEAD/lake-wallpapers.jpg -------------------------------------------------------------------------------- /FISHEYE.cp36-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoyoberenguer/Fisheye/HEAD/FISHEYE.cp36-win_amd64.pyd -------------------------------------------------------------------------------- /setup_FishEye.py: -------------------------------------------------------------------------------- 1 | # distutils: extra_compile_args = -fopenmp 2 | # distutils: extra_link_args = -fopenmp 3 | 4 | from distutils.core import setup 5 | from distutils.extension import Extension 6 | from Cython.Distutils import build_ext 7 | import numpy 8 | 9 | ext_modules = [Extension("FISHEYE", ["fisheye.pyx"], 10 | include_dirs=[numpy.get_include()], 11 | extra_compile_args=['/openmp'], 12 | extra_link_args=['/openmp'], 13 | )] 14 | 15 | setup( 16 | name="FISHEYE", 17 | cmdclass={"build_ext": build_ext}, 18 | ext_modules=ext_modules, 19 | include_dirs=[numpy.get_include()] 20 | ) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yoann Berenguer 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 | -------------------------------------------------------------------------------- /FishEyeAlgorithm.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame 3 | import math 4 | def fish_eye(image: pygame.Surface): 5 | """ Fish eye algorithm """ 6 | w, h = image.get_size() 7 | w2 = w / 2 8 | h2 = h / 2 9 | image_copy = pygame.Surface((w, h), flags=pygame.RLEACCEL).convert() 10 | for y in range(h): 11 | # Normalize every pixels along y axis 12 | # when y = 0 --> ny = -1 13 | # when y = h --> ny = +1 14 | ny = ((2 * y) / h) - 1 15 | # ny * ny pre calculated 16 | ny2 = ny ** 2 17 | for x in range(w): 18 | # Normalize every pixels along x axis 19 | # when x = 0 --> nx = -1 20 | # when x = w --> nx = +1 21 | nx = ((2 * x) / w) - 1 22 | # pre calculated nx * nx 23 | nx2 = nx ** 2 24 | 25 | # calculate distance from center (0, 0) 26 | r = math.sqrt(nx2 + ny2) 27 | 28 | # discard pixel if r below 0.0 or above 1.0 29 | if 0.0 <= r <= 1.0: 30 | 31 | nr = (r + 1 - math.sqrt(1 - r ** 2)) / 2 32 | if nr <= 1.0: 33 | 34 | theta = math.atan2(ny, nx) 35 | nxn = nr * math.cos(theta) 36 | nyn = nr * math.sin(theta) 37 | x2 = int(nxn * w2 + w2) 38 | y2 = int(nyn * h2 + h2) 39 | 40 | if 0 <= int(y2 * w + x2) < w * h: 41 | 42 | pixel = image.get_at((x2, y2)) 43 | image_copy.set_at((x, y), pixel) 44 | 45 | return image_copy 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fisheye 2 | ## Fisheye lens algorithm using python and pygame 3 | 4 | ![alt text](https://github.com/yoyoberenguer/Fisheye/blob/master/lake-wallpapers.jpg) 5 | 6 | ![alt text](https://github.com/yoyoberenguer/Fisheye/blob/master/lake.jpg) 7 | 8 | ## PROJECT: 9 | 10 | This Library provide a selection of fast `CYTHON` methods for generating fisheye rendering. 11 | The fisheye algorithms are compatible for both `24, 32-bit format textures`. 12 | 13 | ## REQUIREMENT: 14 | ``` 15 | - python > 3.0 16 | - numpy arrays 17 | - pygame with SDL version 1.2 (SDL version 2 untested) 18 | Cython 19 | - A compiler such visual studio, MSVC, CGYWIN setup correctly 20 | on your system. 21 | - a C compiler for windows (Visual Studio, MinGW etc) install on your system 22 | and linked to your windows environment. 23 | Note that some adjustment might be needed once a compiler is install on your system, 24 | refer to external documentation or tutorial in order to setup this process. 25 | e.g https://devblogs.microsoft.com/python/unable-to-find-vcvarsall-bat/ 26 | ``` 27 | 28 | ## MULTI - PROCESSING CAPABILITY 29 | 30 | The flag OPENMP can be changed any time if you wish to use multiprocessing 31 | or not (default True, using multi-processing). 32 | Also you can change the number of threads needed with the flag THREAD_NUMBER (default 8 threads) 33 | 34 | 35 | ## BUILDING PROJECT: 36 | ``` 37 | In a command prompt and under the directory containing the source files 38 | C:\>python setup_FishEye.py build_ext --inplace 39 | 40 | If the compilation fail, refers to the requirement section and make sure cython 41 | and a C-compiler are correctly install on your system. 42 | ``` 43 | 44 | ## HOW TO: 45 | ``` 46 | Run the python file FISH_EYE.py for a demonstration. 47 | 48 | 32-bit Fish eye lens image can be obtain the following way in python 49 | ``` 50 | 51 | ```python 52 | # ------------------------------------------ 53 | import pygame 54 | from FISHEYE import fish_eye24, fish_eye32 55 | 56 | screen = pygame.display.set_mode((800, 600)) 57 | surface32 = pygame.image.load("EMARALD.jpg").convert_alpha() 58 | surface32 = pygame.transform.smoothscale(surface32, (800, 600)) 59 | fisheye_surface = fish_eye32(surface32) 60 | 61 | screen.blit(fisheye_surface, (0, 0)) 62 | pygame.display.flip() 63 | 64 | # ------------------------------------------ 65 | ``` 66 | 67 | ![alt text](https://github.com/yoyoberenguer/Fisheye/blob/master/fishey32.PNG) 68 | -------------------------------------------------------------------------------- /FISH_EYE.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | """ 4 | MIT License 5 | 6 | Copyright (c) 2019 Yoann Berenguer 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | 26 | """ 27 | 28 | 29 | # PYGAME IS REQUIRED 30 | try: 31 | import pygame 32 | from pygame import Color, Surface, SRCALPHA, RLEACCEL, BufferProxy 33 | from pygame.surfarray import pixels3d, array_alpha, pixels_alpha, array3d 34 | from pygame.image import frombuffer 35 | 36 | except ImportError: 37 | print("\n library is missing on your system." 38 | "\nTry: \n C:\\pip install pygame on a window command prompt.") 39 | raise SystemExit 40 | 41 | 42 | from FISHEYE import fish_eye24, fish_eye32 43 | 44 | import random 45 | 46 | if __name__ == '__main__': 47 | 48 | w, h = 400, 400 49 | screen = pygame.display.set_mode((w * 2, h)) 50 | 51 | background = pygame.image.load('back.png').convert() 52 | background.set_alpha(None) 53 | 54 | surface24 = pygame.image.load("EMARALD.jpg").convert() 55 | surface32 = pygame.image.load("EMARALD.jpg").convert_alpha() 56 | 57 | background = pygame.transform.smoothscale(background, (w * 2, h)) 58 | surface24 = pygame.transform.smoothscale(surface24, (w, h)) 59 | surface32 = pygame.transform.smoothscale(surface32, (w, h)) 60 | 61 | i = 0 62 | fisheye_surface = fish_eye32(surface32) 63 | while 1: 64 | pygame.event.pump() 65 | keys = pygame.key.get_pressed() 66 | for event in pygame.event.get(): 67 | if event.type == pygame.MOUSEMOTION: 68 | MOUSE_POS = event.pos 69 | 70 | if keys[pygame.K_F8]: 71 | pygame.image.save(screen, 'Screendump' + str(i) + '.png') 72 | 73 | if keys[pygame.K_ESCAPE]: 74 | break 75 | 76 | screen.fill((0, 0, 0)) 77 | screen.blit(background, (0, 0)) 78 | screen.blit(fisheye_surface, (0, 0)) 79 | screen.blit(surface24, (w, 0)) 80 | pygame.display.flip() 81 | 82 | i += 1 83 | pygame.quit() 84 | -------------------------------------------------------------------------------- /fisheye.pyx: -------------------------------------------------------------------------------- 1 | ###cython: boundscheck=False, wraparound=False, nonecheck=False, optimize.use_switch=True 2 | 3 | """ 4 | MIT License 5 | 6 | Copyright (c) 2019 Yoann Berenguer 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | 26 | 27 | """ 28 | 29 | # NUMPY IS REQUIRED 30 | try: 31 | import numpy 32 | from numpy import ndarray, zeros, empty, uint8, int32, float64, float32, dstack, full, ones,\ 33 | asarray, ascontiguousarray 34 | except ImportError: 35 | print("\n library is missing on your system." 36 | "\nTry: \n C:\\pip install numpy on a window command prompt.") 37 | raise SystemExit 38 | 39 | # CYTHON IS REQUIRED 40 | try: 41 | cimport cython 42 | from cython.parallel cimport prange 43 | except ImportError: 44 | print("\n library is missing on your system." 45 | "\nTry: \n C:\\pip install cython on a window command prompt.") 46 | raise SystemExit 47 | 48 | cimport numpy as np 49 | 50 | # OPENCV IS REQUIRED 51 | try: 52 | import cv2 53 | except ImportError: 54 | print("\n library is missing on your system." 55 | "\nTry: \n C:\\pip install opencv-python on a window command prompt.") 56 | raise SystemExit 57 | 58 | 59 | # PYGAME IS REQUIRED 60 | try: 61 | import pygame 62 | from pygame import Color, Surface, SRCALPHA, RLEACCEL, BufferProxy 63 | from pygame.surfarray import pixels3d, array_alpha, pixels_alpha, array3d 64 | from pygame.image import frombuffer 65 | 66 | except ImportError: 67 | print("\n library is missing on your system." 68 | "\nTry: \n C:\\pip install pygame on a window command prompt.") 69 | raise SystemExit 70 | 71 | 72 | from libc.math cimport sin, sqrt, cos, atan2 73 | 74 | DEF OPENMP = True 75 | 76 | # num_threads – The num_threads argument indicates how many threads the team should consist of. 77 | # If not given, OpenMP will decide how many threads to use. 78 | # Typically this is the number of cores available on the machine. However, 79 | # this may be controlled through the omp_set_num_threads() function, 80 | # or through the OMP_NUM_THREADS environment variable. 81 | if OPENMP == True: 82 | DEF THREAD_NUMBER = 8 83 | else: 84 | DEF THREAD_NUMNER = 1 85 | 86 | DEF SCHEDULE = 'static' 87 | 88 | # ------------------------------------ INTERFACE ---------------------------------------------- 89 | 90 | def fish_eye24(image)->Surface: 91 | return fish_eye24_c(image) 92 | 93 | def fish_eye32(image)->Surface: 94 | return fish_eye32_c(image) 95 | 96 | # ------------------------------------ IMPLEMENTATION------------------------------------------ 97 | 98 | @cython.boundscheck(False) 99 | @cython.wraparound(False) 100 | @cython.nonecheck(False) 101 | @cython.cdivision(True) 102 | cdef fish_eye24_c(image): 103 | """ 104 | Transform an image into a fish eye lens model. 105 | 106 | :param image: Surface (compatible only with 24-32 bit surface) 107 | :return: Return a Surface without alpha channel, fish eye lens model. 108 | 109 | """ 110 | assert isinstance(image, Surface), \ 111 | "\nArgument image is not a pygame.Surface type, got %s " % type(image) 112 | 113 | try: 114 | array = pixels3d(image) 115 | except (pygame.error, ValueError): 116 | try: 117 | array = array3d(image) 118 | except: 119 | raise ValueError('\nInvalid pixel format.') 120 | 121 | cdef double w, h 122 | w, h = image.get_size() 123 | 124 | assert (w!=0 and h!=0),\ 125 | 'Incorrect image format (w>0, h>0) got (w:%s h:%s) ' % (w, h) 126 | 127 | cdef: 128 | unsigned char [:, :, :] rgb_array = array 129 | int y=0, x=0, v 130 | double ny, ny2, nx, nx2, r, theta, nxn, nyn, nr 131 | int x2, y2 132 | double s = w * h 133 | double c1 = 2 / h 134 | double c2 = 2 / w 135 | double w2 = w / 2 136 | double h2 = h / 2 137 | unsigned char [:, :, ::1] rgb_empty = zeros((int(h), int(w), 3), dtype=uint8) 138 | 139 | with nogil: 140 | for y in prange(h, schedule=SCHEDULE, num_threads=THREAD_NUMBER, chunksize=8): 141 | ny = y * c1 - 1 142 | ny2 = ny * ny 143 | for x in range(w): 144 | nx = x * c2 - 1.0 145 | nx2 = nx * nx 146 | r = sqrt(nx2 + ny2) 147 | if 0.0 <= r <= 1.0: 148 | nr = (r + 1.0 - sqrt(1.0 - (nx2 + ny2))) * 0.5 149 | if nr <= 1.0: 150 | theta = atan2(ny, nx) 151 | nxn = nr * cos(theta) 152 | nyn = nr * sin(theta) 153 | x2 = (nxn * w2 + w2) 154 | y2 = (nyn * h2 + h2) 155 | v = (y2 * w + x2) 156 | if 0 <= v < s: 157 | rgb_empty[y, x, 0] = rgb_array[x2, y2, 0] 158 | rgb_empty[y, x, 1] = rgb_array[x2, y2, 1] 159 | rgb_empty[y, x, 2] = rgb_array[x2, y2, 2] 160 | 161 | return pygame.image.frombuffer(rgb_empty, (w, h), 'RGB') 162 | 163 | 164 | 165 | @cython.boundscheck(False) 166 | @cython.wraparound(False) 167 | @cython.nonecheck(False) 168 | @cython.cdivision(True) 169 | cdef fish_eye32_c(image): 170 | """ 171 | Transform an image into a fish eye lens model (compatible 32-bit) 172 | 173 | :param image: Surface compatible 32-bit format with per-pixel 174 | :return: Return a 32-bit Surface with alpha channel, fish eye lens model with per-pixel transparency 175 | 176 | """ 177 | assert isinstance(image, Surface), \ 178 | "\nArgument image is not a pygame.Surface type, got %s " % type(image) 179 | 180 | try: 181 | array = pixels3d(image) 182 | alpha = pixels_alpha(image) 183 | except (pygame.error, ValueError): 184 | raise ValueError('\nIncompatible pixel format.') 185 | 186 | cdef double w, h 187 | w, h = image.get_size() 188 | 189 | assert (w!=0 and h!=0),\ 190 | 'Incorrect image format (w>0, h>0) got (w:%s h:%s) ' % (w, h) 191 | 192 | cdef: 193 | unsigned char [:, :, :] rgb_array = array 194 | unsigned char [:, :] alpha_array = alpha 195 | int y=0, x=0, v 196 | double ny, ny2, nx, nx2, r, theta, nxn, nyn, nr 197 | int x2, y2 198 | double s = w * h 199 | double c1 = 2 / h 200 | double c2 = 2 / w 201 | double w2 = w / 2 202 | double h2 = h / 2 203 | unsigned char [:, :, ::1] rgb_empty = zeros((int(h), int(w), 4), dtype=uint8) 204 | with nogil: 205 | for y in prange(h, schedule=SCHEDULE, num_threads=THREAD_NUMBER, chunksize=8): 206 | ny = y * c1 - 1 207 | ny2 = ny * ny 208 | for x in range(w): 209 | nx = x * c2 - 1.0 210 | nx2 = nx * nx 211 | r = sqrt(nx2 + ny2) 212 | if 0.0 <= r <= 1.0: 213 | nr = (r + 1.0 - sqrt(1.0 - (nx2 + ny2))) * 0.5 214 | if nr <= 1.0: 215 | theta = atan2(ny, nx) 216 | nxn = nr * cos(theta) 217 | nyn = nr * sin(theta) 218 | x2 = (nxn * w2 + w2) 219 | y2 = (nyn * h2 + h2) 220 | v = (y2 * w + x2) 221 | if 0 <= v < s: 222 | rgb_empty[y, x, 0] = rgb_array[x2, y2, 0] 223 | rgb_empty[y, x, 1] = rgb_array[x2, y2, 1] 224 | rgb_empty[y, x, 2] = rgb_array[x2, y2, 2] 225 | rgb_empty[y, x, 3] = rgb_array[x2, y2, 3] 226 | 227 | return pygame.image.frombuffer(rgb_empty, (w, h), 'RGBA') 228 | --------------------------------------------------------------------------------