├── .editorconfig ├── .github └── workflows │ ├── build-wheels.yml │ └── ci.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── .style.yapf ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── assets └── glac.jpg ├── bench.py ├── pydelatin ├── __init__.py ├── delatin.py ├── py.typed └── util.py ├── pyproject.toml ├── setup.cfg ├── setup.py ├── src ├── base.cpp ├── base.h ├── blur.cpp ├── blur.h ├── heightmap.cpp ├── heightmap.h ├── main.cpp ├── pybind11_glm.hpp ├── stb_image.h ├── stb_image_write.h ├── triangulator.cpp └── triangulator.h └── test ├── README.md ├── bench_js ├── .gitignore ├── bench.js ├── index.js ├── package.json └── yarn.lock └── data └── fuji.png /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | charset = utf-8 12 | indent_style = space 13 | indent_size = 2 14 | 15 | # 4 space indentation 16 | [*.{py,pyx}] 17 | indent_size = 4 18 | 19 | # Tab indentation (no size specified) 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/workflows/build-wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build Wheels 2 | 3 | # Only run on new tags starting with `v` 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | 9 | jobs: 10 | build_wheels: 11 | name: Build wheel on ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest, macos-13] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Build wheels 21 | uses: pypa/cibuildwheel@v2.20.0 22 | env: 23 | # From rio-color here: 24 | # https://github.com/mapbox/rio-color/blob/0ab59ad8e2db99ad1d0c8bd8c2e4cf8d0c3114cf/appveyor.yml#L3 25 | CIBW_SKIP: "cp2* cp35* pp* *-win32 *-manylinux_i686" 26 | CIBW_ARCHS_MACOS: x86_64 arm64 27 | CIBW_BEFORE_BUILD_MACOS: brew install glm && export CPLUS_INCLUDE_PATH=$(brew --prefix glm)/include:$CPLUS_INCLUDE_PATH 28 | CIBW_BEFORE_ALL_LINUX: curl -sL https://github.com/g-truc/glm/releases/download/0.9.9.8/glm-0.9.9.8.zip > glm.zip && unzip -q glm.zip && cp -r glm/glm/ /usr/include/ 29 | 30 | - uses: actions/upload-artifact@v2 31 | with: 32 | path: ./wheelhouse/*.whl 33 | 34 | build_sdist: 35 | name: Build source distribution 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | 40 | - uses: actions/setup-python@v2 41 | name: Install Python 42 | with: 43 | python-version: "3.11" 44 | 45 | - name: Build sdist 46 | run: | 47 | python -m pip install build 48 | python -m build . --sdist 49 | 50 | - uses: actions/upload-artifact@v2 51 | with: 52 | path: dist/*.tar.gz 53 | 54 | upload_pypi: 55 | needs: [build_wheels, build_sdist] 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/download-artifact@v2 59 | with: 60 | name: artifact 61 | path: dist 62 | 63 | - uses: pypa/gh-action-pypi-publish@release/v1 64 | with: 65 | user: __token__ 66 | password: ${{ secrets.pypi_password }} 67 | # To test: repository_url: https://test.pypi.org/legacy/ 68 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # On every pull request, but only on push to master 4 | on: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - "*" 10 | pull_request: 11 | 12 | jobs: 13 | tests: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: [3.9] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | # - name: Install dependencies 28 | # run: | 29 | # python -m pip install --upgrade pip 30 | # python -m pip install numpy Cython 31 | # python -m pip install . 32 | # python -m pip install '.[test]' 33 | 34 | # - name: Run tests 35 | # run: pytest 36 | 37 | # TODO: re-enable pre-commit 38 | # # Run pre-commit (only for python-3.9) 39 | # - name: run pre-commit 40 | # if: matrix.python-version == 3.9 41 | # run: | 42 | # python -m pip install pre-commit 43 | # pre-commit run --all-files 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | env/ 113 | venv/ 114 | ENV/ 115 | env.bak/ 116 | venv.bak/ 117 | 118 | # Spyder project settings 119 | .spyderproject 120 | .spyproject 121 | 122 | # Rope project settings 123 | .ropeproject 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | 136 | # pytype static type analyzer 137 | .pytype/ 138 | 139 | # Cython debug symbols 140 | cython_debug/ 141 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | 4 | # Default to Python 3 5 | default_language_version: 6 | python: python3 7 | 8 | # Optionally both commit and push 9 | default_stages: [commit] 10 | 11 | repos: 12 | - repo: https://github.com/pre-commit/pre-commit-hooks 13 | rev: v2.4.0 14 | hooks: 15 | - id: trailing-whitespace 16 | - id: end-of-file-fixer 17 | - id: check-added-large-files 18 | 19 | - repo: https://github.com/PyCQA/isort 20 | rev: 5.4.2 21 | hooks: 22 | - id: isort 23 | 24 | - repo: https://github.com/psf/black 25 | rev: 22.10.0 26 | hooks: 27 | - id: black 28 | args: ["--skip-string-normalization"] 29 | language_version: python3 30 | 31 | - repo: https://github.com/PyCQA/pylint 32 | rev: pylint-2.6.0 33 | hooks: 34 | - id: pylint 35 | 36 | - repo: https://github.com/pre-commit/mirrors-mypy 37 | rev: v0.812 38 | hooks: 39 | - id: mypy 40 | args: ["--ignore-missing-imports"] 41 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Specify a score threshold to be exceeded before program exits with error. 9 | fail-under=10.0 10 | 11 | # Add files or directories to the blacklist. They should be base names, not 12 | # paths. 13 | ignore=CVS 14 | 15 | # Add files or directories matching the regex patterns to the blacklist. The 16 | # regex matches against base names, not paths. 17 | ignore-patterns= 18 | 19 | # Python code to execute, usually for sys.path manipulation such as 20 | # pygtk.require(). 21 | #init-hook= 22 | 23 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 24 | # number of processors available to use. 25 | jobs=0 26 | 27 | # Control the amount of potential inferred values when inferring a single 28 | # object. This can help the performance when dealing with large functions or 29 | # complex, nested conditions. 30 | limit-inference-results=100 31 | 32 | # List of plugins (as comma separated values of python module names) to load, 33 | # usually to register additional checkers. 34 | load-plugins= 35 | 36 | # Pickle collected data for later comparisons. 37 | persistent=yes 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=yes 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=all 64 | 65 | # Enable the message, report, category or checker with the given id(s). You can 66 | # either give multiple identifier separated by comma (,) or put this option 67 | # multiple time (only on the command line, not in the configuration file where 68 | # it should appear only once). See also the "--disable" option for examples. 69 | # 70 | # Note: For a description of any of these, you can search 71 | # https://pycodequ.al/docs/search.html?q=${error-message} 72 | # Example: 73 | # https://pycodequ.al/docs/search.html?q=assignment-from-no-return 74 | enable= 75 | assignment-from-no-return, 76 | dangerous-default-value, 77 | f-string-without-interpolation, 78 | import-outside-toplevel, 79 | invalid-overridden-method, 80 | no-self-argument, 81 | pointless-string-statement, 82 | redefined-outer-name, 83 | super-with-arguments, 84 | trailing-comma-tuple, 85 | undefined-variable, 86 | unnecessary-comprehension, 87 | unnecessary-lambda, 88 | unused-argument, 89 | unused-import, 90 | unused-variable, 91 | useless-object-inheritance, 92 | 93 | [REPORTS] 94 | 95 | # Python expression which should return a score less than or equal to 10. You 96 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 97 | # which contain the number of messages in each category, as well as 'statement' 98 | # which is the total number of statements analyzed. This score is used by the 99 | # global evaluation report (RP0004). 100 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 101 | 102 | # Template used to display messages. This is a python new-style format string 103 | # used to format the message information. See doc for all details. 104 | #msg-template= 105 | 106 | # Set the output format. Available formats are text, parseable, colorized, json 107 | # and msvs (visual studio). You can also give a reporter class, e.g. 108 | # mypackage.mymodule.MyReporterClass. 109 | output-format=text 110 | 111 | # Tells whether to display a full report or only the messages. 112 | reports=no 113 | 114 | # Activate the evaluation score. 115 | score=yes 116 | 117 | 118 | [REFACTORING] 119 | 120 | # Maximum number of nested blocks for function / method body 121 | max-nested-blocks=5 122 | 123 | # Complete name of functions that never returns. When checking for 124 | # inconsistent-return-statements if a never returning function is called then 125 | # it will be considered as an explicit return statement and no message will be 126 | # printed. 127 | never-returning-functions=sys.exit 128 | 129 | 130 | [VARIABLES] 131 | 132 | # Tells whether unused global variables should be treated as a violation. 133 | allow-global-unused-variables=yes 134 | 135 | # Tells whether we should check for unused import in __init__ files. 136 | init-import=no 137 | 138 | # List of qualified module names which can have objects that can redefine 139 | # builtins. 140 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 141 | 142 | 143 | [BASIC] 144 | 145 | # Naming style matching correct argument names. 146 | argument-naming-style=snake_case 147 | 148 | # Regular expression matching correct argument names. Overrides argument- 149 | # naming-style. 150 | #argument-rgx= 151 | 152 | # Naming style matching correct attribute names. 153 | attr-naming-style=snake_case 154 | 155 | # Regular expression matching correct attribute names. Overrides attr-naming- 156 | # style. 157 | #attr-rgx= 158 | 159 | # Naming style matching correct class attribute names. 160 | class-attribute-naming-style=any 161 | 162 | # Regular expression matching correct class attribute names. Overrides class- 163 | # attribute-naming-style. 164 | #class-attribute-rgx= 165 | 166 | # Naming style matching correct class names. 167 | class-naming-style=PascalCase 168 | 169 | # Regular expression matching correct class names. Overrides class-naming- 170 | # style. 171 | #class-rgx= 172 | 173 | # Naming style matching correct constant names. 174 | const-naming-style=UPPER_CASE 175 | 176 | # Regular expression matching correct constant names. Overrides const-naming- 177 | # style. 178 | #const-rgx= 179 | 180 | # Minimum line length for functions/classes that require docstrings, shorter 181 | # ones are exempt. 182 | docstring-min-length=-1 183 | 184 | # Naming style matching correct function names. 185 | function-naming-style=snake_case 186 | 187 | # Regular expression matching correct function names. Overrides function- 188 | # naming-style. 189 | #function-rgx= 190 | 191 | # Good variable names which should always be accepted, separated by a comma. 192 | # Note: These take effect if you enable the `invalid-name` rule 193 | good-names= 194 | _, # unused return output 195 | df, # pandas or dask DataFrame 196 | f, # open file handle 197 | h, # hexagon id 198 | i, 199 | j, 200 | k, 201 | r, # requests response 202 | zf, # open zip file handle 203 | 204 | # Good variable names regexes, separated by a comma. If names match any regex, 205 | # they will always be accepted 206 | good-names-rgxs= 207 | 208 | # Regular expression matching correct inline iteration names. Overrides 209 | # inlinevar-naming-style. 210 | #inlinevar-rgx= 211 | 212 | # Naming style matching correct method names. 213 | method-naming-style=snake_case 214 | 215 | # Regular expression matching correct method names. Overrides method-naming- 216 | # style. 217 | #method-rgx= 218 | 219 | # Naming style matching correct module names. 220 | module-naming-style=snake_case 221 | 222 | # Regular expression matching correct module names. Overrides module-naming- 223 | # style. 224 | #module-rgx= 225 | 226 | # Colon-delimited sets of names that determine each other's naming style when 227 | # the name regexes allow several styles. 228 | name-group= 229 | 230 | # Regular expression which should only match function or class names that do 231 | # not require a docstring. 232 | no-docstring-rgx=^_ 233 | 234 | # List of decorators that produce properties, such as abc.abstractproperty. Add 235 | # to this list to register other decorators that produce valid properties. 236 | # These decorators are taken in consideration only for invalid-name. 237 | property-classes=abc.abstractproperty 238 | 239 | # Naming style matching correct variable names. 240 | variable-naming-style=snake_case 241 | 242 | # Regular expression matching correct variable names. Overrides variable- 243 | # naming-style. 244 | #variable-rgx= 245 | 246 | 247 | [STRING] 248 | 249 | # This flag controls whether inconsistent-quotes generates a warning when the 250 | # character used as a quote delimiter is used inconsistently within a module. 251 | check-quote-consistency=no 252 | 253 | # This flag controls whether the implicit-str-concat should generate a warning 254 | # on implicit string concatenation in sequences defined over several lines. 255 | check-str-concat-over-line-jumps=no 256 | 257 | 258 | [IMPORTS] 259 | 260 | # List of modules that can be imported at any level, not just the top level 261 | # one. 262 | allow-any-import-level= 263 | 264 | # Allow wildcard imports from modules that define __all__. 265 | allow-wildcard-with-all=yes 266 | 267 | # Deprecated modules which should not be used, separated by a comma. 268 | deprecated-modules=optparse,tkinter.tix 269 | 270 | 271 | [CLASSES] 272 | 273 | # List of method names used to declare (i.e. assign) instance attributes. 274 | defining-attr-methods=__init__, 275 | __new__, 276 | setUp, 277 | __post_init__ 278 | 279 | # List of member names, which should be excluded from the protected access 280 | # warning. 281 | exclude-protected=_asdict, 282 | _fields, 283 | _replace, 284 | _source, 285 | _make 286 | 287 | # List of valid names for the first argument in a class method. 288 | valid-classmethod-first-arg=cls 289 | 290 | # List of valid names for the first argument in a metaclass class method. 291 | valid-metaclass-classmethod-first-arg=cls 292 | 293 | 294 | ; [DESIGN] 295 | ; 296 | ; # Maximum number of arguments for function / method. 297 | ; max-args=5 298 | ; 299 | ; # Maximum number of attributes for a class (see R0902). 300 | ; max-attributes=7 301 | ; 302 | ; # Maximum number of boolean expressions in an if statement (see R0916). 303 | ; max-bool-expr=5 304 | ; 305 | ; # Maximum number of branch for function / method body. 306 | ; max-branches=12 307 | ; 308 | ; # Maximum number of locals for function / method body. 309 | ; max-locals=15 310 | ; 311 | ; # Maximum number of parents for a class (see R0901). 312 | ; max-parents=7 313 | ; 314 | ; # Maximum number of public methods for a class (see R0904). 315 | ; max-public-methods=20 316 | ; 317 | ; # Maximum number of return / yield for function / method body. 318 | ; max-returns=6 319 | ; 320 | ; # Maximum number of statements in function / method body. 321 | ; max-statements=50 322 | ; 323 | ; # Minimum number of public methods for a class (see R0903). 324 | ; min-public-methods=2 325 | 326 | 327 | [EXCEPTIONS] 328 | 329 | # Exceptions that will emit a warning when being caught. Defaults to 330 | # "BaseException, Exception". 331 | overgeneral-exceptions=BaseException, 332 | Exception 333 | -------------------------------------------------------------------------------- /.style.yapf: -------------------------------------------------------------------------------- 1 | [style] 2 | # Align closing bracket with visual indentation. 3 | align_closing_bracket_with_visual_indent=False 4 | 5 | # Allow dictionary keys to exist on multiple lines. For example: 6 | # 7 | # x = { 8 | # ('this is the first element of a tuple', 9 | # 'this is the second element of a tuple'): 10 | # value, 11 | # } 12 | allow_multiline_dictionary_keys=False 13 | 14 | # Allow lambdas to be formatted on more than one line. 15 | allow_multiline_lambdas=False 16 | 17 | # Allow splitting before a default / named assignment in an argument list. 18 | allow_split_before_default_or_named_assigns=True 19 | 20 | # Allow splits before the dictionary value. 21 | allow_split_before_dict_value=True 22 | 23 | # Let spacing indicate operator precedence. For example: 24 | # 25 | # a = 1 * 2 + 3 / 4 26 | # b = 1 / 2 - 3 * 4 27 | # c = (1 + 2) * (3 - 4) 28 | # d = (1 - 2) / (3 + 4) 29 | # e = 1 * 2 - 3 30 | # f = 1 + 2 + 3 + 4 31 | # 32 | # will be formatted as follows to indicate precedence: 33 | # 34 | # a = 1*2 + 3/4 35 | # b = 1/2 - 3*4 36 | # c = (1+2) * (3-4) 37 | # d = (1-2) / (3+4) 38 | # e = 1*2 - 3 39 | # f = 1 + 2 + 3 + 4 40 | # 41 | arithmetic_precedence_indication=False 42 | 43 | # Number of blank lines surrounding top-level function and class 44 | # definitions. 45 | blank_lines_around_top_level_definition=2 46 | 47 | # Insert a blank line before a class-level docstring. 48 | blank_line_before_class_docstring=False 49 | 50 | # Insert a blank line before a module docstring. 51 | blank_line_before_module_docstring=False 52 | 53 | # Insert a blank line before a 'def' or 'class' immediately nested 54 | # within another 'def' or 'class'. For example: 55 | # 56 | # class Foo: 57 | # # <------ this blank line 58 | # def method(): 59 | # ... 60 | blank_line_before_nested_class_or_def=False 61 | 62 | # Do not split consecutive brackets. Only relevant when 63 | # dedent_closing_brackets is set. For example: 64 | # 65 | # call_func_that_takes_a_dict( 66 | # { 67 | # 'key1': 'value1', 68 | # 'key2': 'value2', 69 | # } 70 | # ) 71 | # 72 | # would reformat to: 73 | # 74 | # call_func_that_takes_a_dict({ 75 | # 'key1': 'value1', 76 | # 'key2': 'value2', 77 | # }) 78 | coalesce_brackets=True 79 | 80 | # The column limit. 81 | column_limit=80 82 | 83 | # The style for continuation alignment. Possible values are: 84 | # 85 | # - SPACE: Use spaces for continuation alignment. This is default behavior. 86 | # - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns 87 | # (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs or 88 | # CONTINUATION_INDENT_WIDTH spaces) for continuation alignment. 89 | # - VALIGN-RIGHT: Vertically align continuation lines to multiple of 90 | # INDENT_WIDTH columns. Slightly right (one tab or a few spaces) if 91 | # cannot vertically align continuation lines with indent characters. 92 | continuation_align_style=SPACE 93 | 94 | # Indent width used for line continuations. 95 | continuation_indent_width=4 96 | 97 | # Put closing brackets on a separate line, dedented, if the bracketed 98 | # expression can't fit in a single line. Applies to all kinds of brackets, 99 | # including function definitions and calls. For example: 100 | # 101 | # config = { 102 | # 'key1': 'value1', 103 | # 'key2': 'value2', 104 | # } # <--- this bracket is dedented and on a separate line 105 | # 106 | # time_series = self.remote_client.query_entity_counters( 107 | # entity='dev3246.region1', 108 | # key='dns.query_latency_tcp', 109 | # transform=Transformation.AVERAGE(window=timedelta(seconds=60)), 110 | # start_ts=now()-timedelta(days=3), 111 | # end_ts=now(), 112 | # ) # <--- this bracket is dedented and on a separate line 113 | dedent_closing_brackets=False 114 | 115 | # Disable the heuristic which places each list element on a separate line 116 | # if the list is comma-terminated. 117 | disable_ending_comma_heuristic=False 118 | 119 | # Place each dictionary entry onto its own line. 120 | each_dict_entry_on_separate_line=True 121 | 122 | # Require multiline dictionary even if it would normally fit on one line. 123 | # For example: 124 | # 125 | # config = { 126 | # 'key1': 'value1' 127 | # } 128 | force_multiline_dict=False 129 | 130 | # The regex for an i18n comment. The presence of this comment stops 131 | # reformatting of that line, because the comments are required to be 132 | # next to the string they translate. 133 | i18n_comment= 134 | 135 | # The i18n function call names. The presence of this function stops 136 | # reformattting on that line, because the string it has cannot be moved 137 | # away from the i18n comment. 138 | i18n_function_call= 139 | 140 | # Indent blank lines. 141 | indent_blank_lines=False 142 | 143 | # Put closing brackets on a separate line, indented, if the bracketed 144 | # expression can't fit in a single line. Applies to all kinds of brackets, 145 | # including function definitions and calls. For example: 146 | # 147 | # config = { 148 | # 'key1': 'value1', 149 | # 'key2': 'value2', 150 | # } # <--- this bracket is indented and on a separate line 151 | # 152 | # time_series = self.remote_client.query_entity_counters( 153 | # entity='dev3246.region1', 154 | # key='dns.query_latency_tcp', 155 | # transform=Transformation.AVERAGE(window=timedelta(seconds=60)), 156 | # start_ts=now()-timedelta(days=3), 157 | # end_ts=now(), 158 | # ) # <--- this bracket is indented and on a separate line 159 | indent_closing_brackets=False 160 | 161 | # Indent the dictionary value if it cannot fit on the same line as the 162 | # dictionary key. For example: 163 | # 164 | # config = { 165 | # 'key1': 166 | # 'value1', 167 | # 'key2': value1 + 168 | # value2, 169 | # } 170 | indent_dictionary_value=True 171 | 172 | # The number of columns to use for indentation. 173 | indent_width=4 174 | 175 | # Join short lines into one line. E.g., single line 'if' statements. 176 | join_multiple_lines=True 177 | 178 | # Do not include spaces around selected binary operators. For example: 179 | # 180 | # 1 + 2 * 3 - 4 / 5 181 | # 182 | # will be formatted as follows when configured with "*,/": 183 | # 184 | # 1 + 2*3 - 4/5 185 | no_spaces_around_selected_binary_operators=set() 186 | 187 | # Use spaces around default or named assigns. 188 | spaces_around_default_or_named_assign=False 189 | 190 | # Adds a space after the opening '{' and before the ending '}' dict delimiters. 191 | # 192 | # {1: 2} 193 | # 194 | # will be formatted as: 195 | # 196 | # { 1: 2 } 197 | spaces_around_dict_delimiters=False 198 | 199 | # Adds a space after the opening '[' and before the ending ']' list delimiters. 200 | # 201 | # [1, 2] 202 | # 203 | # will be formatted as: 204 | # 205 | # [ 1, 2 ] 206 | spaces_around_list_delimiters=False 207 | 208 | # Use spaces around the power operator. 209 | spaces_around_power_operator=True 210 | 211 | # Use spaces around the subscript / slice operator. For example: 212 | # 213 | # my_list[1 : 10 : 2] 214 | spaces_around_subscript_colon=False 215 | 216 | # Adds a space after the opening '(' and before the ending ')' tuple delimiters. 217 | # 218 | # (1, 2, 3) 219 | # 220 | # will be formatted as: 221 | # 222 | # ( 1, 2, 3 ) 223 | spaces_around_tuple_delimiters=False 224 | 225 | # The number of spaces required before a trailing comment. 226 | # This can be a single value (representing the number of spaces 227 | # before each trailing comment) or list of values (representing 228 | # alignment column values; trailing comments within a block will 229 | # be aligned to the first column value that is greater than the maximum 230 | # line length within the block). For example: 231 | # 232 | # With spaces_before_comment=5: 233 | # 234 | # 1 + 1 # Adding values 235 | # 236 | # will be formatted as: 237 | # 238 | # 1 + 1 # Adding values <-- 5 spaces between the end of the statement and comment 239 | # 240 | # With spaces_before_comment=15, 20: 241 | # 242 | # 1 + 1 # Adding values 243 | # two + two # More adding 244 | # 245 | # longer_statement # This is a longer statement 246 | # short # This is a shorter statement 247 | # 248 | # a_very_long_statement_that_extends_beyond_the_final_column # Comment 249 | # short # This is a shorter statement 250 | # 251 | # will be formatted as: 252 | # 253 | # 1 + 1 # Adding values <-- end of line comments in block aligned to col 15 254 | # two + two # More adding 255 | # 256 | # longer_statement # This is a longer statement <-- end of line comments in block aligned to col 20 257 | # short # This is a shorter statement 258 | # 259 | # a_very_long_statement_that_extends_beyond_the_final_column # Comment <-- the end of line comments are aligned based on the line length 260 | # short # This is a shorter statement 261 | # 262 | spaces_before_comment=2 263 | 264 | # Insert a space between the ending comma and closing bracket of a list, 265 | # etc. 266 | space_between_ending_comma_and_closing_bracket=True 267 | 268 | # Use spaces inside brackets, braces, and parentheses. For example: 269 | # 270 | # method_call( 1 ) 271 | # my_dict[ 3 ][ 1 ][ get_index( *args, **kwargs ) ] 272 | # my_set = { 1, 2, 3 } 273 | space_inside_brackets=False 274 | 275 | # Split before arguments 276 | split_all_comma_separated_values=False 277 | 278 | # Split before arguments, but do not split all subexpressions recursively 279 | # (unless needed). 280 | split_all_top_level_comma_separated_values=False 281 | 282 | # Split before arguments if the argument list is terminated by a 283 | # comma. 284 | split_arguments_when_comma_terminated=False 285 | 286 | # Set to True to prefer splitting before '+', '-', '*', '/', '//', or '@' 287 | # rather than after. 288 | split_before_arithmetic_operator=False 289 | 290 | # Set to True to prefer splitting before '&', '|' or '^' rather than 291 | # after. 292 | split_before_bitwise_operator=True 293 | 294 | # Split before the closing bracket if a list or dict literal doesn't fit on 295 | # a single line. 296 | split_before_closing_bracket=False 297 | 298 | # Split before a dictionary or set generator (comp_for). For example, note 299 | # the split before the 'for': 300 | # 301 | # foo = { 302 | # variable: 'Hello world, have a nice day!' 303 | # for variable in bar if variable != 42 304 | # } 305 | split_before_dict_set_generator=True 306 | 307 | # Split before the '.' if we need to split a longer expression: 308 | # 309 | # foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d)) 310 | # 311 | # would reformat to something like: 312 | # 313 | # foo = ('This is a really long string: {}, {}, {}, {}' 314 | # .format(a, b, c, d)) 315 | split_before_dot=False 316 | 317 | # Split after the opening paren which surrounds an expression if it doesn't 318 | # fit on a single line. 319 | split_before_expression_after_opening_paren=False 320 | 321 | # If an argument / parameter list is going to be split, then split before 322 | # the first argument. 323 | split_before_first_argument=True 324 | 325 | # Set to True to prefer splitting before 'and' or 'or' rather than 326 | # after. 327 | split_before_logical_operator=True 328 | 329 | # Split named assignments onto individual lines. 330 | split_before_named_assigns=True 331 | 332 | # Set to True to split list comprehensions and generators that have 333 | # non-trivial expressions and multiple clauses before each of these 334 | # clauses. For example: 335 | # 336 | # result = [ 337 | # a_long_var + 100 for a_long_var in xrange(1000) 338 | # if a_long_var % 10] 339 | # 340 | # would reformat to something like: 341 | # 342 | # result = [ 343 | # a_long_var + 100 344 | # for a_long_var in xrange(1000) 345 | # if a_long_var % 10] 346 | split_complex_comprehension=True 347 | 348 | # The penalty for splitting right after the opening bracket. 349 | split_penalty_after_opening_bracket=0 350 | 351 | # The penalty for splitting the line after a unary operator. 352 | split_penalty_after_unary_operator=10000 353 | 354 | # The penalty of splitting the line around the '+', '-', '*', '/', '//', 355 | # ``%``, and '@' operators. 356 | split_penalty_arithmetic_operator=300 357 | 358 | # The penalty for splitting right before an if expression. 359 | split_penalty_before_if_expr=30 360 | 361 | # The penalty of splitting the line around the '&', '|', and '^' 362 | # operators. 363 | split_penalty_bitwise_operator=300 364 | 365 | # The penalty for splitting a list comprehension or generator 366 | # expression. 367 | split_penalty_comprehension=80 368 | 369 | # The penalty for characters over the column limit. 370 | split_penalty_excess_character=4500 371 | 372 | # The penalty incurred by adding a line split to the unwrapped line. The 373 | # more line splits added the higher the penalty. 374 | split_penalty_for_added_line_split=30 375 | 376 | # The penalty of splitting a list of "import as" names. For example: 377 | # 378 | # from a_very_long_or_indented_module_name_yada_yad import (long_argument_1, 379 | # long_argument_2, 380 | # long_argument_3) 381 | # 382 | # would reformat to something like: 383 | # 384 | # from a_very_long_or_indented_module_name_yada_yad import ( 385 | # long_argument_1, long_argument_2, long_argument_3) 386 | split_penalty_import_names=0 387 | 388 | # The penalty of splitting the line around the 'and' and 'or' 389 | # operators. 390 | split_penalty_logical_operator=300 391 | 392 | # Use the Tab character for indentation. 393 | use_tabs=False 394 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.2.8] - 2024-08-22 4 | 5 | - Updated wheel builds 6 | 7 | ## [0.2.7] - 2022-11-18 8 | 9 | - PEP 518-compliant with `pyproject.toml` and `build-system` 10 | 11 | ## [0.2.6] - 2021-12-05 12 | 13 | - Use `oldest-supported-numpy` package when building wheels for greatest compatibility support 14 | 15 | ## [0.2.5] - 2021-12-05 16 | 17 | - Build wheels for Mac ARM architectures 18 | 19 | ## [0.2.4] - 2021-12-05 20 | 21 | - Build wheels for Python 3.10 22 | 23 | ## [0.2.3] - 2021-11-08 24 | 25 | - Fix ssize_t on Windows (#24), thanks @davidbrochart ! 26 | 27 | ## [0.2.2] - 2020-10-19 28 | 29 | - Fix accidentally not including the last fix in 0.2.1 30 | 31 | ## [0.2.1] - 2020-10-19 32 | 33 | - Fixes to allow for a Conda package on Windows 34 | 35 | ## [0.2.0] - 2020-10-10 36 | 37 | - Update docs with instructions for saving mesh 38 | - Add `rescale_positions` util 39 | 40 | ## [0.1.0] - 2020-10-05 41 | 42 | - Initial release 43 | 44 | ## [0.0.1], [0.0.2] - 2020-10-03 45 | 46 | - Test releases on PyPI to test auto-building wheels with cibuildwheel 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kyle Barron 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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.md 2 | include LICENSE 3 | include README.md 4 | include pydelatin/py.typed 5 | 6 | global-include *.cpp 7 | global-include *.h 8 | 9 | recursive-include tests * 10 | recursive-exclude * __pycache__ 11 | recursive-exclude * *.py[co] 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pydelatin 2 | 3 | A Python wrapper of [`hmm`][hmm] (of which [Delatin][delatin] is a port) for fast terrain mesh generation. 4 | 5 | [![][image_url]][example] 6 | 7 | [image_url]: https://raw.githubusercontent.com/kylebarron/pydelatin/master/assets/glac.jpg 8 | [example]: https://kylebarron.dev/quantized-mesh-encoder 9 | 10 | [hmm]: https://github.com/fogleman/hmm 11 | [delatin]: https://github.com/mapbox/delatin 12 | 13 | A screenshot of Glacier National Park taken from [the demo][example]. The mesh 14 | is created using `pydelatin`, encoded using 15 | [`quantized-mesh-encoder`][quantized-mesh-encoder], served on-demand using 16 | [`dem-tiler`][dem-tiler], and rendered with [deck.gl](https://deck.gl). 17 | 18 | [quantized-mesh-encoder]: https://github.com/kylebarron/quantized-mesh-encoder 19 | [dem-tiler]: https://github.com/kylebarron/dem-tiler 20 | 21 | ## Install 22 | 23 | With pip: 24 | 25 | ``` 26 | pip install pydelatin 27 | ``` 28 | 29 | or with Conda: 30 | 31 | ``` 32 | conda install -c conda-forge pydelatin 33 | ``` 34 | 35 | On Windows, installing via Conda is strongly recommended. 36 | 37 | If installing with pip on Windows, [`glm`][glm] is a prerequisite for building 38 | from source. Open an issue if you'd like to help package binary wheels for 39 | Windows. 40 | 41 | [glm]: https://glm.g-truc.net/ 42 | 43 | ## Using 44 | 45 | ### Example 46 | 47 | ```py 48 | from pydelatin import Delatin 49 | 50 | tin = Delatin(terrain, width, height) 51 | # Mesh vertices 52 | tin.vertices 53 | # Mesh triangles 54 | tin.triangles 55 | ``` 56 | 57 | ### API 58 | 59 | The API is similar to that of [`hmm`][hmm]. 60 | 61 | Additionally I include a helper function: `decode_ele`, to decode a Mapbox 62 | Terrain RGB or Terrarium PNG array to elevations. 63 | 64 | #### `Delatin` 65 | 66 | ##### Arguments 67 | 68 | - `arr` (numpy `ndarray`): data array. If a 2D array, dimensions are expected to be (height, width). If a 1D array, height and width parameters must be passed, and the array is assumed to be in C order. 69 | - `height` (`int`, default: `None`): height of array; required when arr is not 2D 70 | - `width` (`int`, default: `None`): width of array; required when arr is not 2D 71 | - `z_scale` (`float`, default: `1`): z scale relative to x & y 72 | - `z_exag` (`float`, default: `1`): z exaggeration 73 | - `max_error` (`float`, default: `0.001`): maximum triangulation error 74 | - `max_triangles` (`int`, default: `None`): maximum number of triangles 75 | - `max_points` (`int`, default: `None`): maximum number of vertices 76 | - `base_height` (`float`, default: `0`): solid base height 77 | - `level` (`bool`, default: `False`): auto level input to full grayscale range 78 | - `invert` (`bool`, default: `False`): invert heightmap 79 | - `blur` (`int`, default: `0`): gaussian blur sigma 80 | - `gamma` (`float`, default: `0`): gamma curve exponent 81 | - `border_size` (`int`, default: `0`): border size in pixels 82 | - `border_height` (`float`, default: `1`): border z height 83 | 84 | ##### Attributes 85 | 86 | - `vertices` (`ndarray` of shape `(-1, 3)`): the interleaved 3D coordinates of each vertex, e.g. `[[x0, y0, z0], [x1, y1, z1], ...]`. 87 | - `triangles` (`ndarray` of shape `(-1, 3)`): represents _indices_ within the `vertices` array. So `[0, 1, 3, ...]` would use the first, second, and fourth vertices within the `vertices` array as a single triangle. 88 | - `error` (`float`): the maximum error of the mesh. 89 | 90 | #### `util.rescale_positions` 91 | 92 | A helper function to rescale the `vertices` output to a new bounding box. 93 | Returns an `ndarray` of shape `(-1, 3)` with positions rescaled. Each row 94 | represents a single 3D point. 95 | 96 | ##### Arguments 97 | 98 | - `vertices`: (`np.ndarray`) vertices output from Delatin 99 | - `bounds`: (`Tuple[float]`) linearly rescale position values to this extent. 100 | Expected to be `[minx, miny, maxx, maxy]`. 101 | - `flip_y`: (`bool`, default `False`) Flip y coordinates. Can be useful since 102 | images' coordinate origin is in the top left. 103 | 104 | ### Saving to mesh formats 105 | 106 | #### Quantized Mesh 107 | 108 | A common mesh format for the web is the [Quantized Mesh][quantized-mesh-spec] 109 | format, which is supported in Cesium and deck.gl (via 110 | [loaders.gl][loaders.gl-quantized-mesh]). You can use 111 | [`quantized-mesh-encoder`][quantized-mesh-encoder] to save in this format: 112 | 113 | ```py 114 | import quantized_mesh_encoder 115 | from pydelatin import Delatin 116 | from pydelatin.util import rescale_positions 117 | 118 | tin = Delatin(terrain, max_error=30) 119 | vertices, triangles = tin.vertices, tin.triangles 120 | 121 | # Rescale vertices linearly from pixel units to world coordinates 122 | rescaled_vertices = rescale_positions(vertices, bounds) 123 | 124 | with open('output.terrain', 'wb') as f: 125 | quantized_mesh_encoder.encode(f, rescaled_vertices, triangles) 126 | ``` 127 | 128 | [quantized-mesh-spec]: https://github.com/CesiumGS/quantized-mesh 129 | [quantized-mesh-encoder]: https://github.com/kylebarron/quantized-mesh-encoder 130 | [loaders.gl-quantized-mesh]: https://loaders.gl/modules/terrain/docs/api-reference/quantized-mesh-loader 131 | 132 | #### Meshio 133 | 134 | Alternatively, you can save to a variety of mesh formats using 135 | [`meshio`][meshio]: 136 | 137 | ```py 138 | from pydelatin import Delatin 139 | import meshio 140 | 141 | tin = Delatin(terrain, max_error=30) 142 | vertices, triangles = tin.vertices, tin.triangles 143 | 144 | cells = [("triangle", triangles)] 145 | mesh = meshio.Mesh(vertices, cells) 146 | # Example output format 147 | # Refer to meshio documentation 148 | mesh.write('foo.vtk') 149 | ``` 150 | 151 | [meshio]: https://github.com/nschloe/meshio 152 | 153 | ## `Martini` or `Delatin`? 154 | 155 | Two popular algorithms for terrain mesh generation are the **"Martini"** 156 | algorithm, found in the JavaScript [`martini`][martini] library and the Python 157 | [`pymartini`][pymartini] library, and the **"Delatin"** algorithm, found in the 158 | C++ [`hmm`][hmm] library, this Python `pydelatin` library, and the JavaScript 159 | [`delatin`][delatin] library. 160 | 161 | Which to use? 162 | 163 | For most purposes, use `pydelatin` over `pymartini`. A good breakdown from [a 164 | Martini issue][martini_desc_issue]: 165 | 166 | > Martini: 167 | > 168 | > - Only works on square 2^n+1 x 2^n+1 grids. 169 | > - Generates a hierarchy of meshes (pick arbitrary detail after a single run) 170 | > - Optimized for meshing speed rather than quality. 171 | > 172 | > Delatin: 173 | > 174 | > - Works on arbitrary raster grids. 175 | > - Generates a single mesh for a particular detail. 176 | > - Optimized for quality (as few triangles as possible for a given error). 177 | 178 | [martini]: https://github.com/mapbox/martini 179 | [pymartini]: https://github.com/kylebarron/pymartini 180 | [martini_desc_issue]: https://github.com/mapbox/martini/issues/15#issuecomment-700475731 181 | 182 | ## Benchmark 183 | 184 | The following uses the same dataset as the [`pymartini` 185 | benchmarks][pymartini_bench], a 512x512 pixel heightmap of Mt. Fuji. 186 | 187 | [pymartini_bench]: https://github.com/kylebarron/pymartini#benchmark 188 | 189 | For the 30-meter mesh, `pydelatin` is 25% slower than `pymartini`, but the mesh 190 | is much more efficient: it has 40% fewer vertices and triangles. 191 | 192 | `pydelatin` is 4-5x faster than the JavaScript `delatin` package. 193 | 194 | ### Python 195 | 196 | ```bash 197 | git clone https://github.com/kylebarron/pydelatin 198 | cd pydelatin 199 | pip install '.[test]' 200 | python bench.py 201 | ``` 202 | 203 | ``` 204 | mesh (max_error=30m): 27.322ms 205 | vertices: 5668, triangles: 11140 206 | 207 | mesh (max_error=1m): 282.946ms 208 | mesh (max_error=2m): 215.839ms 209 | mesh (max_error=3m): 163.424ms 210 | mesh (max_error=4m): 127.203ms 211 | mesh (max_error=5m): 106.596ms 212 | mesh (max_error=6m): 91.868ms 213 | mesh (max_error=7m): 82.572ms 214 | mesh (max_error=8m): 74.335ms 215 | mesh (max_error=9m): 65.893ms 216 | mesh (max_error=10m): 60.999ms 217 | mesh (max_error=11m): 55.213ms 218 | mesh (max_error=12m): 54.475ms 219 | mesh (max_error=13m): 48.662ms 220 | mesh (max_error=14m): 47.029ms 221 | mesh (max_error=15m): 44.517ms 222 | mesh (max_error=16m): 42.059ms 223 | mesh (max_error=17m): 39.699ms 224 | mesh (max_error=18m): 37.657ms 225 | mesh (max_error=19m): 36.333ms 226 | mesh (max_error=20m): 34.131ms 227 | ``` 228 | 229 | ### JS (Node) 230 | 231 | This benchmarks against the [`delatin`][delatin] JavaScript module. 232 | 233 | ```bash 234 | git clone https://github.com/kylebarron/pydelatin 235 | cd test/bench_js/ 236 | yarn 237 | wget https://raw.githubusercontent.com/mapbox/delatin/master/index.js 238 | node -r esm bench.js 239 | ``` 240 | 241 | ``` 242 | mesh (max_error=30m): 143.038ms 243 | vertices: 5668 244 | triangles: 11140 245 | 246 | mesh (max_error=0m): 1169.226ms 247 | mesh (max_error=1m): 917.290ms 248 | mesh (max_error=2m): 629.776ms 249 | mesh (max_error=3m): 476.958ms 250 | mesh (max_error=4m): 352.907ms 251 | mesh (max_error=5m): 290.946ms 252 | mesh (max_error=6m): 240.556ms 253 | mesh (max_error=7m): 234.181ms 254 | mesh (max_error=8m): 188.273ms 255 | mesh (max_error=9m): 162.743ms 256 | mesh (max_error=10m): 145.734ms 257 | mesh (max_error=11m): 130.119ms 258 | mesh (max_error=12m): 119.865ms 259 | mesh (max_error=13m): 114.645ms 260 | mesh (max_error=14m): 101.390ms 261 | mesh (max_error=15m): 100.065ms 262 | mesh (max_error=16m): 96.247ms 263 | mesh (max_error=17m): 89.508ms 264 | mesh (max_error=18m): 85.754ms 265 | mesh (max_error=19m): 79.838ms 266 | mesh (max_error=20m): 75.607ms 267 | ``` 268 | 269 | ## License 270 | 271 | This package wraps \@fogleman's [`hmm`][hmm], a C++ library that is also 272 | MIT-licensed. 273 | -------------------------------------------------------------------------------- /assets/glac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylebarron/pydelatin/49ffad710548f1bd2839bf49b97a44431d88a382/assets/glac.jpg -------------------------------------------------------------------------------- /bench.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | from imageio import imread 4 | 5 | from pydelatin import Delatin 6 | from pydelatin.util import decode_ele 7 | 8 | path = './test/data/fuji.png' 9 | fuji = imread(path) 10 | terrain = decode_ele(fuji, 'mapbox') 11 | 12 | start = time() 13 | tin = Delatin(terrain, max_error=30) 14 | vertices, triangles = tin.vertices, tin.triangles 15 | end = time() 16 | 17 | print(f'mesh (max_error=30m): {(end - start) * 1000:.3f}ms') 18 | print(f'vertices: {vertices.shape[0]}, triangles: {triangles.shape[0]}\n') 19 | 20 | for i in range(1, 21): 21 | start = time() 22 | Delatin(terrain, max_error=i) 23 | end = time() 24 | print(f'mesh (max_error={i}m): {(end - start) * 1000:.3f}ms') 25 | -------------------------------------------------------------------------------- /pydelatin/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for pydelatin.""" 2 | 3 | __author__ = """Kyle Barron""" 4 | __email__ = 'kylebarron2@gmail.com' 5 | __version__ = '0.2.7' 6 | 7 | from . import util 8 | from .delatin import Delatin 9 | -------------------------------------------------------------------------------- /pydelatin/delatin.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import numpy as np 4 | 5 | from _pydelatin import PydelatinTriangulator 6 | 7 | 8 | class Delatin: 9 | def __init__( 10 | self, 11 | arr: np.ndarray, 12 | *, 13 | height: Optional[int] = None, 14 | width: Optional[int] = None, 15 | z_scale: float = 1, 16 | z_exag: float = 1, 17 | max_error: float = 0.001, 18 | max_triangles: Optional[int] = None, 19 | max_points: Optional[int] = None, 20 | base_height: float = 0, 21 | level: bool = False, 22 | invert: bool = False, 23 | blur: int = 0, 24 | gamma: float = 0, 25 | border_size: int = 0, 26 | border_height: float = 1 27 | ): 28 | """ 29 | 30 | Args: 31 | - arr: data array. If a 2D array, dimensions are expected to be 32 | (height, width). If a 1D array, height and width parameters must 33 | be passed, and the array is assumed to be in C order. 34 | 35 | Kwargs: 36 | - height: height of array; required when arr is not 2D 37 | - width: width of array; required when arr is not 2D 38 | - z_scale: z scale relative to x & y 39 | - z_exag: z exaggeration 40 | - max_error: maximum triangulation error 41 | - max_triangles: maximum number of triangles 42 | - max_points: maximum number of vertices 43 | - base_height: solid base height 44 | - level: auto level input to full grayscale range 45 | - invert: invert heightmap 46 | - blur: gaussian blur sigma 47 | - gamma: gamma curve exponent 48 | - border_size: border size in pixels 49 | - border_height: border z height 50 | """ 51 | max_triangles = max_triangles if max_triangles is not None else 0 52 | max_points = max_points if max_points is not None else 0 53 | 54 | if len(arr.shape) != 2: 55 | if height is None or width is None: 56 | msg = 'Height and width must be passed when arr is not 2D' 57 | raise ValueError(msg) 58 | else: 59 | height, width = arr.shape 60 | 61 | self.tri = PydelatinTriangulator( 62 | width, 63 | height, 64 | max_error, 65 | z_scale, 66 | z_exag, 67 | max_triangles, 68 | max_points, 69 | level, 70 | invert, 71 | blur, 72 | gamma, 73 | border_size, 74 | border_height, 75 | base_height, 76 | ) 77 | self.tri.setData(arr.flatten()) 78 | self.tri.run() 79 | 80 | @property 81 | def vertices(self): 82 | return self.tri.getPoints().reshape(-1, 3) 83 | 84 | @property 85 | def triangles(self): 86 | return self.tri.getTriangles().reshape(-1, 3).astype(np.uint32) 87 | 88 | @property 89 | def error(self): 90 | return self.tri.getError() 91 | -------------------------------------------------------------------------------- /pydelatin/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylebarron/pydelatin/49ffad710548f1bd2839bf49b97a44431d88a382/pydelatin/py.typed -------------------------------------------------------------------------------- /pydelatin/util.py: -------------------------------------------------------------------------------- 1 | import math 2 | from typing import Tuple 3 | 4 | import numpy as np 5 | 6 | 7 | # This is copied from pymartini 8 | def decode_ele(png: np.ndarray, encoding: str) -> np.ndarray: 9 | """Decode array to elevations 10 | Arguments: 11 | - png (np.ndarray). Ndarray of elevations encoded in three channels, 12 | representing red, green, and blue. Must be of shape (tile_size, 13 | tile_size, >=3), where `tile_size` is usually 256 or 512 14 | - encoding: (str): Either 'mapbox' or 'terrarium', the two main RGB 15 | encodings for elevation values. 16 | Returns: 17 | (np.array) Array of shape (tile_size^2) with decoded elevation values 18 | """ 19 | allowed_encodings = ['mapbox', 'terrarium'] 20 | if encoding not in allowed_encodings: 21 | raise ValueError(f'encoding must be one of {allowed_encodings}') 22 | 23 | if png.shape[0] <= 4: 24 | png = png.T 25 | 26 | # Get bands 27 | if encoding == 'mapbox': 28 | red = png[:, :, 0] * (256 * 256) 29 | green = png[:, :, 1] * (256) 30 | blue = png[:, :, 2] 31 | 32 | # Compute float height 33 | terrain = (red + green + blue) / 10 - 10000 34 | elif encoding == 'terrarium': 35 | red = png[:, :, 0] * (256) 36 | green = png[:, :, 1] 37 | blue = png[:, :, 2] / 256 38 | 39 | # Compute float height 40 | terrain = (red + green + blue) - 32768 41 | 42 | return terrain 43 | 44 | 45 | def rescale_positions( 46 | vertices: np.ndarray, 47 | bounds: Tuple[float, float, float, float], 48 | flip_y: bool = False, 49 | ): 50 | """Rescale positions to bounding box 51 | 52 | Args: 53 | - vertices: vertices output from Delatin 54 | - bounds: linearly rescale position values to this extent, expected to 55 | be [minx, miny, maxx, maxy]. 56 | - flip_y: (bool) Flip y coordinates. Can be useful since images' 57 | coordinate origin is in the top left. 58 | 59 | Returns: 60 | (np.ndarray): ndarray of shape (-1, 3) with positions rescaled. Each row 61 | represents a single 3D point. 62 | """ 63 | out = np.zeros(vertices.shape, dtype=np.float32) 64 | 65 | tile_size = vertices[:, :2].max() 66 | minx, miny, maxx, maxy = bounds 67 | x_scale = (maxx - minx) / tile_size 68 | y_scale = (maxy - miny) / tile_size 69 | 70 | if flip_y: 71 | scalar = np.array([x_scale, -y_scale]) 72 | offset = np.array([minx, maxy]) 73 | else: 74 | scalar = np.array([x_scale, y_scale]) 75 | offset = np.array([minx, miny]) 76 | 77 | # Rescale x, y positions 78 | out[:, :2] = vertices[:, :2] * scalar + offset 79 | out[:, 2] = vertices[:, 2] 80 | return out 81 | 82 | 83 | def latitude_adjustment(lat: float): 84 | """Latitude adjustment for web-mercator projection 85 | 86 | Args: 87 | - lat: latitude in degrees 88 | """ 89 | return math.cos(math.radians(lat)) 90 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["pybind11", "setuptools", "wheel", "cython>=0.29.29", "oldest-supported-numpy"] 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.2.7 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:pydelatin/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | max-line-length = 80 20 | 21 | [aliases] 22 | test = pytest 23 | 24 | [tool:pytest] 25 | collect_ignore = ['setup.py'] 26 | 27 | [isort] 28 | line_length = 80 29 | multi_line_output = 4 30 | known_first_party = _pydelatin 31 | 32 | [pycodestyle] 33 | max-line-length = 80 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | # pylint: disable=import-outside-toplevel 3 | from glob import glob 4 | 5 | from pybind11.setup_helpers import Pybind11Extension, build_ext 6 | from setuptools import find_packages, setup 7 | 8 | with open("README.md") as f: 9 | readme = f.read() 10 | 11 | ext_modules = [ 12 | Pybind11Extension( 13 | "_pydelatin", 14 | sorted(glob("src/*.cpp")), # Sort source files for reproducibility 15 | ), 16 | ] 17 | 18 | 19 | setup( 20 | name="pydelatin", 21 | version="0.2.8", 22 | python_requires=">=3.6", 23 | author="Kyle Barron", 24 | author_email="kylebarron2@gmail.com", 25 | url="https://github.com/kylebarron/pydelatin", 26 | description="A wrapper for hmm", 27 | long_description=readme, 28 | long_description_content_type="text/markdown", 29 | packages=find_packages(include=["pydelatin", "pydelatin.*"]), 30 | ext_modules=ext_modules, 31 | install_requires=["numpy"], 32 | extras_require={"test": ["pytest", "pytest-benchmark", "imageio"]}, 33 | cmdclass={"build_ext": build_ext}, 34 | zip_safe=False, 35 | ) 36 | -------------------------------------------------------------------------------- /src/base.cpp: -------------------------------------------------------------------------------- 1 | #include "base.h" 2 | 3 | #define GLM_ENABLE_EXPERIMENTAL 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | void AddBase( 10 | std::vector &points, 11 | std::vector &triangles, 12 | const int w, const int h, const float z) 13 | { 14 | const int w1 = w - 1; 15 | const int h1 = h - 1; 16 | 17 | std::map x0s; 18 | std::map x1s; 19 | std::map y0s; 20 | std::map y1s; 21 | std::unordered_map lookup; 22 | 23 | // find points along each edge 24 | for (int i = 0; i < points.size(); i++) { 25 | const auto &p = points[i]; 26 | bool edge = false; 27 | if (p.x == 0) { 28 | x0s[p.y] = p.z; 29 | edge = true; 30 | } else if (p.x == w1) { 31 | x1s[p.y] = p.z; 32 | edge = true; 33 | } 34 | if (p.y == 0) { 35 | y0s[p.x] = p.z; 36 | edge = true; 37 | } else if (p.y == h1) { 38 | y1s[p.x] = p.z; 39 | edge = true; 40 | } 41 | if (edge) { 42 | lookup[p] = i; 43 | } 44 | } 45 | 46 | std::vector> sx0s(x0s.begin(), x0s.end()); 47 | std::vector> sx1s(x1s.begin(), x1s.end()); 48 | std::vector> sy0s(y0s.begin(), y0s.end()); 49 | std::vector> sy1s(y1s.begin(), y1s.end()); 50 | 51 | const auto pointIndex = [&lookup, &points]( 52 | const float x, const float y, const float z) 53 | { 54 | const glm::vec3 point(x, y, z); 55 | if (lookup.find(point) == lookup.end()) { 56 | lookup[point] = points.size(); 57 | points.push_back(point); 58 | } 59 | return lookup[point]; 60 | }; 61 | 62 | // compute base center point 63 | const int center = pointIndex(w * 0.5f, h * 0.5f, z); 64 | 65 | // edge x = 0 66 | for (int i = 1; i < sx0s.size(); i++) { 67 | const int y0 = sx0s[i-1].first; 68 | const int y1 = sx0s[i].first; 69 | const float z0 = sx0s[i-1].second; 70 | const float z1 = sx0s[i].second; 71 | const int p00 = pointIndex(0, y0, z); 72 | const int p01 = pointIndex(0, y0, z0); 73 | const int p10 = pointIndex(0, y1, z); 74 | const int p11 = pointIndex(0, y1, z1); 75 | triangles.emplace_back(p01, p10, p00); 76 | triangles.emplace_back(p01, p11, p10); 77 | triangles.emplace_back(center, p00, p10); 78 | } 79 | 80 | // edge x = w1 81 | for (int i = 1; i < sx1s.size(); i++) { 82 | const int y0 = sx1s[i-1].first; 83 | const int y1 = sx1s[i].first; 84 | const float z0 = sx1s[i-1].second; 85 | const float z1 = sx1s[i].second; 86 | const int p00 = pointIndex(w1, y0, z); 87 | const int p01 = pointIndex(w1, y0, z0); 88 | const int p10 = pointIndex(w1, y1, z); 89 | const int p11 = pointIndex(w1, y1, z1); 90 | triangles.emplace_back(p00, p10, p01); 91 | triangles.emplace_back(p10, p11, p01); 92 | triangles.emplace_back(center, p10, p00); 93 | } 94 | 95 | // edge y = 0 96 | for (int i = 1; i < sy0s.size(); i++) { 97 | const int x0 = sy0s[i-1].first; 98 | const int x1 = sy0s[i].first; 99 | const float z0 = sy0s[i-1].second; 100 | const float z1 = sy0s[i].second; 101 | const int p00 = pointIndex(x0, 0, z); 102 | const int p01 = pointIndex(x0, 0, z0); 103 | const int p10 = pointIndex(x1, 0, z); 104 | const int p11 = pointIndex(x1, 0, z1); 105 | triangles.emplace_back(p00, p10, p01); 106 | triangles.emplace_back(p10, p11, p01); 107 | triangles.emplace_back(center, p10, p00); 108 | } 109 | 110 | // edge y = h1 111 | for (int i = 1; i < sy1s.size(); i++) { 112 | const int x0 = sy1s[i-1].first; 113 | const int x1 = sy1s[i].first; 114 | const float z0 = sy1s[i-1].second; 115 | const float z1 = sy1s[i].second; 116 | const int p00 = pointIndex(x0, h1, z); 117 | const int p01 = pointIndex(x0, h1, z0); 118 | const int p10 = pointIndex(x1, h1, z); 119 | const int p11 = pointIndex(x1, h1, z1); 120 | triangles.emplace_back(p01, p10, p00); 121 | triangles.emplace_back(p01, p11, p10); 122 | triangles.emplace_back(center, p00, p10); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void AddBase( 7 | std::vector &points, 8 | std::vector &triangles, 9 | const int w, const int h, const float z); 10 | -------------------------------------------------------------------------------- /src/blur.cpp: -------------------------------------------------------------------------------- 1 | #include "blur.h" 2 | 3 | #include 4 | 5 | // see: http://blog.ivank.net/fastest-gaussian-blur.html 6 | 7 | namespace { 8 | 9 | std::vector BoxesForGaussian(const float sigma, const int n) { 10 | const float wIdeal = std::sqrt((12 * sigma * sigma / n) + 1); 11 | int wl = wIdeal; 12 | if (wl % 2 == 0) { 13 | wl--; 14 | } 15 | const int wu = wl + 2; 16 | 17 | const float mIdeal = 18 | (12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / 19 | (-4 * wl - 4); 20 | const int m = std::round(mIdeal); 21 | 22 | std::vector sizes; 23 | for (int i = 0; i < n; i++) { 24 | sizes.push_back(i < m ? wl : wu); 25 | } 26 | return sizes; 27 | } 28 | 29 | void BoxBlurH( 30 | std::vector &src, 31 | std::vector &dst, 32 | const int w, const int h, const int r) 33 | { 34 | const float m = 1.f / (r + r + 1); 35 | for (int i = 0; i < h; i++) { 36 | int ti = i * w; 37 | int li = ti; 38 | int ri = ti + r; 39 | float fv = src[ti]; 40 | float lv = src[ti + w - 1]; 41 | float val = (r + 1) * fv; 42 | for (int j = 0; j < r; j++) { 43 | val += src[ti + j]; 44 | } 45 | for (int j = 0; j <= r; j++) { 46 | val += src[ri] - fv; 47 | dst[ti] = val * m; 48 | ri++; 49 | ti++; 50 | } 51 | for (int j = r + 1; j < w - r; j++) { 52 | val += src[ri] - src[li]; 53 | dst[ti] = val * m; 54 | li++; 55 | ri++; 56 | ti++; 57 | } 58 | for (int j = w - r; j < w; j++) { 59 | val += lv - src[li]; 60 | dst[ti] = val * m; 61 | li++; 62 | ti++; 63 | } 64 | } 65 | } 66 | 67 | void BoxBlurV( 68 | std::vector &src, 69 | std::vector &dst, 70 | const int w, const int h, const int r) 71 | { 72 | const float m = 1.f / (r + r + 1); 73 | for (int i = 0; i < w; i++) { 74 | int ti = i; 75 | int li = ti; 76 | int ri = ti + r * w; 77 | float fv = src[ti]; 78 | float lv = src[ti + w * (h - 1)]; 79 | float val = (r + 1) * fv; 80 | for (int j = 0; j < r; j++) { 81 | val += src[ti + j * w]; 82 | } 83 | for (int j = 0; j <= r; j++) { 84 | val += src[ri] - fv; 85 | dst[ti] = val * m; 86 | ri += w; 87 | ti += w; 88 | } 89 | for (int j = r + 1; j < h - r; j++) { 90 | val += src[ri] - src[li]; 91 | dst[ti] = val * m; 92 | li += w; 93 | ri += w; 94 | ti += w; 95 | } 96 | for (int j = h - r; j < h; j++) { 97 | val += lv - src[li]; 98 | dst[ti] = val * m; 99 | li += w; 100 | ti += w; 101 | } 102 | } 103 | } 104 | 105 | 106 | void BoxBlur( 107 | std::vector &src, 108 | std::vector &dst, 109 | const int w, const int h, const int r) 110 | { 111 | dst.assign(src.begin(), src.end()); 112 | BoxBlurH(dst, src, w, h, r); 113 | BoxBlurV(src, dst, w, h, r); 114 | } 115 | 116 | } 117 | 118 | std::vector GaussianBlur( 119 | const std::vector &data, 120 | const int w, const int h, const int r) 121 | { 122 | std::vector src = data; 123 | std::vector dst(data.size()); 124 | const std::vector boxes = BoxesForGaussian(r, 3); 125 | BoxBlur(src, dst, w, h, (boxes[0] - 1) / 2); 126 | BoxBlur(dst, src, w, h, (boxes[1] - 1) / 2); 127 | BoxBlur(src, dst, w, h, (boxes[2] - 1) / 2); 128 | return dst; 129 | } 130 | -------------------------------------------------------------------------------- /src/blur.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | std::vector GaussianBlur( 6 | const std::vector &data, 7 | const int w, const int h, const int r); 8 | -------------------------------------------------------------------------------- /src/heightmap.cpp: -------------------------------------------------------------------------------- 1 | #include "heightmap.h" 2 | 3 | #define GLM_ENABLE_EXPERIMENTAL 4 | #include 5 | 6 | #include "blur.h" 7 | 8 | #define STB_IMAGE_IMPLEMENTATION 9 | #include "stb_image.h" 10 | 11 | #define STB_IMAGE_WRITE_IMPLEMENTATION 12 | #include "stb_image_write.h" 13 | 14 | Heightmap::Heightmap(const std::string &path) : 15 | m_Width(0), 16 | m_Height(0) 17 | { 18 | int w, h, c; 19 | uint16_t *data = stbi_load_16(path.c_str(), &w, &h, &c, 1); 20 | if (!data) { 21 | return; 22 | } 23 | m_Width = w; 24 | m_Height = h; 25 | const int n = w * h; 26 | const float m = 1.f / 65535.f; 27 | m_Data.resize(n); 28 | for (int i = 0; i < n; i++) { 29 | m_Data[i] = data[i] * m; 30 | } 31 | free(data); 32 | } 33 | 34 | Heightmap::Heightmap( 35 | const int width, 36 | const int height, 37 | const std::vector &data) : 38 | m_Width(width), 39 | m_Height(height), 40 | m_Data(data) 41 | {} 42 | 43 | void Heightmap::AutoLevel() { 44 | float lo = m_Data[0]; 45 | float hi = m_Data[0]; 46 | for (int i = 0; i < m_Data.size(); i++) { 47 | lo = std::min(lo, m_Data[i]); 48 | hi = std::max(hi, m_Data[i]); 49 | } 50 | if (hi == lo) { 51 | return; 52 | } 53 | for (int i = 0; i < m_Data.size(); i++) { 54 | m_Data[i] = (m_Data[i] - lo) / (hi - lo); 55 | } 56 | } 57 | 58 | void Heightmap::Invert() { 59 | for (int i = 0; i < m_Data.size(); i++) { 60 | m_Data[i] = 1.f - m_Data[i]; 61 | } 62 | } 63 | 64 | void Heightmap::GammaCurve(const float gamma) { 65 | for (int i = 0; i < m_Data.size(); i++) { 66 | m_Data[i] = std::pow(m_Data[i], gamma); 67 | } 68 | } 69 | 70 | void Heightmap::AddBorder(const int size, const float z) { 71 | const int w = m_Width + size * 2; 72 | const int h = m_Height + size * 2; 73 | std::vector data(w * h, z); 74 | int i = 0; 75 | for (int y = 0; y < m_Height; y++) { 76 | int j = (y + size) * w + size; 77 | for (int x = 0; x < m_Width; x++) { 78 | data[j++] = m_Data[i++]; 79 | } 80 | } 81 | m_Width = w; 82 | m_Height = h; 83 | m_Data = data; 84 | } 85 | 86 | void Heightmap::GaussianBlur(const int r) { 87 | m_Data = ::GaussianBlur(m_Data, m_Width, m_Height, r); 88 | } 89 | 90 | std::vector Heightmap::Normalmap(const float zScale) const { 91 | const int w = m_Width - 1; 92 | const int h = m_Height - 1; 93 | std::vector result(w * h); 94 | int i = 0; 95 | for (int y0 = 0; y0 < h; y0++) { 96 | const int y1 = y0 + 1; 97 | const float yc = y0 + 0.5f; 98 | for (int x0 = 0; x0 < w; x0++) { 99 | const int x1 = x0 + 1; 100 | const float xc = x0 + 0.5f; 101 | const float z00 = At(x0, y0) * -zScale; 102 | const float z01 = At(x0, y1) * -zScale; 103 | const float z10 = At(x1, y0) * -zScale; 104 | const float z11 = At(x1, y1) * -zScale; 105 | const float zc = (z00 + z01 + z10 + z11) / 4.f; 106 | const glm::vec3 p00(x0, y0, z00); 107 | const glm::vec3 p01(x0, y1, z01); 108 | const glm::vec3 p10(x1, y0, z10); 109 | const glm::vec3 p11(x1, y1, z11); 110 | const glm::vec3 pc(xc, yc, zc); 111 | const glm::vec3 n0 = glm::triangleNormal(pc, p00, p10); 112 | const glm::vec3 n1 = glm::triangleNormal(pc, p10, p11); 113 | const glm::vec3 n2 = glm::triangleNormal(pc, p11, p01); 114 | const glm::vec3 n3 = glm::triangleNormal(pc, p01, p00); 115 | result[i] = glm::normalize(n0 + n1 + n2 + n3); 116 | i++; 117 | } 118 | } 119 | return result; 120 | } 121 | 122 | void Heightmap::SaveNormalmap( 123 | const std::string &path, 124 | const float zScale) const 125 | { 126 | const std::vector nm = Normalmap(zScale); 127 | std::vector data(nm.size() * 3); 128 | int i = 0; 129 | for (glm::vec3 n : nm) { 130 | n = (n + 1.f) / 2.f; 131 | data[i++] = uint8_t(n.x * 255); 132 | data[i++] = uint8_t(n.y * 255); 133 | data[i++] = uint8_t(n.z * 255); 134 | } 135 | stbi_write_png( 136 | path.c_str(), m_Width - 1, m_Height - 1, 3, 137 | data.data(), (m_Width - 1) * 3); 138 | } 139 | 140 | std::pair Heightmap::FindCandidate( 141 | const glm::ivec2 p0, 142 | const glm::ivec2 p1, 143 | const glm::ivec2 p2) const 144 | { 145 | const auto edge = []( 146 | const glm::ivec2 a, const glm::ivec2 b, const glm::ivec2 c) 147 | { 148 | return (b.x - c.x) * (a.y - c.y) - (b.y - c.y) * (a.x - c.x); 149 | }; 150 | 151 | // triangle bounding box 152 | const glm::ivec2 min = glm::min(glm::min(p0, p1), p2); 153 | const glm::ivec2 max = glm::max(glm::max(p0, p1), p2); 154 | 155 | // forward differencing variables 156 | int w00 = edge(p1, p2, min); 157 | int w01 = edge(p2, p0, min); 158 | int w02 = edge(p0, p1, min); 159 | const int a01 = p1.y - p0.y; 160 | const int b01 = p0.x - p1.x; 161 | const int a12 = p2.y - p1.y; 162 | const int b12 = p1.x - p2.x; 163 | const int a20 = p0.y - p2.y; 164 | const int b20 = p2.x - p0.x; 165 | 166 | // pre-multiplied z values at vertices 167 | const float a = edge(p0, p1, p2); 168 | const float z0 = At(p0) / a; 169 | const float z1 = At(p1) / a; 170 | const float z2 = At(p2) / a; 171 | 172 | // iterate over pixels in bounding box 173 | float maxError = 0; 174 | glm::ivec2 maxPoint(0); 175 | for (int y = min.y; y <= max.y; y++) { 176 | // compute starting offset 177 | int dx = 0; 178 | if (w00 < 0 && a12 != 0) { 179 | dx = std::max(dx, -w00 / a12); 180 | } 181 | if (w01 < 0 && a20 != 0) { 182 | dx = std::max(dx, -w01 / a20); 183 | } 184 | if (w02 < 0 && a01 != 0) { 185 | dx = std::max(dx, -w02 / a01); 186 | } 187 | 188 | int w0 = w00 + a12 * dx; 189 | int w1 = w01 + a20 * dx; 190 | int w2 = w02 + a01 * dx; 191 | 192 | bool wasInside = false; 193 | 194 | for (int x = min.x + dx; x <= max.x; x++) { 195 | // check if inside triangle 196 | if (w0 >= 0 && w1 >= 0 && w2 >= 0) { 197 | wasInside = true; 198 | 199 | // compute z using barycentric coordinates 200 | const float z = z0 * w0 + z1 * w1 + z2 * w2; 201 | const float dz = std::abs(z - At(x, y)); 202 | if (dz > maxError) { 203 | maxError = dz; 204 | maxPoint = glm::ivec2(x, y); 205 | } 206 | } else if (wasInside) { 207 | break; 208 | } 209 | 210 | w0 += a12; 211 | w1 += a20; 212 | w2 += a01; 213 | } 214 | 215 | w00 += b12; 216 | w01 += b20; 217 | w02 += b01; 218 | } 219 | 220 | if (maxPoint == p0 || maxPoint == p1 || maxPoint == p2) { 221 | maxError = 0; 222 | } 223 | 224 | return std::make_pair(maxPoint, maxError); 225 | } 226 | -------------------------------------------------------------------------------- /src/heightmap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class Heightmap { 10 | public: 11 | Heightmap(const std::string &path); 12 | 13 | Heightmap( 14 | const int width, 15 | const int height, 16 | const std::vector &data); 17 | 18 | int Width() const { 19 | return m_Width; 20 | } 21 | 22 | int Height() const { 23 | return m_Height; 24 | } 25 | 26 | float At(const int x, const int y) const { 27 | return m_Data[y * m_Width + x]; 28 | } 29 | 30 | float At(const glm::ivec2 p) const { 31 | return m_Data[p.y * m_Width + p.x]; 32 | } 33 | 34 | void AutoLevel(); 35 | 36 | void Invert(); 37 | 38 | void GammaCurve(const float gamma); 39 | 40 | void AddBorder(const int size, const float z); 41 | 42 | void GaussianBlur(const int r); 43 | 44 | std::vector Normalmap(const float zScale) const; 45 | 46 | void SaveNormalmap(const std::string &path, const float zScale) const; 47 | 48 | std::pair FindCandidate( 49 | const glm::ivec2 p0, 50 | const glm::ivec2 p1, 51 | const glm::ivec2 p2) const; 52 | 53 | private: 54 | int m_Width; 55 | int m_Height; 56 | std::vector m_Data; 57 | }; 58 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // No longer needed because I'm creating numpy arrays to pass back 6 | // #include "pybind11_glm.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "base.h" 14 | #include "heightmap.h" 15 | #include "triangulator.h" 16 | 17 | namespace py = pybind11; 18 | 19 | struct PydelatinTriangulator { 20 | PydelatinTriangulator( 21 | const int width, const int height, 22 | const float maxError, const float zScale, const float zExaggeration, 23 | const int maxTriangles, const int maxPoints, const bool level, 24 | const bool invert, const int blurSigma, const float gamma, 25 | const int borderSize, const float borderHeight, const float baseHeight 26 | ) : 27 | width(width), height(height), maxError(maxError), zScale(zScale), 28 | zExaggeration(zExaggeration), maxTriangles(maxTriangles), maxPoints(maxPoints), 29 | level(level), invert(invert), blurSigma(blurSigma), gamma(gamma), 30 | borderSize(borderSize), borderHeight(borderHeight), baseHeight(baseHeight) 31 | { } 32 | 33 | void setWidth(const int &width_) { width = width_; } 34 | const int &getWidth() const { return width; } 35 | 36 | void setHeight(const int &height_) { height = height_; } 37 | const int &getHeight() const { return height; } 38 | 39 | void setMaxError(const float &maxError_) { maxError = maxError_; } 40 | const float &getMaxError() const { return maxError; } 41 | 42 | void setData(const py::array_t &data_) { 43 | // x must have ndim = 1; can be non-writeable 44 | auto r = data_.unchecked<1>(); 45 | Py_ssize_t size = r.shape(0); 46 | 47 | std::vector data__(size); 48 | 49 | for (size_t i = 0; i < size; i++) { 50 | data__[i] = r(i); 51 | } 52 | 53 | data = data__; 54 | } 55 | 56 | // https://stackoverflow.com/a/49693704 57 | const py::array_t getPoints() const { 58 | /* No pointer is passed, so NumPy will allocate the buffer */ 59 | auto result = py::array_t(points.size() * 3); 60 | 61 | py::buffer_info buf = result.request(); 62 | 63 | float *ptr1 = (float *) buf.ptr; 64 | 65 | for (size_t i = 0; i < points.size(); i++) { 66 | const auto &p = points[i]; 67 | ptr1[i * 3 + 0] = p.x; 68 | ptr1[i * 3 + 1] = p.y; 69 | ptr1[i * 3 + 2] = p.z; 70 | } 71 | 72 | return result; 73 | } 74 | 75 | const py::array_t getTriangles() const { 76 | /* No pointer is passed, so NumPy will allocate the buffer */ 77 | auto result = py::array_t(triangles.size() * 3); 78 | 79 | py::buffer_info buf = result.request(); 80 | 81 | int32_t *ptr1 = (int32_t *) buf.ptr; 82 | 83 | for (size_t i = 0; i < triangles.size(); i++) { 84 | const auto &p = triangles[i]; 85 | ptr1[i * 3 + 0] = p.x; 86 | ptr1[i * 3 + 1] = p.y; 87 | ptr1[i * 3 + 2] = p.z; 88 | } 89 | 90 | return result; 91 | } 92 | 93 | const float &getError() const { return error; } 94 | 95 | void run() { 96 | const auto hm = std::make_shared(width, height, data); 97 | // auto level heightmap 98 | if (level) { 99 | hm->AutoLevel(); 100 | } 101 | 102 | // invert heightmap 103 | if (invert) { 104 | hm->Invert(); 105 | } 106 | 107 | // blur heightmap 108 | if (blurSigma > 0) { 109 | hm->GaussianBlur(blurSigma); 110 | } 111 | 112 | // apply gamma curve 113 | if (gamma > 0) { 114 | hm->GammaCurve(gamma); 115 | } 116 | 117 | // add border 118 | if (borderSize > 0) { 119 | hm->AddBorder(borderSize, borderHeight); 120 | } 121 | 122 | // get updated size 123 | int w = hm->Width(); 124 | int h = hm->Height(); 125 | 126 | Triangulator tri(hm); 127 | tri.Run(maxError, maxTriangles, maxPoints); 128 | points = tri.Points(zScale * zExaggeration); 129 | triangles = tri.Triangles(); 130 | error = tri.Error(); 131 | 132 | // add base 133 | if (baseHeight > 0) { 134 | const float z = -baseHeight * zScale * zExaggeration; 135 | AddBase(points, triangles, w, h, z); 136 | } 137 | } 138 | 139 | int width; 140 | int height; 141 | float maxError; 142 | float zScale; 143 | float zExaggeration; 144 | int maxTriangles; 145 | int maxPoints; 146 | bool level; 147 | bool invert; 148 | int blurSigma; 149 | float gamma; 150 | int borderSize; 151 | float borderHeight; 152 | float baseHeight; 153 | 154 | std::vector data; 155 | std::vector points; 156 | std::vector triangles; 157 | float error; 158 | }; 159 | 160 | PYBIND11_MODULE(_pydelatin, m) { 161 | m.doc() = R"pbdoc( 162 | Pybind11 example plugin 163 | ----------------------- 164 | 165 | .. currentmodule:: python_example 166 | 167 | .. autosummary:: 168 | :toctree: _generate 169 | 170 | add 171 | subtract 172 | )pbdoc"; 173 | 174 | py::class_(m, "PydelatinTriangulator") 175 | .def(py::init< 176 | const int, const int, 177 | const float, const float, const float, 178 | const int, const int, const bool, const bool, const int, 179 | const float, const int, const float, const float 180 | >()) 181 | .def("setWidth", &PydelatinTriangulator::setWidth) 182 | .def("getWidth", &PydelatinTriangulator::getWidth) 183 | .def("setHeight", &PydelatinTriangulator::setHeight) 184 | .def("getHeight", &PydelatinTriangulator::getHeight) 185 | .def("setMaxError", &PydelatinTriangulator::setMaxError) 186 | .def("getMaxError", &PydelatinTriangulator::getMaxError) 187 | .def("setData", &PydelatinTriangulator::setData) 188 | .def("getPoints", &PydelatinTriangulator::getPoints) 189 | .def("getTriangles", &PydelatinTriangulator::getTriangles) 190 | .def("getError", &PydelatinTriangulator::getError) 191 | .def("run", &PydelatinTriangulator::run) 192 | ; 193 | } 194 | -------------------------------------------------------------------------------- /src/pybind11_glm.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | python/pybind11_glm.hpp: Transparent conversion for glm types to NumPy arrays. 3 | This header is based on pybind11/eigen.h. 4 | 5 | Copyright (c) 2016 Patrik Huber 6 | 7 | All rights reserved. Use of this source code is governed by a 8 | BSD-style license that can be found in pybind11's LICENSE file. 9 | 10 | From 11 | https://github.com/FlorianRhiem/VFRendering/blob/f36812a3c0a385272f82b8879ac52d612eea71be/python/pybind11_glm.hpp 12 | */ 13 | #pragma once 14 | 15 | #include "glm/gtc/type_ptr.hpp" // includes all vector and matrix types too 16 | 17 | #include "pybind11/numpy.h" 18 | 19 | #include 20 | #include // would probably be better to use exceptions - but I think they were not showing in Python 21 | 22 | NAMESPACE_BEGIN(pybind11) 23 | NAMESPACE_BEGIN(detail) 24 | 25 | /** 26 | * @file python/pybind11_glm.hpp 27 | * @brief Transparent conversion to and from Python for glm vector and matrix types. 28 | * 29 | * All converters for matrices assume col-major storage of glm, the default. 30 | * Things will likely break if non-default storage order is used. 31 | * 32 | * Note: GLM follows the GLSL matrix definition, so e.g. a glm::tmat4x3 has 4 cols 33 | * and 3 rows, and is thus (in the standard mathematical notation) a 3x4 matrix. 34 | */ 35 | 36 | template 37 | struct type_caster> 38 | { 39 | using vector_type = glm::tvec2; 40 | using Scalar = T; 41 | static constexpr std::size_t num_elements = 2; 42 | 43 | bool load(handle src, bool) 44 | { 45 | array_t buf = array_t::ensure(src); 46 | if (!buf) 47 | return false; 48 | 49 | if (buf.ndim() == 1) // a 1-dimensional vector 50 | { 51 | if (buf.shape(0) != num_elements) { 52 | return false; // not a 2-elements vector 53 | } 54 | if (buf.strides(0) != sizeof(Scalar)) 55 | { 56 | std::cout << "An array with non-standard strides is given. Please pass a contiguous array." << std::endl; 57 | return false; 58 | } 59 | value = glm::make_vec2(buf.mutable_data()); // make_vec* copies the data (unnecessarily) 60 | } 61 | else { // buf.ndim() != 1 62 | return false; 63 | } 64 | return true; 65 | } 66 | 67 | static handle cast(const vector_type& src, return_value_policy /* policy */, handle /* parent */) 68 | { 69 | return array( 70 | num_elements, // shape 71 | glm::value_ptr(src) // data 72 | ).release(); 73 | } 74 | 75 | // Specifies the doc-string for the type in Python: 76 | PYBIND11_TYPE_CASTER(vector_type, _("numpy.ndarray[") + npy_format_descriptor::name + 77 | _("[") + _() + _("]]")); 78 | }; 79 | 80 | template 81 | struct type_caster> 82 | { 83 | using vector_type = glm::tvec3; 84 | using Scalar = T; 85 | static constexpr std::size_t num_elements = 3; 86 | 87 | bool load(handle src, bool) 88 | { 89 | array_t buf = array_t::ensure(src); 90 | if (!buf) 91 | return false; 92 | 93 | if (buf.ndim() == 1) // a 1-dimensional vector 94 | { 95 | if (buf.shape(0) != num_elements) { 96 | return false; // not a 3-elements vector 97 | } 98 | if (buf.strides(0) != sizeof(Scalar)) 99 | { 100 | std::cout << "An array with non-standard strides is given. Please pass a contiguous array." << std::endl; 101 | return false; 102 | } 103 | value = glm::make_vec3(buf.mutable_data()); // make_vec* copies the data (unnecessarily) 104 | } 105 | else { // buf.ndim() != 1 106 | return false; 107 | } 108 | return true; 109 | } 110 | 111 | static handle cast(const vector_type& src, return_value_policy /* policy */, handle /* parent */) 112 | { 113 | return array( 114 | num_elements, // shape 115 | glm::value_ptr(src) // data 116 | ).release(); 117 | } 118 | 119 | // Specifies the doc-string for the type in Python: 120 | PYBIND11_TYPE_CASTER(vector_type, _("numpy.ndarray[") + npy_format_descriptor::name + 121 | _("[") + _() + _("]]")); 122 | }; 123 | 124 | template 125 | struct type_caster> 126 | { 127 | using vector_type = glm::tvec4; 128 | using Scalar = T; 129 | static constexpr std::size_t num_elements = 4; 130 | 131 | bool load(handle src, bool) 132 | { 133 | array_t buf = array_t::ensure(src); 134 | if (!buf) 135 | return false; 136 | 137 | if (buf.ndim() == 1) // a 1-dimensional vector 138 | { 139 | if (buf.shape(0) != num_elements) { 140 | return false; // not a 4-elements vector 141 | } 142 | if (buf.strides(0) != sizeof(Scalar)) 143 | { 144 | std::cout << "An array with non-standard strides is given. Please pass a contiguous array." << std::endl; 145 | return false; 146 | } 147 | value = glm::make_vec4(buf.mutable_data()); // make_vec* copies the data (unnecessarily) 148 | } 149 | else { // buf.ndim() != 1 150 | return false; 151 | } 152 | return true; 153 | } 154 | 155 | static handle cast(const vector_type& src, return_value_policy /* policy */, handle /* parent */) 156 | { 157 | return array( 158 | num_elements, // shape 159 | glm::value_ptr(src) // data 160 | ).release(); 161 | } 162 | 163 | // Specifies the doc-string for the type in Python: 164 | PYBIND11_TYPE_CASTER(vector_type, _("numpy.ndarray[") + npy_format_descriptor::name + 165 | _("[") + _() + _("]]")); 166 | }; 167 | 168 | template 169 | struct type_caster> 170 | { 171 | using matrix_type = glm::tmat3x3; 172 | using Scalar = T; 173 | static constexpr std::size_t num_rows = 3; 174 | static constexpr std::size_t num_cols = 3; 175 | 176 | bool load(handle src, bool) 177 | { 178 | array_t buf = array_t::ensure(src); 179 | if (!buf) 180 | return false; 181 | 182 | if (buf.ndim() == 2) // a 2-dimensional matrix 183 | { 184 | if (buf.shape(0) != num_rows || buf.shape(1) != num_cols) { 185 | return false; // not a 3x3 matrix 186 | } 187 | if (buf.strides(0) / sizeof(Scalar) != num_cols || buf.strides(1) != sizeof(Scalar)) 188 | { 189 | std::cout << "An array with non-standard strides is given. Please pass a contiguous array." << std::endl; 190 | return false; 191 | } 192 | // What we get from Python is laid out in row-major memory order, while GLM's 193 | // storage is col-major, thus, we transpose. 194 | value = glm::transpose(glm::make_mat3x3(buf.mutable_data())); // make_mat*() copies the data (unnecessarily) 195 | } 196 | else { // buf.ndim() != 2 197 | return false; 198 | } 199 | return true; 200 | } 201 | 202 | static handle cast(const matrix_type& src, return_value_policy /* policy */, handle /* parent */) 203 | { 204 | return array( 205 | { num_rows, num_cols }, // shape 206 | { sizeof(Scalar), sizeof(Scalar) * num_rows }, // strides - flip the row/col layout! 207 | glm::value_ptr(src) // data 208 | ).release(); 209 | } 210 | 211 | // Specifies the doc-string for the type in Python: 212 | PYBIND11_TYPE_CASTER(matrix_type, _("numpy.ndarray[") + npy_format_descriptor::name + 213 | _("[") + _() + _(", ") + _() + _("]]")); 214 | }; 215 | 216 | template 217 | struct type_caster> 218 | { 219 | using matrix_type = glm::tmat4x3; 220 | using Scalar = T; 221 | static constexpr std::size_t num_rows = 3; 222 | static constexpr std::size_t num_cols = 4; 223 | 224 | bool load(handle src, bool) 225 | { 226 | array_t buf = array_t::ensure(src); 227 | if (!buf) 228 | return false; 229 | 230 | if (buf.ndim() == 2) // a 2-dimensional matrix 231 | { 232 | if (buf.shape(0) != num_rows || buf.shape(1) != num_cols) { 233 | return false; // not a 3x4 matrix 234 | } 235 | if (buf.strides(0) / sizeof(Scalar) != num_cols || buf.strides(1) != sizeof(Scalar)) 236 | { 237 | std::cout << "An array with non-standard strides is given. Please pass a contiguous array." << std::endl; 238 | return false; 239 | } 240 | // What we get from Python is laid out in row-major memory order, while GLM's 241 | // storage is col-major, thus, we create a mat3x4 and then transpose. 242 | value = glm::transpose(glm::make_mat3x4(buf.mutable_data())); // make_mat*() copies the data (unnecessarily) 243 | } 244 | else { // buf.ndim() != 2 245 | return false; 246 | } 247 | return true; 248 | } 249 | 250 | static handle cast(const matrix_type& src, return_value_policy /* policy */, handle /* parent */) 251 | { 252 | return array( 253 | { num_rows, num_cols }, // shape 254 | { sizeof(Scalar), sizeof(Scalar) * num_rows }, // strides - flip the row/col layout! 255 | glm::value_ptr(src) // data 256 | ).release(); 257 | } 258 | 259 | // Specifies the doc-string for the type in Python: 260 | PYBIND11_TYPE_CASTER(matrix_type, _("numpy.ndarray[") + npy_format_descriptor::name + 261 | _("[") + _() + _(", ") + _() + _("]]")); 262 | }; 263 | 264 | template 265 | struct type_caster> 266 | { 267 | using matrix_type = glm::tmat4x4; 268 | using Scalar = T; 269 | static constexpr std::size_t num_rows = 4; 270 | static constexpr std::size_t num_cols = 4; 271 | 272 | bool load(handle src, bool) 273 | { 274 | array_t buf = array_t::ensure(src); 275 | if (!buf) 276 | return false; 277 | 278 | if (buf.ndim() == 2) // a 2-dimensional matrix 279 | { 280 | if (buf.shape(0) != num_rows || buf.shape(1) != num_cols) { 281 | return false; // not a 4x4 matrix 282 | } 283 | if (buf.strides(0) / sizeof(Scalar) != num_cols || buf.strides(1) != sizeof(Scalar)) 284 | { 285 | std::cout << "An array with non-standard strides is given. Please pass a contiguous array." << std::endl; 286 | return false; 287 | } 288 | // What we get from Python is laid out in row-major memory order, while GLM's 289 | // storage is col-major, thus, we transpose. 290 | value = glm::transpose(glm::make_mat4x4(buf.mutable_data())); // make_mat*() copies the data (unnecessarily) 291 | } 292 | else { // buf.ndim() != 2 293 | return false; 294 | } 295 | return true; 296 | } 297 | 298 | static handle cast(const matrix_type& src, return_value_policy /* policy */, handle /* parent */) 299 | { 300 | return array( 301 | { num_rows, num_cols }, // shape 302 | { sizeof(Scalar), sizeof(Scalar) * num_rows }, // strides - flip the row/col layout! 303 | glm::value_ptr(src) // data 304 | ).release(); 305 | } 306 | 307 | // Specifies the doc-string for the type in Python: 308 | PYBIND11_TYPE_CASTER(matrix_type, _("numpy.ndarray[") + npy_format_descriptor::name + 309 | _("[") + _() + _(", ") + _() + _("]]")); 310 | }; 311 | 312 | NAMESPACE_END(detail) 313 | NAMESPACE_END(pybind11) 314 | -------------------------------------------------------------------------------- /src/stb_image_write.h: -------------------------------------------------------------------------------- 1 | /* stb_image_write - v1.13 - public domain - http://nothings.org/stb 2 | writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 3 | no warranty implied; use at your own risk 4 | 5 | Before #including, 6 | 7 | #define STB_IMAGE_WRITE_IMPLEMENTATION 8 | 9 | in the file that you want to have the implementation. 10 | 11 | Will probably not work correctly with strict-aliasing optimizations. 12 | 13 | ABOUT: 14 | 15 | This header file is a library for writing images to C stdio or a callback. 16 | 17 | The PNG output is not optimal; it is 20-50% larger than the file 18 | written by a decent optimizing implementation; though providing a custom 19 | zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. 20 | This library is designed for source code compactness and simplicity, 21 | not optimal image file size or run-time performance. 22 | 23 | BUILDING: 24 | 25 | You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. 26 | You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace 27 | malloc,realloc,free. 28 | You can #define STBIW_MEMMOVE() to replace memmove() 29 | You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function 30 | for PNG compression (instead of the builtin one), it must have the following signature: 31 | unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); 32 | The returned data will be freed with STBIW_FREE() (free() by default), 33 | so it must be heap allocated with STBIW_MALLOC() (malloc() by default), 34 | 35 | UNICODE: 36 | 37 | If compiling for Windows and you wish to use Unicode filenames, compile 38 | with 39 | #define STBIW_WINDOWS_UTF8 40 | and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert 41 | Windows wchar_t filenames to utf8. 42 | 43 | USAGE: 44 | 45 | There are five functions, one for each image file format: 46 | 47 | int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); 48 | int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); 49 | int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); 50 | int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); 51 | int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); 52 | 53 | void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically 54 | 55 | There are also five equivalent functions that use an arbitrary write function. You are 56 | expected to open/close your file-equivalent before and after calling these: 57 | 58 | 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); 59 | int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 60 | int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 61 | int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); 62 | int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); 63 | 64 | where the callback is: 65 | void stbi_write_func(void *context, void *data, int size); 66 | 67 | You can configure it with these global variables: 68 | int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE 69 | int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression 70 | int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode 71 | 72 | 73 | You can define STBI_WRITE_NO_STDIO to disable the file variant of these 74 | functions, so the library will not use stdio.h at all. However, this will 75 | also disable HDR writing, because it requires stdio for formatted output. 76 | 77 | Each function returns 0 on failure and non-0 on success. 78 | 79 | The functions create an image file defined by the parameters. The image 80 | is a rectangle of pixels stored from left-to-right, top-to-bottom. 81 | Each pixel contains 'comp' channels of data stored interleaved with 8-bits 82 | per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is 83 | monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. 84 | The *data pointer points to the first byte of the top-left-most pixel. 85 | For PNG, "stride_in_bytes" is the distance in bytes from the first byte of 86 | a row of pixels to the first byte of the next row of pixels. 87 | 88 | PNG creates output files with the same number of components as the input. 89 | The BMP format expands Y to RGB in the file format and does not 90 | output alpha. 91 | 92 | PNG supports writing rectangles of data even when the bytes storing rows of 93 | data are not consecutive in memory (e.g. sub-rectangles of a larger image), 94 | by supplying the stride between the beginning of adjacent rows. The other 95 | formats do not. (Thus you cannot write a native-format BMP through the BMP 96 | writer, both because it is in BGR order and because it may have padding 97 | at the end of the line.) 98 | 99 | PNG allows you to set the deflate compression level by setting the global 100 | variable 'stbi_write_png_compression_level' (it defaults to 8). 101 | 102 | HDR expects linear float data. Since the format is always 32-bit rgb(e) 103 | data, alpha (if provided) is discarded, and for monochrome data it is 104 | replicated across all three channels. 105 | 106 | TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed 107 | data, set the global variable 'stbi_write_tga_with_rle' to 0. 108 | 109 | JPEG does ignore alpha channels in input data; quality is between 1 and 100. 110 | Higher quality looks better but results in a bigger image. 111 | JPEG baseline (no JPEG progressive). 112 | 113 | CREDITS: 114 | 115 | 116 | Sean Barrett - PNG/BMP/TGA 117 | Baldur Karlsson - HDR 118 | Jean-Sebastien Guay - TGA monochrome 119 | Tim Kelsey - misc enhancements 120 | Alan Hickman - TGA RLE 121 | Emmanuel Julien - initial file IO callback implementation 122 | Jon Olick - original jo_jpeg.cpp code 123 | Daniel Gibson - integrate JPEG, allow external zlib 124 | Aarni Koskela - allow choosing PNG filter 125 | 126 | bugfixes: 127 | github:Chribba 128 | Guillaume Chereau 129 | github:jry2 130 | github:romigrou 131 | Sergio Gonzalez 132 | Jonas Karlsson 133 | Filip Wasil 134 | Thatcher Ulrich 135 | github:poppolopoppo 136 | Patrick Boettcher 137 | github:xeekworx 138 | Cap Petschulat 139 | Simon Rodriguez 140 | Ivan Tikhonov 141 | github:ignotion 142 | Adam Schackart 143 | 144 | LICENSE 145 | 146 | See end of file for license information. 147 | 148 | */ 149 | 150 | #ifndef INCLUDE_STB_IMAGE_WRITE_H 151 | #define INCLUDE_STB_IMAGE_WRITE_H 152 | 153 | #include 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 | -------------------------------------------------------------------------------- /src/triangulator.cpp: -------------------------------------------------------------------------------- 1 | #include "triangulator.h" 2 | 3 | Triangulator::Triangulator(const std::shared_ptr &heightmap) : 4 | m_Heightmap(heightmap) {} 5 | 6 | void Triangulator::Run( 7 | const float maxError, 8 | const int maxTriangles, 9 | const int maxPoints) 10 | { 11 | // add points at all four corners 12 | const int x0 = 0; 13 | const int y0 = 0; 14 | const int x1 = m_Heightmap->Width() - 1; 15 | const int y1 = m_Heightmap->Height() - 1; 16 | const int p0 = AddPoint(glm::ivec2(x0, y0)); 17 | const int p1 = AddPoint(glm::ivec2(x1, y0)); 18 | const int p2 = AddPoint(glm::ivec2(x0, y1)); 19 | const int p3 = AddPoint(glm::ivec2(x1, y1)); 20 | 21 | // add initial two triangles 22 | const int t0 = AddTriangle(p3, p0, p2, -1, -1, -1, -1); 23 | AddTriangle(p0, p3, p1, t0, -1, -1, -1); 24 | Flush(); 25 | 26 | // helper function to check if triangulation is complete 27 | const auto done = [this, maxError, maxTriangles, maxPoints]() { 28 | const float e = Error(); 29 | if (e <= maxError) { 30 | return true; 31 | } 32 | if (maxTriangles > 0 && NumTriangles() >= maxTriangles) { 33 | return true; 34 | } 35 | if (maxPoints > 0 && NumPoints() >= maxPoints) { 36 | return true; 37 | } 38 | return e == 0; 39 | }; 40 | 41 | while (!done()) { 42 | Step(); 43 | } 44 | } 45 | 46 | float Triangulator::Error() const { 47 | return m_Errors[m_Queue[0]]; 48 | } 49 | 50 | std::vector Triangulator::Points(const float zScale) const { 51 | std::vector points; 52 | points.reserve(m_Points.size()); 53 | const int h1 = m_Heightmap->Height() - 1; 54 | for (const glm::ivec2 &p : m_Points) { 55 | points.emplace_back(p.x, h1 - p.y, m_Heightmap->At(p.x, p.y) * zScale); 56 | } 57 | return points; 58 | } 59 | 60 | std::vector Triangulator::Triangles() const { 61 | std::vector triangles; 62 | triangles.reserve(m_Queue.size()); 63 | for (const int i : m_Queue) { 64 | triangles.emplace_back( 65 | m_Triangles[i * 3 + 0], 66 | m_Triangles[i * 3 + 1], 67 | m_Triangles[i * 3 + 2]); 68 | } 69 | return triangles; 70 | } 71 | 72 | void Triangulator::Flush() { 73 | for (const int t : m_Pending) { 74 | // rasterize triangle to find maximum pixel error 75 | const auto pair = m_Heightmap->FindCandidate( 76 | m_Points[m_Triangles[t*3+0]], 77 | m_Points[m_Triangles[t*3+1]], 78 | m_Points[m_Triangles[t*3+2]]); 79 | // update metadata 80 | m_Candidates[t] = pair.first; 81 | m_Errors[t] = pair.second; 82 | // add triangle to priority queue 83 | QueuePush(t); 84 | } 85 | 86 | m_Pending.clear(); 87 | } 88 | 89 | void Triangulator::Step() { 90 | // pop triangle with highest error from priority queue 91 | const int t = QueuePop(); 92 | 93 | const int e0 = t * 3 + 0; 94 | const int e1 = t * 3 + 1; 95 | const int e2 = t * 3 + 2; 96 | 97 | const int p0 = m_Triangles[e0]; 98 | const int p1 = m_Triangles[e1]; 99 | const int p2 = m_Triangles[e2]; 100 | 101 | const glm::ivec2 a = m_Points[p0]; 102 | const glm::ivec2 b = m_Points[p1]; 103 | const glm::ivec2 c = m_Points[p2]; 104 | const glm::ivec2 p = m_Candidates[t]; 105 | 106 | const int pn = AddPoint(p); 107 | 108 | const auto collinear = []( 109 | const glm::ivec2 p0, const glm::ivec2 p1, const glm::ivec2 p2) 110 | { 111 | return (p1.y-p0.y)*(p2.x-p1.x) == (p2.y-p1.y)*(p1.x-p0.x); 112 | }; 113 | 114 | const auto handleCollinear = [this](const int pn, const int a) { 115 | const int a0 = a - a % 3; 116 | const int al = a0 + (a + 1) % 3; 117 | const int ar = a0 + (a + 2) % 3; 118 | const int p0 = m_Triangles[ar]; 119 | const int pr = m_Triangles[a]; 120 | const int pl = m_Triangles[al]; 121 | const int hal = m_Halfedges[al]; 122 | const int har = m_Halfedges[ar]; 123 | 124 | const int b = m_Halfedges[a]; 125 | 126 | if (b < 0) { 127 | const int t0 = AddTriangle(pn, p0, pr, -1, har, -1, a0); 128 | const int t1 = AddTriangle(p0, pn, pl, t0, -1, hal, -1); 129 | Legalize(t0 + 1); 130 | Legalize(t1 + 2); 131 | return; 132 | } 133 | 134 | const int b0 = b - b % 3; 135 | const int bl = b0 + (b + 2) % 3; 136 | const int br = b0 + (b + 1) % 3; 137 | const int p1 = m_Triangles[bl]; 138 | const int hbl = m_Halfedges[bl]; 139 | const int hbr = m_Halfedges[br]; 140 | 141 | QueueRemove(b / 3); 142 | 143 | const int t0 = AddTriangle(p0, pr, pn, har, -1, -1, a0); 144 | const int t1 = AddTriangle(pr, p1, pn, hbr, -1, t0 + 1, b0); 145 | const int t2 = AddTriangle(p1, pl, pn, hbl, -1, t1 + 1, -1); 146 | const int t3 = AddTriangle(pl, p0, pn, hal, t0 + 2, t2 + 1, -1); 147 | 148 | Legalize(t0); 149 | Legalize(t1); 150 | Legalize(t2); 151 | Legalize(t3); 152 | }; 153 | 154 | if (collinear(a, b, p)) { 155 | handleCollinear(pn, e0); 156 | } else if (collinear(b, c, p)) { 157 | handleCollinear(pn, e1); 158 | } else if (collinear(c, a, p)) { 159 | handleCollinear(pn, e2); 160 | } else { 161 | const int h0 = m_Halfedges[e0]; 162 | const int h1 = m_Halfedges[e1]; 163 | const int h2 = m_Halfedges[e2]; 164 | 165 | const int t0 = AddTriangle(p0, p1, pn, h0, -1, -1, e0); 166 | const int t1 = AddTriangle(p1, p2, pn, h1, -1, t0 + 1, -1); 167 | const int t2 = AddTriangle(p2, p0, pn, h2, t0 + 2, t1 + 1, -1); 168 | 169 | Legalize(t0); 170 | Legalize(t1); 171 | Legalize(t2); 172 | } 173 | 174 | Flush(); 175 | } 176 | 177 | int Triangulator::AddPoint(const glm::ivec2 point) { 178 | const int i = m_Points.size(); 179 | m_Points.push_back(point); 180 | return i; 181 | } 182 | 183 | int Triangulator::AddTriangle( 184 | const int a, const int b, const int c, 185 | const int ab, const int bc, const int ca, 186 | int e) 187 | { 188 | if (e < 0) { 189 | // new halfedge index 190 | e = m_Triangles.size(); 191 | // add triangle vertices 192 | m_Triangles.push_back(a); 193 | m_Triangles.push_back(b); 194 | m_Triangles.push_back(c); 195 | // add triangle halfedges 196 | m_Halfedges.push_back(ab); 197 | m_Halfedges.push_back(bc); 198 | m_Halfedges.push_back(ca); 199 | // add triangle metadata 200 | m_Candidates.emplace_back(0); 201 | m_Errors.push_back(0); 202 | m_QueueIndexes.push_back(-1); 203 | } else { 204 | // set triangle vertices 205 | m_Triangles[e + 0] = a; 206 | m_Triangles[e + 1] = b; 207 | m_Triangles[e + 2] = c; 208 | // set triangle halfedges 209 | m_Halfedges[e + 0] = ab; 210 | m_Halfedges[e + 1] = bc; 211 | m_Halfedges[e + 2] = ca; 212 | } 213 | 214 | // link neighboring halfedges 215 | if (ab >= 0) { 216 | m_Halfedges[ab] = e + 0; 217 | } 218 | if (bc >= 0) { 219 | m_Halfedges[bc] = e + 1; 220 | } 221 | if (ca >= 0) { 222 | m_Halfedges[ca] = e + 2; 223 | } 224 | 225 | // add triangle to pending queue for later rasterization 226 | const int t = e / 3; 227 | m_Pending.push_back(t); 228 | 229 | // return first halfedge index 230 | return e; 231 | } 232 | 233 | void Triangulator::Legalize(const int a) { 234 | // if the pair of triangles doesn't satisfy the Delaunay condition 235 | // (p1 is inside the circumcircle of [p0, pl, pr]), flip them, 236 | // then do the same check/flip recursively for the new pair of triangles 237 | // 238 | // pl pl 239 | // /||\ / \ 240 | // al/ || \bl al/ \a 241 | // / || \ / \ 242 | // / a||b \ flip /___ar___\ 243 | // p0\ || /p1 => p0\---bl---/p1 244 | // \ || / \ / 245 | // ar\ || /br b\ /br 246 | // \||/ \ / 247 | // pr pr 248 | 249 | const auto inCircle = []( 250 | const glm::ivec2 a, const glm::ivec2 b, const glm::ivec2 c, 251 | const glm::ivec2 p) 252 | { 253 | const int64_t dx = a.x - p.x; 254 | const int64_t dy = a.y - p.y; 255 | const int64_t ex = b.x - p.x; 256 | const int64_t ey = b.y - p.y; 257 | const int64_t fx = c.x - p.x; 258 | const int64_t fy = c.y - p.y; 259 | const int64_t ap = dx * dx + dy * dy; 260 | const int64_t bp = ex * ex + ey * ey; 261 | const int64_t cp = fx * fx + fy * fy; 262 | return dx*(ey*cp-bp*fy)-dy*(ex*cp-bp*fx)+ap*(ex*fy-ey*fx) < 0; 263 | }; 264 | 265 | const int b = m_Halfedges[a]; 266 | 267 | if (b < 0) { 268 | return; 269 | } 270 | 271 | const int a0 = a - a % 3; 272 | const int b0 = b - b % 3; 273 | const int al = a0 + (a + 1) % 3; 274 | const int ar = a0 + (a + 2) % 3; 275 | const int bl = b0 + (b + 2) % 3; 276 | const int br = b0 + (b + 1) % 3; 277 | const int p0 = m_Triangles[ar]; 278 | const int pr = m_Triangles[a]; 279 | const int pl = m_Triangles[al]; 280 | const int p1 = m_Triangles[bl]; 281 | 282 | if (!inCircle(m_Points[p0], m_Points[pr], m_Points[pl], m_Points[p1])) { 283 | return; 284 | } 285 | 286 | const int hal = m_Halfedges[al]; 287 | const int har = m_Halfedges[ar]; 288 | const int hbl = m_Halfedges[bl]; 289 | const int hbr = m_Halfedges[br]; 290 | 291 | QueueRemove(a / 3); 292 | QueueRemove(b / 3); 293 | 294 | const int t0 = AddTriangle(p0, p1, pl, -1, hbl, hal, a0); 295 | const int t1 = AddTriangle(p1, p0, pr, t0, har, hbr, b0); 296 | 297 | Legalize(t0 + 1); 298 | Legalize(t1 + 2); 299 | } 300 | 301 | // priority queue functions 302 | 303 | void Triangulator::QueuePush(const int t) { 304 | const int i = m_Queue.size(); 305 | m_QueueIndexes[t] = i; 306 | m_Queue.push_back(t); 307 | QueueUp(i); 308 | } 309 | 310 | int Triangulator::QueuePop() { 311 | const int n = m_Queue.size() - 1; 312 | QueueSwap(0, n); 313 | QueueDown(0, n); 314 | return QueuePopBack(); 315 | } 316 | 317 | int Triangulator::QueuePopBack() { 318 | const int t = m_Queue.back(); 319 | m_Queue.pop_back(); 320 | m_QueueIndexes[t] = -1; 321 | return t; 322 | } 323 | 324 | void Triangulator::QueueRemove(const int t) { 325 | const int i = m_QueueIndexes[t]; 326 | if (i < 0) { 327 | const auto it = std::find(m_Pending.begin(), m_Pending.end(), t); 328 | if (it != m_Pending.end()) { 329 | std::swap(*it, m_Pending.back()); 330 | m_Pending.pop_back(); 331 | } else { 332 | // this shouldn't happen! 333 | } 334 | return; 335 | } 336 | const int n = m_Queue.size() - 1; 337 | if (n != i) { 338 | QueueSwap(i, n); 339 | if (!QueueDown(i, n)) { 340 | QueueUp(i); 341 | } 342 | } 343 | QueuePopBack(); 344 | } 345 | 346 | bool Triangulator::QueueLess(const int i, const int j) const { 347 | return -m_Errors[m_Queue[i]] < -m_Errors[m_Queue[j]]; 348 | } 349 | 350 | void Triangulator::QueueSwap(const int i, const int j) { 351 | const int pi = m_Queue[i]; 352 | const int pj = m_Queue[j]; 353 | m_Queue[i] = pj; 354 | m_Queue[j] = pi; 355 | m_QueueIndexes[pi] = j; 356 | m_QueueIndexes[pj] = i; 357 | } 358 | 359 | void Triangulator::QueueUp(const int j0) { 360 | int j = j0; 361 | while (1) { 362 | int i = (j - 1) / 2; 363 | if (i == j || !QueueLess(j, i)) { 364 | break; 365 | } 366 | QueueSwap(i, j); 367 | j = i; 368 | } 369 | } 370 | 371 | bool Triangulator::QueueDown(const int i0, const int n) { 372 | int i = i0; 373 | while (1) { 374 | const int j1 = 2 * i + 1; 375 | if (j1 >= n || j1 < 0) { 376 | break; 377 | } 378 | const int j2 = j1 + 1; 379 | int j = j1; 380 | if (j2 < n && QueueLess(j2, j1)) { 381 | j = j2; 382 | } 383 | if (!QueueLess(j, i)) { 384 | break; 385 | } 386 | QueueSwap(i, j); 387 | i = j; 388 | } 389 | return i > i0; 390 | } 391 | -------------------------------------------------------------------------------- /src/triangulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "heightmap.h" 8 | 9 | class Triangulator { 10 | public: 11 | Triangulator(const std::shared_ptr &heightmap); 12 | 13 | void Run( 14 | const float maxError, 15 | const int maxTriangles, 16 | const int maxPoints); 17 | 18 | int NumPoints() const { 19 | return m_Points.size(); 20 | } 21 | 22 | int NumTriangles() const { 23 | return m_Queue.size(); 24 | } 25 | 26 | float Error() const; 27 | 28 | std::vector Points(const float zScale) const; 29 | 30 | std::vector Triangles() const; 31 | 32 | private: 33 | void Flush(); 34 | 35 | void Step(); 36 | 37 | int AddPoint(const glm::ivec2 point); 38 | 39 | int AddTriangle( 40 | const int a, const int b, const int c, 41 | const int ab, const int bc, const int ca, 42 | int e); 43 | 44 | void Legalize(const int a); 45 | 46 | void QueuePush(const int t); 47 | int QueuePop(); 48 | int QueuePopBack(); 49 | void QueueRemove(const int t); 50 | bool QueueLess(const int i, const int j) const; 51 | void QueueSwap(const int i, const int j); 52 | void QueueUp(const int j0); 53 | bool QueueDown(const int i0, const int n); 54 | 55 | std::shared_ptr m_Heightmap; 56 | 57 | std::vector m_Points; 58 | 59 | std::vector m_Triangles; 60 | std::vector m_Halfedges; 61 | 62 | std::vector m_Candidates; 63 | std::vector m_Errors; 64 | std::vector m_QueueIndexes; 65 | 66 | std::vector m_Queue; 67 | 68 | std::vector m_Pending; 69 | }; 70 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | 2 | Fuji.png comes from the Mapbox Terrain-RGB dataset, from `10/906/404`. 3 | 4 | Download with: 5 | 6 | ``` 7 | https://api.mapbox.com/v4/mapbox.terrain-rgb/10/906/404@2x.png?access_token=... 8 | ``` 9 | -------------------------------------------------------------------------------- /test/bench_js/.gitignore: -------------------------------------------------------------------------------- 1 | index.js 2 | -------------------------------------------------------------------------------- /test/bench_js/bench.js: -------------------------------------------------------------------------------- 1 | 2 | import fs from 'fs'; 3 | import {PNG} from 'pngjs'; 4 | import Delatin from './index.js'; 5 | 6 | 7 | const {width, height, data} = PNG.sync.read(fs.readFileSync('../data/fuji.png')); 8 | 9 | const heights = new Float64Array(width * height); 10 | 11 | for (let i = 0; i < heights.length; i++) { 12 | const r = data[4 * i]; 13 | const g = data[4 * i + 1]; 14 | const b = data[4 * i + 2]; 15 | heights[i] = Math.round(((r * 256 * 256 + g * 256.0 + b) / 10.0 - 10000.0) * 10) / 10; 16 | } 17 | 18 | // Re-instantiating the class every time, so that I'm testing the total time to 19 | // get **one** mesh resolution level. On my computer, instantiation only takes 20 | // 18ms. 21 | console.time('mesh (max_error=30m)'); 22 | let tin = new Delatin(heights, width, height); 23 | tin.run(30); 24 | console.timeEnd('mesh (max_error=30m)'); 25 | 26 | console.log(`vertices: ${tin.coords.length >> 1}`); 27 | console.log(`triangles: ${tin.triangles.length / 3}\n`); 28 | 29 | for (var i = 0; i < 21; i++) { 30 | console.time(`mesh (max_error=${i}m)`); 31 | tin = new Delatin(heights, width, height); 32 | tin.run(i); 33 | console.timeEnd(`mesh (max_error=${i}m)`); 34 | } 35 | -------------------------------------------------------------------------------- /test/bench_js/index.js: -------------------------------------------------------------------------------- 1 | 2 | export default class Delatin { 3 | 4 | constructor(data, width, height = width) { 5 | this.data = data; // height data 6 | this.width = width; 7 | this.height = height; 8 | 9 | this.coords = []; // vertex coordinates (x, y) 10 | this.triangles = []; // mesh triangle indices 11 | 12 | // additional triangle data 13 | this._halfedges = []; 14 | this._candidates = []; 15 | this._queueIndices = []; 16 | 17 | this._queue = []; // queue of added triangles 18 | this._errors = []; 19 | this._rms = []; 20 | this._pending = []; // triangles pending addition to queue 21 | this._pendingLen = 0; 22 | 23 | this._rmsSum = 0; 24 | 25 | const x1 = width - 1; 26 | const y1 = height - 1; 27 | const p0 = this._addPoint(0, 0); 28 | const p1 = this._addPoint(x1, 0); 29 | const p2 = this._addPoint(0, y1); 30 | const p3 = this._addPoint(x1, y1); 31 | 32 | // add initial two triangles 33 | const t0 = this._addTriangle(p3, p0, p2, -1, -1, -1); 34 | this._addTriangle(p0, p3, p1, t0, -1, -1); 35 | this._flush(); 36 | } 37 | 38 | // refine the mesh until its maximum error gets below the given one 39 | run(maxError = 1) { 40 | while (this.getMaxError() > maxError) { 41 | this.refine(); 42 | } 43 | } 44 | 45 | // refine the mesh with a single point 46 | refine() { 47 | this._step(); 48 | this._flush(); 49 | } 50 | 51 | // max error of the current mesh 52 | getMaxError() { 53 | return this._errors[0]; 54 | } 55 | 56 | // root-mean-square deviation of the current mesh 57 | getRMSD() { 58 | return this._rmsSum > 0 ? Math.sqrt(this._rmsSum / (this.width * this.height)) : 0; 59 | } 60 | 61 | // height value at a given position 62 | heightAt(x, y) { 63 | return this.data[this.width * y + x]; 64 | } 65 | 66 | // rasterize and queue all triangles that got added or updated in _step 67 | _flush() { 68 | const coords = this.coords; 69 | for (let i = 0; i < this._pendingLen; i++) { 70 | const t = this._pending[i]; 71 | // rasterize triangle to find maximum pixel error 72 | const a = 2 * this.triangles[t * 3 + 0]; 73 | const b = 2 * this.triangles[t * 3 + 1]; 74 | const c = 2 * this.triangles[t * 3 + 2]; 75 | this._findCandidate(coords[a], coords[a + 1], coords[b], coords[b + 1], coords[c], coords[c + 1], t); 76 | } 77 | this._pendingLen = 0; 78 | } 79 | 80 | // rasterize a triangle, find its max error, and queue it for processing 81 | _findCandidate(p0x, p0y, p1x, p1y, p2x, p2y, t) { 82 | // triangle bounding box 83 | const minX = Math.min(p0x, p1x, p2x); 84 | const minY = Math.min(p0y, p1y, p2y); 85 | const maxX = Math.max(p0x, p1x, p2x); 86 | const maxY = Math.max(p0y, p1y, p2y); 87 | 88 | // forward differencing variables 89 | let w00 = orient(p1x, p1y, p2x, p2y, minX, minY); 90 | let w01 = orient(p2x, p2y, p0x, p0y, minX, minY); 91 | let w02 = orient(p0x, p0y, p1x, p1y, minX, minY); 92 | const a01 = p1y - p0y; 93 | const b01 = p0x - p1x; 94 | const a12 = p2y - p1y; 95 | const b12 = p1x - p2x; 96 | const a20 = p0y - p2y; 97 | const b20 = p2x - p0x; 98 | 99 | // pre-multiplied z values at vertices 100 | const a = orient(p0x, p0y, p1x, p1y, p2x, p2y); 101 | const z0 = this.heightAt(p0x, p0y) / a; 102 | const z1 = this.heightAt(p1x, p1y) / a; 103 | const z2 = this.heightAt(p2x, p2y) / a; 104 | 105 | // iterate over pixels in bounding box 106 | let maxError = 0; 107 | let mx = 0; 108 | let my = 0; 109 | let rms = 0; 110 | for (let y = minY; y <= maxY; y++) { 111 | // compute starting offset 112 | let dx = 0; 113 | if (w00 < 0 && a12 !== 0) { 114 | dx = Math.max(dx, Math.floor(-w00 / a12)); 115 | } 116 | if (w01 < 0 && a20 !== 0) { 117 | dx = Math.max(dx, Math.floor(-w01 / a20)); 118 | } 119 | if (w02 < 0 && a01 !== 0) { 120 | dx = Math.max(dx, Math.floor(-w02 / a01)); 121 | } 122 | 123 | let w0 = w00 + a12 * dx; 124 | let w1 = w01 + a20 * dx; 125 | let w2 = w02 + a01 * dx; 126 | 127 | let wasInside = false; 128 | 129 | for (let x = minX + dx; x <= maxX; x++) { 130 | // check if inside triangle 131 | if (w0 >= 0 && w1 >= 0 && w2 >= 0) { 132 | wasInside = true; 133 | 134 | // compute z using barycentric coordinates 135 | const z = z0 * w0 + z1 * w1 + z2 * w2; 136 | const dz = Math.abs(z - this.heightAt(x, y)); 137 | rms += dz * dz; 138 | if (dz > maxError) { 139 | maxError = dz; 140 | mx = x; 141 | my = y; 142 | } 143 | } else if (wasInside) { 144 | break; 145 | } 146 | 147 | w0 += a12; 148 | w1 += a20; 149 | w2 += a01; 150 | } 151 | 152 | w00 += b12; 153 | w01 += b20; 154 | w02 += b01; 155 | } 156 | 157 | if (mx === p0x && my === p0y || mx === p1x && my === p1y || mx === p2x && my === p2y) { 158 | maxError = 0; 159 | } 160 | 161 | // update triangle metadata 162 | this._candidates[2 * t] = mx; 163 | this._candidates[2 * t + 1] = my; 164 | this._rms[t] = rms; 165 | 166 | // add triangle to priority queue 167 | this._queuePush(t, maxError, rms); 168 | } 169 | 170 | // process the next triangle in the queue, splitting it with a new point 171 | _step() { 172 | // pop triangle with highest error from priority queue 173 | const t = this._queuePop(); 174 | 175 | const e0 = t * 3 + 0; 176 | const e1 = t * 3 + 1; 177 | const e2 = t * 3 + 2; 178 | 179 | const p0 = this.triangles[e0]; 180 | const p1 = this.triangles[e1]; 181 | const p2 = this.triangles[e2]; 182 | 183 | const ax = this.coords[2 * p0]; 184 | const ay = this.coords[2 * p0 + 1]; 185 | const bx = this.coords[2 * p1]; 186 | const by = this.coords[2 * p1 + 1]; 187 | const cx = this.coords[2 * p2]; 188 | const cy = this.coords[2 * p2 + 1]; 189 | const px = this._candidates[2 * t]; 190 | const py = this._candidates[2 * t + 1]; 191 | 192 | const pn = this._addPoint(px, py); 193 | 194 | if (orient(ax, ay, bx, by, px, py) === 0) { 195 | this._handleCollinear(pn, e0); 196 | 197 | } else if (orient(bx, by, cx, cy, px, py) === 0) { 198 | this._handleCollinear(pn, e1); 199 | 200 | } else if (orient(cx, cy, ax, ay, px, py) === 0) { 201 | this._handleCollinear(pn, e2); 202 | 203 | } else { 204 | const h0 = this._halfedges[e0]; 205 | const h1 = this._halfedges[e1]; 206 | const h2 = this._halfedges[e2]; 207 | 208 | const t0 = this._addTriangle(p0, p1, pn, h0, -1, -1, e0); 209 | const t1 = this._addTriangle(p1, p2, pn, h1, -1, t0 + 1); 210 | const t2 = this._addTriangle(p2, p0, pn, h2, t0 + 2, t1 + 1); 211 | 212 | this._legalize(t0); 213 | this._legalize(t1); 214 | this._legalize(t2); 215 | } 216 | } 217 | 218 | // add coordinates for a new vertex 219 | _addPoint(x, y) { 220 | const i = this.coords.length >> 1; 221 | this.coords.push(x, y); 222 | return i; 223 | } 224 | 225 | // add or update a triangle in the mesh 226 | _addTriangle(a, b, c, ab, bc, ca, e = this.triangles.length) { 227 | const t = e / 3; // new triangle index 228 | 229 | // add triangle vertices 230 | this.triangles[e + 0] = a; 231 | this.triangles[e + 1] = b; 232 | this.triangles[e + 2] = c; 233 | 234 | // add triangle halfedges 235 | this._halfedges[e + 0] = ab; 236 | this._halfedges[e + 1] = bc; 237 | this._halfedges[e + 2] = ca; 238 | 239 | // link neighboring halfedges 240 | if (ab >= 0) { 241 | this._halfedges[ab] = e + 0; 242 | } 243 | if (bc >= 0) { 244 | this._halfedges[bc] = e + 1; 245 | } 246 | if (ca >= 0) { 247 | this._halfedges[ca] = e + 2; 248 | } 249 | 250 | // init triangle metadata 251 | this._candidates[2 * t + 0] = 0; 252 | this._candidates[2 * t + 1] = 0; 253 | this._queueIndices[t] = -1; 254 | this._rms[t] = 0; 255 | 256 | // add triangle to pending queue for later rasterization 257 | this._pending[this._pendingLen++] = t; 258 | 259 | // return first halfedge index 260 | return e; 261 | } 262 | 263 | _legalize(a) { 264 | // if the pair of triangles doesn't satisfy the Delaunay condition 265 | // (p1 is inside the circumcircle of [p0, pl, pr]), flip them, 266 | // then do the same check/flip recursively for the new pair of triangles 267 | // 268 | // pl pl 269 | // /||\ / \ 270 | // al/ || \bl al/ \a 271 | // / || \ / \ 272 | // / a||b \ flip /___ar___\ 273 | // p0\ || /p1 => p0\---bl---/p1 274 | // \ || / \ / 275 | // ar\ || /br b\ /br 276 | // \||/ \ / 277 | // pr pr 278 | 279 | const b = this._halfedges[a]; 280 | 281 | if (b < 0) { 282 | return; 283 | } 284 | 285 | const a0 = a - a % 3; 286 | const b0 = b - b % 3; 287 | const al = a0 + (a + 1) % 3; 288 | const ar = a0 + (a + 2) % 3; 289 | const bl = b0 + (b + 2) % 3; 290 | const br = b0 + (b + 1) % 3; 291 | const p0 = this.triangles[ar]; 292 | const pr = this.triangles[a]; 293 | const pl = this.triangles[al]; 294 | const p1 = this.triangles[bl]; 295 | const coords = this.coords; 296 | 297 | if (!inCircle( 298 | coords[2 * p0], coords[2 * p0 + 1], 299 | coords[2 * pr], coords[2 * pr + 1], 300 | coords[2 * pl], coords[2 * pl + 1], 301 | coords[2 * p1], coords[2 * p1 + 1])) { 302 | return; 303 | } 304 | 305 | const hal = this._halfedges[al]; 306 | const har = this._halfedges[ar]; 307 | const hbl = this._halfedges[bl]; 308 | const hbr = this._halfedges[br]; 309 | 310 | this._queueRemove(a0 / 3); 311 | this._queueRemove(b0 / 3); 312 | 313 | const t0 = this._addTriangle(p0, p1, pl, -1, hbl, hal, a0); 314 | const t1 = this._addTriangle(p1, p0, pr, t0, har, hbr, b0); 315 | 316 | this._legalize(t0 + 1); 317 | this._legalize(t1 + 2); 318 | } 319 | 320 | // handle a case where new vertex is on the edge of a triangle 321 | _handleCollinear(pn, a) { 322 | const a0 = a - a % 3; 323 | const al = a0 + (a + 1) % 3; 324 | const ar = a0 + (a + 2) % 3; 325 | const p0 = this.triangles[ar]; 326 | const pr = this.triangles[a]; 327 | const pl = this.triangles[al]; 328 | const hal = this._halfedges[al]; 329 | const har = this._halfedges[ar]; 330 | 331 | const b = this._halfedges[a]; 332 | 333 | if (b < 0) { 334 | const t0 = this._addTriangle(pn, p0, pr, -1, har, -1, a0); 335 | const t1 = this._addTriangle(p0, pn, pl, t0, -1, hal); 336 | this._legalize(t0 + 1); 337 | this._legalize(t1 + 2); 338 | return; 339 | } 340 | 341 | const b0 = b - b % 3; 342 | const bl = b0 + (b + 2) % 3; 343 | const br = b0 + (b + 1) % 3; 344 | const p1 = this.triangles[bl]; 345 | const hbl = this._halfedges[bl]; 346 | const hbr = this._halfedges[br]; 347 | 348 | this._queueRemove(b0 / 3); 349 | 350 | const t0 = this._addTriangle(p0, pr, pn, har, -1, -1, a0); 351 | const t1 = this._addTriangle(pr, p1, pn, hbr, -1, t0 + 1, b0); 352 | const t2 = this._addTriangle(p1, pl, pn, hbl, -1, t1 + 1); 353 | const t3 = this._addTriangle(pl, p0, pn, hal, t0 + 2, t2 + 1); 354 | 355 | this._legalize(t0); 356 | this._legalize(t1); 357 | this._legalize(t2); 358 | this._legalize(t3); 359 | } 360 | 361 | // priority queue methods 362 | 363 | _queuePush(t, error, rms) { 364 | const i = this._queue.length; 365 | this._queueIndices[t] = i; 366 | this._queue.push(t); 367 | this._errors.push(error); 368 | this._rmsSum += rms; 369 | this._queueUp(i); 370 | } 371 | 372 | _queuePop() { 373 | const n = this._queue.length - 1; 374 | this._queueSwap(0, n); 375 | this._queueDown(0, n); 376 | return this._queuePopBack(); 377 | } 378 | 379 | _queuePopBack() { 380 | const t = this._queue.pop(); 381 | this._errors.pop(); 382 | this._rmsSum -= this._rms[t]; 383 | this._queueIndices[t] = -1; 384 | return t; 385 | } 386 | 387 | _queueRemove(t) { 388 | const i = this._queueIndices[t]; 389 | if (i < 0) { 390 | const it = this._pending.indexOf(t); 391 | if (it !== -1) { 392 | this._pending[it] = this._pending[--this._pendingLen]; 393 | } else { 394 | throw new Error('Broken triangulation (something went wrong).'); 395 | } 396 | return; 397 | } 398 | const n = this._queue.length - 1; 399 | if (n !== i) { 400 | this._queueSwap(i, n); 401 | if (!this._queueDown(i, n)) { 402 | this._queueUp(i); 403 | } 404 | } 405 | this._queuePopBack(); 406 | } 407 | 408 | _queueLess(i, j) { 409 | return this._errors[i] > this._errors[j]; 410 | } 411 | 412 | _queueSwap(i, j) { 413 | const pi = this._queue[i]; 414 | const pj = this._queue[j]; 415 | this._queue[i] = pj; 416 | this._queue[j] = pi; 417 | this._queueIndices[pi] = j; 418 | this._queueIndices[pj] = i; 419 | const e = this._errors[i]; 420 | this._errors[i] = this._errors[j]; 421 | this._errors[j] = e; 422 | } 423 | 424 | _queueUp(j0) { 425 | let j = j0; 426 | while (true) { 427 | const i = (j - 1) >> 1; 428 | if (i === j || !this._queueLess(j, i)) { 429 | break; 430 | } 431 | this._queueSwap(i, j); 432 | j = i; 433 | } 434 | } 435 | 436 | _queueDown(i0, n) { 437 | let i = i0; 438 | while (true) { 439 | const j1 = 2 * i + 1; 440 | if (j1 >= n || j1 < 0) { 441 | break; 442 | } 443 | const j2 = j1 + 1; 444 | let j = j1; 445 | if (j2 < n && this._queueLess(j2, j1)) { 446 | j = j2; 447 | } 448 | if (!this._queueLess(j, i)) { 449 | break; 450 | } 451 | this._queueSwap(i, j); 452 | i = j; 453 | } 454 | return i > i0; 455 | } 456 | } 457 | 458 | function orient(ax, ay, bx, by, cx, cy) { 459 | return (bx - cx) * (ay - cy) - (by - cy) * (ax - cx); 460 | } 461 | 462 | function inCircle(ax, ay, bx, by, cx, cy, px, py) { 463 | const dx = ax - px; 464 | const dy = ay - py; 465 | const ex = bx - px; 466 | const ey = by - py; 467 | const fx = cx - px; 468 | const fy = cy - py; 469 | 470 | const ap = dx * dx + dy * dy; 471 | const bp = ex * ex + ey * ey; 472 | const cp = fx * fx + fy * fy; 473 | 474 | return dx * (ey * cp - bp * fy) - 475 | dy * (ex * cp - bp * fx) + 476 | ap * (ex * fy - ey * fx) < 0; 477 | } 478 | -------------------------------------------------------------------------------- /test/bench_js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "esm": "^3.2.25", 4 | "pngjs": "^5.0.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/bench_js/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | esm@^3.2.25: 6 | version "3.2.25" 7 | resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" 8 | integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== 9 | 10 | pngjs@^5.0.0: 11 | version "5.0.0" 12 | resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" 13 | integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== 14 | -------------------------------------------------------------------------------- /test/data/fuji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylebarron/pydelatin/49ffad710548f1bd2839bf49b97a44431d88a382/test/data/fuji.png --------------------------------------------------------------------------------